bluesky appview implementation using microcosm and other services
server.reddwarf.app
appview
bluesky
reddwarf
microcosm
1package sticket
2
3import (
4 "log"
5 "net/http"
6 "sync"
7
8 "github.com/google/uuid"
9 "github.com/gorilla/websocket"
10)
11
12const HeaderKey = "X-STICKET"
13
14type Event struct {
15 Type string `json:"type"`
16 Payload any `json:"payload"`
17}
18
19type Manager struct {
20 clients map[string]*websocket.Conn
21 mu sync.RWMutex
22 upgrader websocket.Upgrader
23}
24
25func New() *Manager {
26 return &Manager{
27 clients: make(map[string]*websocket.Conn),
28 upgrader: websocket.Upgrader{
29 CheckOrigin: func(r *http.Request) bool { return true },
30 },
31 }
32}
33
34func (m *Manager) HandleWS(w http.ResponseWriter, r *http.Request) {
35 conn, err := m.upgrader.Upgrade(w, r, nil)
36 if err != nil {
37 log.Printf("Sticket: Failed to upgrade: %v", err)
38 return
39 }
40
41 clientUUID := r.URL.Query().Get("uuid")
42 //isNew := false
43
44 if clientUUID == "" {
45 //isNew = true
46 clientUUID = uuid.New().String()
47 } else {
48 if _, err := uuid.Parse(clientUUID); err != nil {
49 //isNew = true
50 clientUUID = uuid.New().String()
51 }
52 }
53
54 m.register(clientUUID, conn)
55
56 defer m.unregister(clientUUID)
57
58 initEvent := Event{
59 Type: "init",
60 Payload: map[string]string{"uuid": clientUUID},
61 }
62 if err := m.sendJson(conn, initEvent); err != nil {
63 return
64 }
65
66 for {
67 if _, _, err := conn.NextReader(); err != nil {
68 break
69 }
70 }
71}
72
73func (m *Manager) SendToClient(uuid string, eventType string, data any) bool {
74 m.mu.RLock()
75 conn, ok := m.clients[uuid]
76 m.mu.RUnlock()
77
78 if !ok {
79 return false
80 }
81
82 evt := Event{
83 Type: eventType,
84 Payload: data,
85 }
86
87 m.mu.Lock()
88 defer m.mu.Unlock()
89
90 if conn, ok = m.clients[uuid]; !ok {
91 return false
92 }
93
94 if err := m.sendJson(conn, evt); err != nil {
95 conn.Close()
96 delete(m.clients, uuid)
97 return false
98 }
99
100 return true
101}
102
103func GetUUIDFromRequest(r *http.Request) string {
104 return r.Header.Get(HeaderKey)
105}
106
107func (m *Manager) register(id string, conn *websocket.Conn) {
108 m.mu.Lock()
109 defer m.mu.Unlock()
110
111 if existing, ok := m.clients[id]; ok {
112 existing.Close()
113 }
114 m.clients[id] = conn
115 log.Printf("Sticket: Client connected [%s]", id)
116}
117
118func (m *Manager) unregister(id string) {
119 m.mu.Lock()
120 defer m.mu.Unlock()
121 if conn, ok := m.clients[id]; ok {
122 conn.Close()
123 delete(m.clients, id)
124 log.Printf("Sticket: Client disconnected [%s]", id)
125 }
126}
127
128func (m *Manager) sendJson(conn *websocket.Conn, v any) error {
129 return conn.WriteJSON(v)
130}