-
Notifications
You must be signed in to change notification settings - Fork 1
/
connection.go
146 lines (126 loc) · 4.28 KB
/
connection.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
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
package waterlink
import (
"errors"
"fmt"
"github.com/gompus/snowflake"
"github.com/gorilla/websocket"
"github.com/lukasl-dev/waterlink/v2/internal/message"
"github.com/lukasl-dev/waterlink/v2/internal/message/opcode"
"github.com/lukasl-dev/waterlink/v2/internal/pkgerror"
"net/http"
"time"
)
// Connection is used to receive and dispatch messages from the
type Connection struct {
opts ConnectionOptions
// conn is the underlying websocket connection.
conn *websocket.Conn
// closed indicates whether the connection has been closed.
closed bool
// sessionResumed reports whether a previous session has been resumed.
sessionResumed bool
// apiVersion is server's API version that was acquired during the handshake.
apiVersion string
}
// Open opens a new websocket connection to addr. The given creds are used to
// authenticate the connection to use the protocol.
func Open(addr string, creds Credentials, opts ...ConnectionOptions) (*Connection, error) {
switch {
case len(opts) == 0:
opts = []ConnectionOptions{defaultConnectionOptions}
case len(opts) > 1:
panic(pkgerror.New("connection: open: too many options"))
}
conn, resp, err := websocket.DefaultDialer.Dial(addr, creds.header())
if err != nil {
return nil, pkgerror.Wrap("connection: open", err)
}
return wrapConn(opts[0], conn, resp.Header), nil
}
// wrapConn wraps the given websocket connection.
func wrapConn(opts ConnectionOptions, conn *websocket.Conn, h http.Header) *Connection {
c := &Connection{opts: opts, conn: conn}
c.header(h)
if opts.EventHandler != nil {
go c.listenForEvents()
}
return c
}
// header adapts the given header's values to the connection's internal state.
func (conn *Connection) header(h http.Header) {
conn.sessionResumed = h.Get("Session-Resumed") == "true"
conn.apiVersion = h.Get("Lavalink-Api-Version")
}
// Closed returns whether the connection has been closed.
func (conn *Connection) Closed() bool {
return conn.closed
}
// Close closes the underlying websocket connection.
func (conn *Connection) Close() error {
conn.closed = true
return conn.conn.Close()
}
// SessionResumed returns true whether a previous session has been resumed.
func (conn *Connection) SessionResumed() bool {
return conn.sessionResumed
}
// APIVersion returns the server's API version that was acquired during the
// handshake.
func (conn *Connection) APIVersion() string {
return conn.apiVersion
}
// Guild returns a Guild used to interact with a specific guild. The
// availability is not checked client-side.
func (conn *Connection) Guild(id snowflake.Snowflake) Guild {
return Guild{conn: conn, id: id}
}
// ConfigureResuming enable the resumption of the session and defines the number
// of seconds after which the session will be considered expired server-side. This
// is useful to avoid stopping the audio players that are related to the session.
func (conn *Connection) ConfigureResuming(key string, timeout time.Duration) error {
return pkgerror.Wrap("connection: configure resuming", conn.conn.WriteJSON(
message.ConfigureResuming{
Outgoing: message.Outgoing{Op: opcode.ConfigureResuming},
Key: &key,
Timeout: uint(timeout.Seconds()),
},
))
}
// DisableResuming disables the resumption of the session. If disabled, audio
// players will stop immediately after the connection is closed.
func (conn *Connection) DisableResuming() error {
return pkgerror.Wrap("connection: disable resuming", conn.conn.WriteJSON(
message.ConfigureResuming{
Outgoing: message.Outgoing{Op: opcode.ConfigureResuming},
},
))
}
// listenForEvents runs the event loop that reads incoming messages from the
// server and dispatches them to the given eventBus.
func (conn *Connection) listenForEvents() {
bus := newEventBus(conn.opts.EventHandler)
for {
_, data, err := conn.conn.ReadMessage()
if err != nil {
conn.handleEventError(err)
break
}
if err := bus.receive(data); err != nil {
conn.handleEventError(err)
}
}
}
// handleEventError handles errors that occur during the event loop.
func (conn *Connection) handleEventError(err interface{}) {
_ = conn.Close()
if conn.opts.HandleEventError != nil {
var issue error
switch err := err.(type) {
case error:
issue = err
default:
issue = errors.New(fmt.Sprint(err))
}
conn.opts.HandleEventError(pkgerror.Wrap("connection", issue))
}
}