171 lines
4.0 KiB
Go
171 lines
4.0 KiB
Go
|
// +build js
|
||
|
|
||
|
// Package wsjs implements typed access to the browser javascript WebSocket API.
|
||
|
//
|
||
|
// https://developer.mozilla.org/en-US/docs/Web/API/WebSocket
|
||
|
package wsjs
|
||
|
|
||
|
import (
|
||
|
"syscall/js"
|
||
|
)
|
||
|
|
||
|
func handleJSError(err *error, onErr func()) {
|
||
|
r := recover()
|
||
|
|
||
|
if jsErr, ok := r.(js.Error); ok {
|
||
|
*err = jsErr
|
||
|
|
||
|
if onErr != nil {
|
||
|
onErr()
|
||
|
}
|
||
|
return
|
||
|
}
|
||
|
|
||
|
if r != nil {
|
||
|
panic(r)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// New is a wrapper around the javascript WebSocket constructor.
|
||
|
func New(url string, protocols []string) (c WebSocket, err error) {
|
||
|
defer handleJSError(&err, func() {
|
||
|
c = WebSocket{}
|
||
|
})
|
||
|
|
||
|
jsProtocols := make([]interface{}, len(protocols))
|
||
|
for i, p := range protocols {
|
||
|
jsProtocols[i] = p
|
||
|
}
|
||
|
|
||
|
c = WebSocket{
|
||
|
v: js.Global().Get("WebSocket").New(url, jsProtocols),
|
||
|
}
|
||
|
|
||
|
c.setBinaryType("arraybuffer")
|
||
|
|
||
|
return c, nil
|
||
|
}
|
||
|
|
||
|
// WebSocket is a wrapper around a javascript WebSocket object.
|
||
|
type WebSocket struct {
|
||
|
v js.Value
|
||
|
}
|
||
|
|
||
|
func (c WebSocket) setBinaryType(typ string) {
|
||
|
c.v.Set("binaryType", string(typ))
|
||
|
}
|
||
|
|
||
|
func (c WebSocket) addEventListener(eventType string, fn func(e js.Value)) func() {
|
||
|
f := js.FuncOf(func(this js.Value, args []js.Value) interface{} {
|
||
|
fn(args[0])
|
||
|
return nil
|
||
|
})
|
||
|
c.v.Call("addEventListener", eventType, f)
|
||
|
|
||
|
return func() {
|
||
|
c.v.Call("removeEventListener", eventType, f)
|
||
|
f.Release()
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// CloseEvent is the type passed to a WebSocket close handler.
|
||
|
type CloseEvent struct {
|
||
|
Code uint16
|
||
|
Reason string
|
||
|
WasClean bool
|
||
|
}
|
||
|
|
||
|
// OnClose registers a function to be called when the WebSocket is closed.
|
||
|
func (c WebSocket) OnClose(fn func(CloseEvent)) (remove func()) {
|
||
|
return c.addEventListener("close", func(e js.Value) {
|
||
|
ce := CloseEvent{
|
||
|
Code: uint16(e.Get("code").Int()),
|
||
|
Reason: e.Get("reason").String(),
|
||
|
WasClean: e.Get("wasClean").Bool(),
|
||
|
}
|
||
|
fn(ce)
|
||
|
})
|
||
|
}
|
||
|
|
||
|
// OnError registers a function to be called when there is an error
|
||
|
// with the WebSocket.
|
||
|
func (c WebSocket) OnError(fn func(e js.Value)) (remove func()) {
|
||
|
return c.addEventListener("error", fn)
|
||
|
}
|
||
|
|
||
|
// MessageEvent is the type passed to a message handler.
|
||
|
type MessageEvent struct {
|
||
|
// string or []byte.
|
||
|
Data interface{}
|
||
|
|
||
|
// There are more fields to the interface but we don't use them.
|
||
|
// See https://developer.mozilla.org/en-US/docs/Web/API/MessageEvent
|
||
|
}
|
||
|
|
||
|
// OnMessage registers a function to be called when the WebSocket receives a message.
|
||
|
func (c WebSocket) OnMessage(fn func(m MessageEvent)) (remove func()) {
|
||
|
return c.addEventListener("message", func(e js.Value) {
|
||
|
var data interface{}
|
||
|
|
||
|
arrayBuffer := e.Get("data")
|
||
|
if arrayBuffer.Type() == js.TypeString {
|
||
|
data = arrayBuffer.String()
|
||
|
} else {
|
||
|
data = extractArrayBuffer(arrayBuffer)
|
||
|
}
|
||
|
|
||
|
me := MessageEvent{
|
||
|
Data: data,
|
||
|
}
|
||
|
fn(me)
|
||
|
|
||
|
return
|
||
|
})
|
||
|
}
|
||
|
|
||
|
// Subprotocol returns the WebSocket subprotocol in use.
|
||
|
func (c WebSocket) Subprotocol() string {
|
||
|
return c.v.Get("protocol").String()
|
||
|
}
|
||
|
|
||
|
// OnOpen registers a function to be called when the WebSocket is opened.
|
||
|
func (c WebSocket) OnOpen(fn func(e js.Value)) (remove func()) {
|
||
|
return c.addEventListener("open", fn)
|
||
|
}
|
||
|
|
||
|
// Close closes the WebSocket with the given code and reason.
|
||
|
func (c WebSocket) Close(code int, reason string) (err error) {
|
||
|
defer handleJSError(&err, nil)
|
||
|
c.v.Call("close", code, reason)
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
// SendText sends the given string as a text message
|
||
|
// on the WebSocket.
|
||
|
func (c WebSocket) SendText(v string) (err error) {
|
||
|
defer handleJSError(&err, nil)
|
||
|
c.v.Call("send", v)
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
// SendBytes sends the given message as a binary message
|
||
|
// on the WebSocket.
|
||
|
func (c WebSocket) SendBytes(v []byte) (err error) {
|
||
|
defer handleJSError(&err, nil)
|
||
|
c.v.Call("send", uint8Array(v))
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
func extractArrayBuffer(arrayBuffer js.Value) []byte {
|
||
|
uint8Array := js.Global().Get("Uint8Array").New(arrayBuffer)
|
||
|
dst := make([]byte, uint8Array.Length())
|
||
|
js.CopyBytesToGo(dst, uint8Array)
|
||
|
return dst
|
||
|
}
|
||
|
|
||
|
func uint8Array(src []byte) js.Value {
|
||
|
uint8Array := js.Global().Get("Uint8Array").New(len(src))
|
||
|
js.CopyBytesToJS(uint8Array, src)
|
||
|
return uint8Array
|
||
|
}
|