2023-02-20 11:55:50 +01:00
|
|
|
//go:build js && wasm
|
2020-03-11 16:07:14 +01:00
|
|
|
|
2023-02-20 11:55:50 +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 {
|
2023-02-20 11:55:50 +01:00
|
|
|
M sync.Mutex
|
|
|
|
ws js.Value
|
|
|
|
conn chan struct{}
|
|
|
|
in chan []byte
|
2020-03-11 16:07:14 +01:00
|
|
|
onMessage handler
|
2023-02-20 11:55:50 +01:00
|
|
|
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{
|
2023-02-20 11:55:50 +01:00
|
|
|
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")
|
2023-02-20 11:55:50 +01:00
|
|
|
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")
|
2023-02-20 11:55:50 +01:00
|
|
|
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")
|
|
|
|
}
|