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