Skip to content

Instantly share code, notes, and snippets.

@ear7h
Created July 18, 2019 08:14
Show Gist options
  • Save ear7h/fc535f6af869cea9d8d42300ceb6c2e8 to your computer and use it in GitHub Desktop.
Save ear7h/fc535f6af869cea9d8d42300ceb6c2e8 to your computer and use it in GitHub Desktop.
client side WebSocket as a net.Conn (wasm)
// +build js
package websocket
import (
"fmt"
"io"
"net"
"syscall/js"
"time"
)
const blobToArr = `function blobToArr (b, cb) {
new Response(b).arrayBuffer().then((arr) => cb(new Uint8Array(arr)))
}`
func init() {
js.Global().Call("eval", blobToArr)
}
func Dial(addr string) (net.Conn, error) {
ret := &conn{
in: make(chan []byte),
more: nil,
isOpen: true,
addr: wsAddr{addr},
}
ws := js.Global().Get("WebSocket").New(addr)
ws.Call("addEventListener",
"message",
js.FuncOf(ret.onMessage))
ws.Call("addEventListener",
"error",
js.FuncOf(ret.onError))
start := make(chan struct{})
ws.Call("addEventListener",
"open",
js.FuncOf(func(_ js.Value, _ []js.Value) interface{} {
start <- struct{}{}
return nil
}))
ret.ws = ws
<-start
return ret, nil
}
type conn struct {
ws js.Value
in chan []byte
more []byte
isOpen bool
addr wsAddr
}
type wsAddr struct {
addr string
}
func (wsAddr) Network() string {
return "ws"
}
func (a wsAddr) String() string {
return a.addr
}
func (c *conn) LocalAddr() net.Addr {
return c.addr
}
func (c *conn) RemoteAddr() net.Addr {
return c.addr
}
func (c *conn) SetDeadline(t time.Time) error {return nil}
func (c *conn) SetReadDeadline(t time.Time) error {return nil}
func (c *conn) SetWriteDeadline(t time.Time) error {return nil}
func (c *conn) Write(byt []byte) (int, error) {
fmt.Println("Write: ", len(byt))
uint8Array := js.Global().Get("Uint8Array").New(len(byt))
js.CopyBytesToJS(uint8Array, byt)
c.ws.Call("send", "")
c.ws.Call("send", uint8Array)
return len(byt), nil
}
func (c *conn) onError(_ js.Value, args []js.Value) interface{} {
err := args[0]
fmt.Println(err)
panic("ws error")
}
func (c *conn) onMessage(_ js.Value, args []js.Value) interface{} {
msg := args[0].Get("data")
switch {
case msg.InstanceOf(js.Global().Get("Blob")):
// new Uint8Array(await new Response(b).arrayBuffer())
cb := func(_ js.Value, args []js.Value) interface{} {
msg := args[0]
arr := make([]byte, msg.Length())
js.CopyBytesToGo(arr, msg)
fmt.Println("onMessage: ", len(arr))
if c.isOpen {
c.in <- arr
}
return nil
}
js.Global().Call("blobToArr",
msg,
js.FuncOf(cb))
default:
fmt.Println("unknown message type: ", msg)
fmt.Println(msg.Get("constructor").Get("name"))
panic("unknown message type")
}
return nil
}
func (c *conn) Read(byt []byte) (n int, err error) {
defer func() { fmt.Println("n: ", n) }()
fmt.Println("in read: ")
fmt.Println("len(byt): ", len(byt))
fmt.Println("len(c.more): ", len(c.more))
if len(c.more) != 0 {
//fmt.Println("c.more: ", c.more)
n = copy(byt, c.more)
c.more = c.more[n:]
if n == len(byt) {
// we filled the buffer
return n, nil
}
byt = byt[:n]
}
c.more, c.isOpen = <-c.in
fmt.Println("read from channel: ", len(c.more), c.isOpen)
n = copy(byt, c.more)
c.more = c.more[n:]
if !c.isOpen {
// the channel is closed and we
// wrote all that we needed into the
// givem buffer
return len(byt), io.EOF
}
return n, nil
}
func (c *conn) Close() error {
if c.isOpen {
c.isOpen = false
close(c.in)
}
return nil
}
@ear7h
Copy link
Author

ear7h commented Jul 18, 2019

I think the code for onMessage causes a race condition...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment