git.hdm.sh
    1
    2
    3
    4
    5
    6
    7
    8
    9
   10
   11
   12
   13
   14
   15
   16
   17
   18
   19
   20
   21
   22
   23
   24
   25
   26
   27
   28
   29
   30
   31
   32
   33
   34
   35
   36
   37
   38
   39
   40
   41
   42
   43
   44
   45
   46
   47
   48
   49
   50
   51
   52
   53
   54
   55
   56
   57
   58
   59
   60
   61
   62
   63
   64
   65
   66
   67
   68
   69
   70
   71
   72
   73
   74
   75
   76
   77
   78
   79
   80
   81
   82
   83
   84
   85
   86
   87
   88
   89
   90
   91
   92
   93
   94
   95
   96
   97
   98
   99
  100
  101
  102
  103
  104
  105
  106
  107
  108
  109
  110
  111
  112
  113
  114
  115
  116
  117
  118
  119
  120
  121
  122
  123
  124
  125
  126
  127
package main

import (
	"log"
	"net/http"
	"sync"

	"github.com/gorilla/websocket"
)

// CHANGE THESE
const (
	port       = ":8080"
	allowedURL = "https://YOURWEBSITE.com"
)

var (
	// current stored scratchpad content
	content     = ""
	contentLock sync.RWMutex

	// connected clients
	clients   = make(map[*websocket.Conn]bool)
	clientsMu sync.Mutex

	// WebSocket upgrader
	upgrader = websocket.Upgrader{
		CheckOrigin: func(r *http.Request) bool {
			origin := r.Header.Get("Origin")
			return origin == allowedURL
		},
	}
)

type Message struct {
	Type    string `json:"type"`
	Content string `json:"content"`
}

func main() {
	http.HandleFunc("/", serveHome)
	http.HandleFunc("/ws", handleWebSocket)

	log.Printf("Server starting on %s (allowing origin: %s)\n", port, allowedURL)
	if err := http.ListenAndServe(port, nil); err != nil {
		log.Fatal("ListenAndServe: ", err)
	}
}

// main page is just index.html
func serveHome(w http.ResponseWriter, r *http.Request) {
	if r.URL.Path != "/" {
		http.Error(w, "Not found", http.StatusNotFound)
		return
	}
	if r.Method != http.MethodGet {
		http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
		return
	}
	http.ServeFile(w, r, "index.html")
}

// the /ws endpoint works over WebSocket API
func handleWebSocket(w http.ResponseWriter, r *http.Request) {
	// HTTP -> WebSocket upgrade
	conn, err := upgrader.Upgrade(w, r, nil)
	if err != nil {
		log.Println("Upgrade error:", err)
		return
	}
	defer conn.Close()

	// register new client
	clientsMu.Lock()
	clients[conn] = true
	clientsMu.Unlock()

	// send current content to new client
	contentLock.RLock()
	currentContent := content
	contentLock.RUnlock()
	
	if err := conn.WriteJSON(Message{Type: "update", Content: currentContent}); err != nil {
		log.Println("Write error:", err)
		return
	}

	// kill client on disconnect
	defer func() {
		clientsMu.Lock()
		delete(clients, conn)
		clientsMu.Unlock()
	}()

	// wait and listen for client messages
	for {
		var msg Message
		err := conn.ReadJSON(&msg)
		if err != nil {
			if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) {
				log.Printf("WebSocket error: %v", err)
			}
			break
		}

		if msg.Type == "update" {
			contentLock.Lock()
			content = msg.Content
			contentLock.Unlock()
			broadcast(msg)
		}
	}
}

func broadcast(msg Message) {
	clientsMu.Lock()
	defer clientsMu.Unlock()

	for client := range clients {
		err := client.WriteJSON(msg)
		if err != nil {
			log.Printf("Broadcast error: %v", err)
			client.Close()
			delete(clients, client)
		}
	}
}