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