-
Notifications
You must be signed in to change notification settings - Fork 27
/
socket.go
115 lines (99 loc) · 2.47 KB
/
socket.go
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
package live
import (
"context"
"fmt"
"net/http"
"sync"
"golang.org/x/net/html"
"nhooyr.io/websocket"
)
const (
// maxMessageBufferSize the maximum number of messages per socket in a buffer.
maxMessageBufferSize = 16
)
// Socket describes a socket from the outside.
type Socket struct {
Session Session
currentRender *html.Node
msgs chan Event
closeSlow func()
data interface{}
dataMu sync.Mutex
}
// NewSocket creates a new socket.
func NewSocket(s Session) *Socket {
return &Socket{
Session: s,
msgs: make(chan Event, maxMessageBufferSize),
}
}
// Assigns returns the data currently assigned to this
// socket.
func (s *Socket) Assigns() interface{} {
s.dataMu.Lock()
defer s.dataMu.Unlock()
return s.data
}
// Assign set data to this socket. This will happen automatically
// if you return data from and `EventHander`.
func (s *Socket) Assign(data interface{}) {
s.dataMu.Lock()
defer s.dataMu.Unlock()
s.data = data
}
// Send an event to this socket.
func (s *Socket) Send(msg Event) {
select {
case s.msgs <- msg:
default:
go s.closeSlow()
}
}
// mount passes this socket to the handlers mount func. This returns data
// which we then set to the socket to store.
func (s *Socket) mount(ctx context.Context, h *Handler, r *http.Request, connected bool) error {
data, err := h.Mount(ctx, h, r, s, connected)
if err != nil {
return fmt.Errorf("mount error: %w", err)
}
s.Assign(data)
return nil
}
// render passes this socket to the handlers render func. This generates
// the HTML we should be showing to the socket. A diff is then run against
// previosuly generated HTML and patches sent to the socket.
func (s *Socket) render(ctx context.Context, h *Handler) error {
s.dataMu.Lock()
defer s.dataMu.Unlock()
// Render handler.
output, err := h.Render(ctx, h.t, s.data)
if err != nil {
return fmt.Errorf("render error: %w", err)
}
node, err := html.Parse(output)
if err != nil {
return fmt.Errorf("html parse error: %w", err)
}
// Get diff
if s.currentRender != nil {
patches, err := Diff(s.currentRender, node)
if err != nil {
return fmt.Errorf("diff error: %w", err)
}
if len(patches) != 0 {
msg := Event{
T: EventPatch,
Data: patches,
}
s.Send(msg)
}
}
s.currentRender = node
return nil
}
// assignWS connect a web socket to a socket.
func (s *Socket) assignWS(ws *websocket.Conn) {
s.closeSlow = func() {
ws.Close(websocket.StatusPolicyViolation, "socket too slow to keep up with messages")
}
}