1 | // +build js
|
---|
2 |
|
---|
3 | // Package wsjs implements typed access to the browser javascript WebSocket API.
|
---|
4 | //
|
---|
5 | // https://developer.mozilla.org/en-US/docs/Web/API/WebSocket
|
---|
6 | package wsjs
|
---|
7 |
|
---|
8 | import (
|
---|
9 | "syscall/js"
|
---|
10 | )
|
---|
11 |
|
---|
12 | func handleJSError(err *error, onErr func()) {
|
---|
13 | r := recover()
|
---|
14 |
|
---|
15 | if jsErr, ok := r.(js.Error); ok {
|
---|
16 | *err = jsErr
|
---|
17 |
|
---|
18 | if onErr != nil {
|
---|
19 | onErr()
|
---|
20 | }
|
---|
21 | return
|
---|
22 | }
|
---|
23 |
|
---|
24 | if r != nil {
|
---|
25 | panic(r)
|
---|
26 | }
|
---|
27 | }
|
---|
28 |
|
---|
29 | // New is a wrapper around the javascript WebSocket constructor.
|
---|
30 | func New(url string, protocols []string) (c WebSocket, err error) {
|
---|
31 | defer handleJSError(&err, func() {
|
---|
32 | c = WebSocket{}
|
---|
33 | })
|
---|
34 |
|
---|
35 | jsProtocols := make([]interface{}, len(protocols))
|
---|
36 | for i, p := range protocols {
|
---|
37 | jsProtocols[i] = p
|
---|
38 | }
|
---|
39 |
|
---|
40 | c = WebSocket{
|
---|
41 | v: js.Global().Get("WebSocket").New(url, jsProtocols),
|
---|
42 | }
|
---|
43 |
|
---|
44 | c.setBinaryType("arraybuffer")
|
---|
45 |
|
---|
46 | return c, nil
|
---|
47 | }
|
---|
48 |
|
---|
49 | // WebSocket is a wrapper around a javascript WebSocket object.
|
---|
50 | type WebSocket struct {
|
---|
51 | v js.Value
|
---|
52 | }
|
---|
53 |
|
---|
54 | func (c WebSocket) setBinaryType(typ string) {
|
---|
55 | c.v.Set("binaryType", string(typ))
|
---|
56 | }
|
---|
57 |
|
---|
58 | func (c WebSocket) addEventListener(eventType string, fn func(e js.Value)) func() {
|
---|
59 | f := js.FuncOf(func(this js.Value, args []js.Value) interface{} {
|
---|
60 | fn(args[0])
|
---|
61 | return nil
|
---|
62 | })
|
---|
63 | c.v.Call("addEventListener", eventType, f)
|
---|
64 |
|
---|
65 | return func() {
|
---|
66 | c.v.Call("removeEventListener", eventType, f)
|
---|
67 | f.Release()
|
---|
68 | }
|
---|
69 | }
|
---|
70 |
|
---|
71 | // CloseEvent is the type passed to a WebSocket close handler.
|
---|
72 | type CloseEvent struct {
|
---|
73 | Code uint16
|
---|
74 | Reason string
|
---|
75 | WasClean bool
|
---|
76 | }
|
---|
77 |
|
---|
78 | // OnClose registers a function to be called when the WebSocket is closed.
|
---|
79 | func (c WebSocket) OnClose(fn func(CloseEvent)) (remove func()) {
|
---|
80 | return c.addEventListener("close", func(e js.Value) {
|
---|
81 | ce := CloseEvent{
|
---|
82 | Code: uint16(e.Get("code").Int()),
|
---|
83 | Reason: e.Get("reason").String(),
|
---|
84 | WasClean: e.Get("wasClean").Bool(),
|
---|
85 | }
|
---|
86 | fn(ce)
|
---|
87 | })
|
---|
88 | }
|
---|
89 |
|
---|
90 | // OnError registers a function to be called when there is an error
|
---|
91 | // with the WebSocket.
|
---|
92 | func (c WebSocket) OnError(fn func(e js.Value)) (remove func()) {
|
---|
93 | return c.addEventListener("error", fn)
|
---|
94 | }
|
---|
95 |
|
---|
96 | // MessageEvent is the type passed to a message handler.
|
---|
97 | type MessageEvent struct {
|
---|
98 | // string or []byte.
|
---|
99 | Data interface{}
|
---|
100 |
|
---|
101 | // There are more fields to the interface but we don't use them.
|
---|
102 | // See https://developer.mozilla.org/en-US/docs/Web/API/MessageEvent
|
---|
103 | }
|
---|
104 |
|
---|
105 | // OnMessage registers a function to be called when the WebSocket receives a message.
|
---|
106 | func (c WebSocket) OnMessage(fn func(m MessageEvent)) (remove func()) {
|
---|
107 | return c.addEventListener("message", func(e js.Value) {
|
---|
108 | var data interface{}
|
---|
109 |
|
---|
110 | arrayBuffer := e.Get("data")
|
---|
111 | if arrayBuffer.Type() == js.TypeString {
|
---|
112 | data = arrayBuffer.String()
|
---|
113 | } else {
|
---|
114 | data = extractArrayBuffer(arrayBuffer)
|
---|
115 | }
|
---|
116 |
|
---|
117 | me := MessageEvent{
|
---|
118 | Data: data,
|
---|
119 | }
|
---|
120 | fn(me)
|
---|
121 |
|
---|
122 | return
|
---|
123 | })
|
---|
124 | }
|
---|
125 |
|
---|
126 | // Subprotocol returns the WebSocket subprotocol in use.
|
---|
127 | func (c WebSocket) Subprotocol() string {
|
---|
128 | return c.v.Get("protocol").String()
|
---|
129 | }
|
---|
130 |
|
---|
131 | // OnOpen registers a function to be called when the WebSocket is opened.
|
---|
132 | func (c WebSocket) OnOpen(fn func(e js.Value)) (remove func()) {
|
---|
133 | return c.addEventListener("open", fn)
|
---|
134 | }
|
---|
135 |
|
---|
136 | // Close closes the WebSocket with the given code and reason.
|
---|
137 | func (c WebSocket) Close(code int, reason string) (err error) {
|
---|
138 | defer handleJSError(&err, nil)
|
---|
139 | c.v.Call("close", code, reason)
|
---|
140 | return err
|
---|
141 | }
|
---|
142 |
|
---|
143 | // SendText sends the given string as a text message
|
---|
144 | // on the WebSocket.
|
---|
145 | func (c WebSocket) SendText(v string) (err error) {
|
---|
146 | defer handleJSError(&err, nil)
|
---|
147 | c.v.Call("send", v)
|
---|
148 | return err
|
---|
149 | }
|
---|
150 |
|
---|
151 | // SendBytes sends the given message as a binary message
|
---|
152 | // on the WebSocket.
|
---|
153 | func (c WebSocket) SendBytes(v []byte) (err error) {
|
---|
154 | defer handleJSError(&err, nil)
|
---|
155 | c.v.Call("send", uint8Array(v))
|
---|
156 | return err
|
---|
157 | }
|
---|
158 |
|
---|
159 | func extractArrayBuffer(arrayBuffer js.Value) []byte {
|
---|
160 | uint8Array := js.Global().Get("Uint8Array").New(arrayBuffer)
|
---|
161 | dst := make([]byte, uint8Array.Length())
|
---|
162 | js.CopyBytesToGo(dst, uint8Array)
|
---|
163 | return dst
|
---|
164 | }
|
---|
165 |
|
---|
166 | func uint8Array(src []byte) js.Value {
|
---|
167 | uint8Array := js.Global().Get("Uint8Array").New(len(src))
|
---|
168 | js.CopyBytesToJS(uint8Array, src)
|
---|
169 | return uint8Array
|
---|
170 | }
|
---|