feat: implement WebSocketInterface for handling WebSocket connections in WASM environment
All checks were successful
Bearer / scan (push) Successful in 24s
Go Build Multi-Platform / build (amd64, freebsd) (push) Successful in 30s
Go Build Multi-Platform / build (arm, linux) (push) Successful in 30s
Go Build Multi-Platform / build (amd64, windows) (push) Successful in 33s
Go Build Multi-Platform / build (arm64, linux) (push) Successful in 40s
Go Build Multi-Platform / build (arm64, windows) (push) Successful in 38s
Go Test Multi-Platform / Test (ubuntu-latest, arm64) (push) Successful in 48s
Go Revive Lint / lint (push) Successful in 1m3s
Go Test Multi-Platform / Test (ubuntu-latest, amd64) (push) Successful in 1m37s
Run Gosec / tests (push) Successful in 1m11s
Go Build Multi-Platform / build (amd64, darwin) (push) Successful in 9m22s
Go Build Multi-Platform / build (amd64, linux) (push) Successful in 9m24s
Go Build Multi-Platform / build (arm, freebsd) (push) Successful in 9m22s
Go Build Multi-Platform / build (arm64, darwin) (push) Successful in 9m22s
Go Build Multi-Platform / build (arm, windows) (push) Successful in 9m24s
Go Build Multi-Platform / build (arm64, freebsd) (push) Successful in 9m24s
Go Build Multi-Platform / Create Release (push) Has been skipped
All checks were successful
Bearer / scan (push) Successful in 24s
Go Build Multi-Platform / build (amd64, freebsd) (push) Successful in 30s
Go Build Multi-Platform / build (arm, linux) (push) Successful in 30s
Go Build Multi-Platform / build (amd64, windows) (push) Successful in 33s
Go Build Multi-Platform / build (arm64, linux) (push) Successful in 40s
Go Build Multi-Platform / build (arm64, windows) (push) Successful in 38s
Go Test Multi-Platform / Test (ubuntu-latest, arm64) (push) Successful in 48s
Go Revive Lint / lint (push) Successful in 1m3s
Go Test Multi-Platform / Test (ubuntu-latest, amd64) (push) Successful in 1m37s
Run Gosec / tests (push) Successful in 1m11s
Go Build Multi-Platform / build (amd64, darwin) (push) Successful in 9m22s
Go Build Multi-Platform / build (amd64, linux) (push) Successful in 9m24s
Go Build Multi-Platform / build (arm, freebsd) (push) Successful in 9m22s
Go Build Multi-Platform / build (arm64, darwin) (push) Successful in 9m22s
Go Build Multi-Platform / build (arm, windows) (push) Successful in 9m24s
Go Build Multi-Platform / build (arm64, freebsd) (push) Successful in 9m24s
Go Build Multi-Platform / Create Release (push) Has been skipped
This commit is contained in:
261
pkg/interfaces/websocket_wasm.go
Normal file
261
pkg/interfaces/websocket_wasm.go
Normal file
@@ -0,0 +1,261 @@
|
||||
//go:build js && wasm
|
||||
// +build js,wasm
|
||||
|
||||
package interfaces
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"net"
|
||||
"sync"
|
||||
"syscall/js"
|
||||
"time"
|
||||
|
||||
"git.quad4.io/Networks/Reticulum-Go/pkg/common"
|
||||
"git.quad4.io/Networks/Reticulum-Go/pkg/debug"
|
||||
)
|
||||
|
||||
type WebSocketInterface struct {
|
||||
BaseInterface
|
||||
wsURL string
|
||||
ws js.Value
|
||||
connected bool
|
||||
mutex sync.RWMutex
|
||||
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 = 1064
|
||||
ws.Bitrate = 10000000
|
||||
|
||||
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 packetData []byte
|
||||
if data.Type() == js.TypeString {
|
||||
packetData = []byte(data.String())
|
||||
} else if data.Type() == js.TypeObject {
|
||||
array := js.Global().Get("Uint8Array").New(data)
|
||||
length := array.Get("length").Int()
|
||||
packetData = make([]byte, length)
|
||||
js.CopyBytesToGo(packetData, array)
|
||||
} else {
|
||||
debug.Log(debug.DEBUG_ERROR, "Unknown WebSocket message type", "type", data.Type().String())
|
||||
return nil
|
||||
}
|
||||
|
||||
if len(packetData) < 4 {
|
||||
debug.Log(debug.DEBUG_ERROR, "WebSocket message too short", "bytes", len(packetData))
|
||||
return nil
|
||||
}
|
||||
|
||||
packetLen := binary.BigEndian.Uint32(packetData[:4])
|
||||
if len(packetData) < int(packetLen)+4 {
|
||||
debug.Log(debug.DEBUG_ERROR, "WebSocket message incomplete", "expected", packetLen+4, "got", len(packetData))
|
||||
return nil
|
||||
}
|
||||
|
||||
packet := packetData[4 : 4+packetLen]
|
||||
|
||||
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(2 * time.Second)
|
||||
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")
|
||||
}
|
||||
|
||||
packetLen := uint32(len(data))
|
||||
packet := make([]byte, 4+len(data))
|
||||
binary.BigEndian.PutUint32(packet[:4], packetLen)
|
||||
copy(packet[4:], data)
|
||||
|
||||
array := js.Global().Get("Uint8Array").New(len(packet))
|
||||
js.CopyBytesToJS(array, packet)
|
||||
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user