zocket/client_conn.go

167 lines
4.1 KiB
Go
Raw Permalink Normal View History

//go:build js && wasm
2020-03-11 16:07:14 +01:00
package zocket
2020-03-11 16:07:14 +01:00
import (
"context"
"errors"
"fmt"
"net"
"runtime/debug"
"sync"
"syscall/js"
"time"
)
type handler func(this js.Value, args []js.Value) interface{}
// ClientConnection is a TCP like Connection between the wasm and a server.
type ClientConnection struct {
M sync.Mutex
ws js.Value
conn chan struct{}
in chan []byte
2020-03-11 16:07:14 +01:00
onMessage handler
buffered []byte
2020-03-11 16:07:14 +01:00
}
func makeHandler(c chan []byte) handler {
return func(this js.Value, args []js.Value) interface{} {
e := args[0]
array := e.Get("data") // is an arraybuffer
u8arr := js.Global().Get("Uint8Array").New(array)
//fmt.Printf("arr: %v, %+v\n", u8arr.Type(), u8arr)
bytes := make([]byte, u8arr.Get("byteLength").Int())
/* ln := */ js.CopyBytesToGo(bytes, u8arr)
//fmt.Printf("got %v bytes [ID: %v]\n", ln, e.Get("lastEventId").String())
//fmt.Printf("message: %+v\n", bytes)
c <- bytes
return nil
}
}
func Dial(ctx context.Context, target string) (net.Conn, error) {
fmt.Printf("dialing %v\n", target)
ret := ClientConnection{
ws: js.Global().Get("WebSocket").New(target),
in: make(chan []byte),
2020-03-11 16:07:14 +01:00
conn: make(chan struct{}),
}
ret.ws.Set("binaryType", "arraybuffer")
ret.onMessage = makeHandler(ret.in)
ret.ws.Call("addEventListener", "open", js.FuncOf(
func(this js.Value, args []js.Value) interface{} {
fmt.Println("Opened")
close(ret.conn)
return nil
}))
ret.ws.Call("addEventListener", "error", js.FuncOf(
func(this js.Value, args []js.Value) interface{} {
fmt.Println("error")
return nil
}))
ret.ws.Call("addEventListener", "close", js.FuncOf(
func(this js.Value, args []js.Value) interface{} {
fmt.Println("close")
return nil
}))
// MessageEvent: [all ro]
// .data - the data sent
// .origin
// .lastEventId
// .source -
ret.ws.Call("addEventListener", "message", js.FuncOf(ret.onMessage))
<-ret.conn // block until the connection is really open
return ret, nil
}
// Read gets some bytes from a ws frame. Blocks.
func (c ClientConnection) Read(b []byte) (int, error) {
fmt.Println("client_read")
c.M.Lock()
defer c.M.Unlock()
if len(c.buffered) > 0 {
for i, _ := range b {
if i >= len(c.buffered) {
c.buffered = nil
return i, nil
}
b[i] = c.buffered[i]
}
if len(c.buffered) > len(b) {
c.buffered = c.buffered[len(b):]
}
return len(b), nil
}
var err error = nil
bytes, ok := <-c.in
if !ok {
err = errors.New("read from closed connection")
}
for i, _ := range b {
if i >= len(bytes) {
fmt.Printf("read %v bytes\n", i)
return i, nil
}
b[i] = bytes[i]
}
if len(bytes) > len(b) {
c.buffered = bytes[len(b):]
}
fmt.Printf("read %v bytes\n", len(b))
return len(b), err
}
// Write puts some bytes packed into websocket frames on the wire.
func (c ClientConnection) Write(b []byte) (int, error) {
array := js.Global().Get("Uint8Array").New(len(b))
n := js.CopyBytesToJS(array, b)
c.ws.Call("send", array)
return n, nil
}
// Close terminates the connection nicely.
func (c ClientConnection) Close() error {
fmt.Println("client_close")
debug.PrintStack()
c.ws.Call("close")
return nil
}
// LocalAddr returns the adress of the local endpoint. Since we operate within a sandbox we don't have any. return a
// dummy and make underlying layers think they're using a tcp conection.
func (c ClientConnection) LocalAddr() net.Addr {
fmt.Println("DBG: call LocalAddr() on ClientConnection")
return Addr{
2020-03-11 16:07:14 +01:00
network: "tcp",
address: "0.0.0.0",
}
}
// RemoteAddr returns the adress of the remote endpoint.
func (c ClientConnection) RemoteAddr() net.Addr {
fmt.Println("DBG: call RemoteAddr() on ClientConnection")
return Addr{
2020-03-11 16:07:14 +01:00
network: "tcp",
// TODO: Use the real servers adress somehow (or none at all? :think:)
address: "127.0.0.1",
}
}
func (c ClientConnection) SetDeadline(t time.Time) error {
return errors.New("SetDeadline not implemented")
}
func (c ClientConnection) SetReadDeadline(t time.Time) error {
return errors.New("SetReadDeadline not implemented")
}
func (c ClientConnection) SetWriteDeadline(t time.Time) error {
return errors.New("SetWriteDeadline not implemented")
}