Files
Reticulum-Go/pkg/interfaces/websocket_wasm.go

252 lines
5.2 KiB
Go

//go:build js && wasm
// +build js,wasm
package interfaces
import (
"fmt"
"net"
"syscall/js"
"time"
"git.quad4.io/Networks/Reticulum-Go/pkg/common"
"git.quad4.io/Networks/Reticulum-Go/pkg/debug"
)
const (
WS_MTU = 1064
WS_BITRATE = 10000000
WS_RECONNECT_DELAY = 2 * time.Second
)
type WebSocketInterface struct {
BaseInterface
wsURL string
ws js.Value
connected bool
messageQueue [][]byte
}
func NewWebSocketInterface(name string, wsURL string, enabled bool) (*WebSocketInterface, error) {
ws := &WebSocketInterface{
BaseInterface: NewBaseInterface(name, common.IF_TYPE_UDP, enabled),
wsURL: wsURL,
messageQueue: make([][]byte, 0),
}
ws.MTU = WS_MTU
ws.Bitrate = WS_BITRATE
return ws, nil
}
func (wsi *WebSocketInterface) GetName() string {
return wsi.Name
}
func (wsi *WebSocketInterface) GetType() common.InterfaceType {
return wsi.Type
}
func (wsi *WebSocketInterface) GetMode() common.InterfaceMode {
return wsi.Mode
}
func (wsi *WebSocketInterface) IsOnline() bool {
wsi.Mutex.RLock()
defer wsi.Mutex.RUnlock()
return wsi.Online && wsi.connected
}
func (wsi *WebSocketInterface) IsDetached() bool {
wsi.Mutex.RLock()
defer wsi.Mutex.RUnlock()
return wsi.Detached
}
func (wsi *WebSocketInterface) Detach() {
wsi.Mutex.Lock()
defer wsi.Mutex.Unlock()
wsi.Detached = true
wsi.Online = false
wsi.closeWebSocket()
}
func (wsi *WebSocketInterface) Enable() {
wsi.Mutex.Lock()
defer wsi.Mutex.Unlock()
wsi.Enabled = true
}
func (wsi *WebSocketInterface) Disable() {
wsi.Mutex.Lock()
defer wsi.Mutex.Unlock()
wsi.Enabled = false
wsi.closeWebSocket()
}
func (wsi *WebSocketInterface) Start() error {
wsi.Mutex.Lock()
defer wsi.Mutex.Unlock()
if wsi.ws.Truthy() {
return fmt.Errorf("WebSocket already started")
}
ws := js.Global().Get("WebSocket").New(wsi.wsURL)
ws.Set("binaryType", "arraybuffer")
ws.Set("onopen", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
wsi.Mutex.Lock()
wsi.connected = true
wsi.Online = true
wsi.Mutex.Unlock()
debug.Log(debug.DEBUG_INFO, "WebSocket connected", "name", wsi.Name, "url", wsi.wsURL)
wsi.Mutex.Lock()
queue := make([][]byte, len(wsi.messageQueue))
copy(queue, wsi.messageQueue)
wsi.messageQueue = wsi.messageQueue[:0]
wsi.Mutex.Unlock()
for _, msg := range queue {
wsi.sendWebSocketMessage(msg)
}
return nil
}))
ws.Set("onmessage", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
if len(args) < 1 {
return nil
}
event := args[0]
data := event.Get("data")
var packet []byte
if data.Type() == js.TypeString {
packet = []byte(data.String())
} else if data.Type() == js.TypeObject {
array := js.Global().Get("Uint8Array").New(data)
length := array.Get("length").Int()
packet = make([]byte, length)
js.CopyBytesToGo(packet, array)
} else {
debug.Log(debug.DEBUG_ERROR, "Unknown WebSocket message type", "type", data.Type().String())
return nil
}
if len(packet) < 1 {
debug.Log(debug.DEBUG_ERROR, "WebSocket message empty")
return nil
}
wsi.Mutex.Lock()
wsi.RxBytes += uint64(len(packet))
wsi.Mutex.Unlock()
wsi.ProcessIncoming(packet)
return nil
}))
ws.Set("onerror", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
debug.Log(debug.DEBUG_ERROR, "WebSocket error", "name", wsi.Name)
return nil
}))
ws.Set("onclose", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
wsi.Mutex.Lock()
wsi.connected = false
wsi.Online = false
wsi.Mutex.Unlock()
debug.Log(debug.DEBUG_INFO, "WebSocket closed", "name", wsi.Name)
if wsi.Enabled && !wsi.Detached {
time.Sleep(WS_RECONNECT_DELAY)
go wsi.Start()
}
return nil
}))
wsi.ws = ws
return nil
}
func (wsi *WebSocketInterface) Stop() error {
wsi.Mutex.Lock()
defer wsi.Mutex.Unlock()
wsi.Enabled = false
wsi.closeWebSocket()
return nil
}
func (wsi *WebSocketInterface) closeWebSocket() {
if wsi.ws.Truthy() {
wsi.ws.Call("close")
wsi.ws = js.Value{}
}
wsi.connected = false
wsi.Online = false
}
func (wsi *WebSocketInterface) Send(data []byte, addr string) error {
if !wsi.IsEnabled() {
return fmt.Errorf("interface not enabled")
}
wsi.Mutex.Lock()
wsi.TxBytes += uint64(len(data))
wsi.Mutex.Unlock()
if !wsi.connected {
wsi.Mutex.Lock()
wsi.messageQueue = append(wsi.messageQueue, data)
wsi.Mutex.Unlock()
return nil
}
return wsi.sendWebSocketMessage(data)
}
func (wsi *WebSocketInterface) sendWebSocketMessage(data []byte) error {
if !wsi.ws.Truthy() {
return fmt.Errorf("WebSocket not initialized")
}
if wsi.ws.Get("readyState").Int() != 1 {
return fmt.Errorf("WebSocket not open")
}
array := js.Global().Get("Uint8Array").New(len(data))
js.CopyBytesToJS(array, data)
wsi.ws.Call("send", array)
debug.Log(debug.DEBUG_VERBOSE, "WebSocket sent packet", "name", wsi.Name, "bytes", len(data))
return nil
}
func (wsi *WebSocketInterface) ProcessOutgoing(data []byte) error {
return wsi.Send(data, "")
}
func (wsi *WebSocketInterface) GetConn() net.Conn {
return nil
}
func (wsi *WebSocketInterface) GetMTU() int {
return wsi.MTU
}
func (wsi *WebSocketInterface) IsEnabled() bool {
wsi.Mutex.RLock()
defer wsi.Mutex.RUnlock()
return wsi.Enabled && wsi.Online && !wsi.Detached
}