All checks were successful
Bearer / scan (push) Successful in 28s
Go Build Multi-Platform / build (amd64, darwin) (push) Successful in 35s
Go Build Multi-Platform / build (amd64, freebsd) (push) Successful in 33s
Go Build Multi-Platform / build (amd64, linux) (push) Successful in 32s
Go Build Multi-Platform / build (amd64, windows) (push) Successful in 32s
Go Build Multi-Platform / build (arm, freebsd) (push) Successful in 35s
Go Build Multi-Platform / build (arm, linux) (push) Successful in 34s
Go Build Multi-Platform / build (arm, windows) (push) Successful in 32s
Go Build Multi-Platform / build (arm64, darwin) (push) Successful in 35s
Go Build Multi-Platform / build (arm64, freebsd) (push) Successful in 34s
Go Build Multi-Platform / build (arm64, windows) (push) Successful in 31s
Go Build Multi-Platform / build (arm64, linux) (push) Successful in 33s
Go Build Multi-Platform / build (wasm, js) (push) Successful in 39s
Go Test Multi-Platform / Test (ubuntu-latest, arm64) (push) Successful in 1m6s
Go Revive Lint / lint (push) Successful in 53s
Run Gosec / tests (push) Successful in 1m22s
Go Test Multi-Platform / Test (ubuntu-latest, amd64) (push) Successful in 1m33s
Go Build Multi-Platform / Create Release (push) Has been skipped
381 lines
9.4 KiB
Go
381 lines
9.4 KiB
Go
//go:build js && wasm
|
|
// +build js,wasm
|
|
|
|
package wasm
|
|
|
|
import (
|
|
"encoding/hex"
|
|
"fmt"
|
|
"syscall/js"
|
|
|
|
"git.quad4.io/Networks/Reticulum-Go/pkg/common"
|
|
"git.quad4.io/Networks/Reticulum-Go/pkg/debug"
|
|
"git.quad4.io/Networks/Reticulum-Go/pkg/destination"
|
|
"git.quad4.io/Networks/Reticulum-Go/pkg/identity"
|
|
"git.quad4.io/Networks/Reticulum-Go/pkg/interfaces"
|
|
"git.quad4.io/Networks/Reticulum-Go/pkg/packet"
|
|
"git.quad4.io/Networks/Reticulum-Go/pkg/transport"
|
|
)
|
|
|
|
var (
|
|
reticulumTransport *transport.Transport
|
|
reticulumDest *destination.Destination
|
|
reticulumIdentity *identity.Identity
|
|
userName string
|
|
peerMap = make(map[string]string)
|
|
stats = struct {
|
|
packetsSent int
|
|
packetsReceived int
|
|
bytesSent int
|
|
bytesReceived int
|
|
}{}
|
|
)
|
|
|
|
// RegisterJSFunctions registers the Reticulum WASM API to the JavaScript global scope.
|
|
func RegisterJSFunctions() {
|
|
js.Global().Set("reticulum", js.ValueOf(map[string]interface{}{
|
|
"init": js.FuncOf(InitReticulum),
|
|
"getIdentity": js.FuncOf(GetIdentity),
|
|
"getDestination": js.FuncOf(GetDestination),
|
|
"announce": js.FuncOf(SendAnnounce),
|
|
"connect": js.FuncOf(ConnectWebSocket),
|
|
"disconnect": js.FuncOf(DisconnectWebSocket),
|
|
"isConnected": js.FuncOf(IsConnected),
|
|
"sendMessage": js.FuncOf(SendMessage),
|
|
"getStats": js.FuncOf(GetStats),
|
|
}))
|
|
}
|
|
|
|
func GetStats(this js.Value, args []js.Value) interface{} {
|
|
return js.ValueOf(map[string]interface{}{
|
|
"packetsSent": stats.packetsSent,
|
|
"packetsReceived": stats.packetsReceived,
|
|
"bytesSent": stats.bytesSent,
|
|
"bytesReceived": stats.bytesReceived,
|
|
})
|
|
}
|
|
|
|
func InitReticulum(this js.Value, args []js.Value) interface{} {
|
|
if len(args) < 1 {
|
|
return js.ValueOf(map[string]interface{}{
|
|
"error": "WebSocket URL required",
|
|
})
|
|
}
|
|
|
|
if reticulumTransport != nil {
|
|
reticulumTransport.Close()
|
|
reticulumTransport = nil
|
|
}
|
|
|
|
wsURL := args[0].String()
|
|
if len(args) >= 2 {
|
|
userName = args[1].String()
|
|
}
|
|
|
|
var id *identity.Identity
|
|
var err error
|
|
|
|
// Check for existing identity in args
|
|
if len(args) >= 3 && !args[2].IsNull() && !args[2].IsUndefined() {
|
|
idHex := args[2].String()
|
|
idBytes, decodeErr := hex.DecodeString(idHex)
|
|
if decodeErr == nil && len(idBytes) == 64 {
|
|
id, err = identity.FromBytes(idBytes)
|
|
if err != nil {
|
|
debug.Log(debug.DEBUG_ERROR, "Failed to load provided identity, generating new one", "error", err)
|
|
id, err = identity.NewIdentity()
|
|
}
|
|
} else {
|
|
id, err = identity.NewIdentity()
|
|
}
|
|
} else {
|
|
id, err = identity.NewIdentity()
|
|
}
|
|
|
|
if err != nil {
|
|
return js.ValueOf(map[string]interface{}{
|
|
"error": fmt.Sprintf("Failed to handle identity: %v", err),
|
|
})
|
|
}
|
|
|
|
cfg := common.DefaultConfig()
|
|
t := transport.NewTransport(cfg)
|
|
|
|
dest, err := destination.New(
|
|
id,
|
|
destination.IN,
|
|
destination.SINGLE,
|
|
"wasm_core",
|
|
t,
|
|
"browser",
|
|
)
|
|
if err != nil {
|
|
return js.ValueOf(map[string]interface{}{
|
|
"error": fmt.Sprintf("Failed to create destination: %v", err),
|
|
})
|
|
}
|
|
|
|
dest.SetPacketCallback(func(data []byte, ni common.NetworkInterface) {
|
|
stats.packetsReceived++
|
|
stats.bytesReceived += len(data)
|
|
|
|
js.Global().Call("onChatMessage", js.ValueOf(map[string]interface{}{
|
|
"text": string(data),
|
|
"from": "",
|
|
}))
|
|
})
|
|
|
|
dest.SetProofStrategy(destination.PROVE_ALL)
|
|
|
|
t.RegisterAnnounceHandler(&announceHandler{})
|
|
|
|
wsInterface, err := interfaces.NewWebSocketInterface("wasm0", wsURL, true)
|
|
if err != nil {
|
|
return js.ValueOf(map[string]interface{}{
|
|
"error": fmt.Sprintf("Failed to create WebSocket interface: %v", err),
|
|
})
|
|
}
|
|
|
|
wsInterface.SetPacketCallback(func(data []byte, ni common.NetworkInterface) {
|
|
msg := fmt.Sprintf("Received packet: %d bytes (type: 0x%02x)", len(data), data[0])
|
|
js.Global().Call("log", msg, "success")
|
|
debug.Log(debug.DEBUG_INFO, "WASM received packet", "bytes", len(data), "type", fmt.Sprintf("0x%02x", data[0]))
|
|
t.HandlePacket(data, ni)
|
|
})
|
|
|
|
if err := t.RegisterInterface("wasm0", wsInterface); err != nil {
|
|
return js.ValueOf(map[string]interface{}{
|
|
"error": fmt.Sprintf("Failed to register interface: %v", err),
|
|
})
|
|
}
|
|
|
|
if err := t.Start(); err != nil {
|
|
return js.ValueOf(map[string]interface{}{
|
|
"error": fmt.Sprintf("Failed to start transport: %v", err),
|
|
})
|
|
}
|
|
|
|
reticulumTransport = t
|
|
reticulumDest = dest
|
|
reticulumIdentity = id
|
|
|
|
return js.ValueOf(map[string]interface{}{
|
|
"success": true,
|
|
"identity": id.GetHexHash(),
|
|
"privateKey": hex.EncodeToString(id.GetPrivateKey()),
|
|
"destination": fmt.Sprintf("%x", dest.GetHash()),
|
|
})
|
|
}
|
|
|
|
func GetIdentity(this js.Value, args []js.Value) interface{} {
|
|
if reticulumIdentity == nil {
|
|
return js.ValueOf(map[string]interface{}{
|
|
"error": "Reticulum not initialized",
|
|
})
|
|
}
|
|
|
|
return js.ValueOf(map[string]interface{}{
|
|
"hash": reticulumIdentity.GetHexHash(),
|
|
})
|
|
}
|
|
|
|
func GetDestination(this js.Value, args []js.Value) interface{} {
|
|
if reticulumDest == nil {
|
|
return js.ValueOf(map[string]interface{}{
|
|
"error": "Reticulum not initialized",
|
|
})
|
|
}
|
|
|
|
return js.ValueOf(map[string]interface{}{
|
|
"hash": fmt.Sprintf("%x", reticulumDest.GetHash()),
|
|
})
|
|
}
|
|
|
|
func SendAnnounce(this js.Value, args []js.Value) interface{} {
|
|
if reticulumDest == nil {
|
|
return js.ValueOf(map[string]interface{}{
|
|
"error": "Reticulum not initialized",
|
|
})
|
|
}
|
|
|
|
var appData []byte
|
|
if len(args) >= 1 && args[0].String() != "" {
|
|
appData = []byte(args[0].String())
|
|
userName = args[0].String()
|
|
} else if userName != "" {
|
|
appData = []byte(userName)
|
|
}
|
|
|
|
if err := reticulumDest.Announce(false, appData, nil); err != nil {
|
|
return js.ValueOf(map[string]interface{}{
|
|
"error": fmt.Sprintf("Failed to send announce: %v", err),
|
|
})
|
|
}
|
|
|
|
return js.ValueOf(map[string]interface{}{
|
|
"success": true,
|
|
})
|
|
}
|
|
|
|
func ConnectWebSocket(this js.Value, args []js.Value) interface{} {
|
|
if reticulumTransport == nil {
|
|
return js.ValueOf(map[string]interface{}{
|
|
"error": "Reticulum not initialized",
|
|
})
|
|
}
|
|
|
|
ifaces := reticulumTransport.GetInterfaces()
|
|
for name, iface := range ifaces {
|
|
if iface.IsOnline() {
|
|
return js.ValueOf(map[string]interface{}{
|
|
"success": true,
|
|
"interface": name,
|
|
})
|
|
}
|
|
if err := iface.Start(); err != nil {
|
|
return js.ValueOf(map[string]interface{}{
|
|
"error": fmt.Sprintf("Failed to connect: %v", err),
|
|
})
|
|
}
|
|
return js.ValueOf(map[string]interface{}{
|
|
"success": true,
|
|
"interface": name,
|
|
})
|
|
}
|
|
|
|
return js.ValueOf(map[string]interface{}{
|
|
"error": "WebSocket interface not found",
|
|
})
|
|
}
|
|
|
|
func DisconnectWebSocket(this js.Value, args []js.Value) interface{} {
|
|
if reticulumTransport == nil {
|
|
return js.ValueOf(map[string]interface{}{
|
|
"error": "Reticulum not initialized",
|
|
})
|
|
}
|
|
|
|
ifaces := reticulumTransport.GetInterfaces()
|
|
for _, iface := range ifaces {
|
|
if err := iface.Stop(); err != nil {
|
|
return js.ValueOf(map[string]interface{}{
|
|
"error": fmt.Sprintf("Failed to stop interface: %v", err),
|
|
})
|
|
}
|
|
return js.ValueOf(map[string]interface{}{
|
|
"success": true,
|
|
})
|
|
}
|
|
|
|
return js.ValueOf(map[string]interface{}{
|
|
"error": "WebSocket interface not found",
|
|
})
|
|
}
|
|
|
|
func IsConnected(this js.Value, args []js.Value) interface{} {
|
|
if reticulumTransport == nil {
|
|
return js.ValueOf(false)
|
|
}
|
|
|
|
ifaces := reticulumTransport.GetInterfaces()
|
|
for _, iface := range ifaces {
|
|
if iface.IsOnline() {
|
|
return js.ValueOf(true)
|
|
}
|
|
}
|
|
|
|
return js.ValueOf(false)
|
|
}
|
|
|
|
type announceHandler struct{}
|
|
|
|
func (h *announceHandler) AspectFilter() []string {
|
|
return nil
|
|
}
|
|
|
|
func (h *announceHandler) ReceivePathResponses() bool {
|
|
return false
|
|
}
|
|
|
|
func (h *announceHandler) ReceivedAnnounce(destHash []byte, ident interface{}, appData []byte) error {
|
|
hashStr := hex.EncodeToString(destHash)
|
|
peerMap[hashStr] = string(appData)
|
|
js.Global().Call("onPeerDiscovered", js.ValueOf(map[string]interface{}{
|
|
"hash": hashStr,
|
|
"appData": string(appData),
|
|
}))
|
|
return nil
|
|
}
|
|
|
|
func SendMessage(this js.Value, args []js.Value) interface{} {
|
|
if len(args) < 2 {
|
|
return js.ValueOf(map[string]interface{}{
|
|
"error": "Destination hash and message required",
|
|
})
|
|
}
|
|
|
|
destHashHex := args[0].String()
|
|
message := args[1].String()
|
|
|
|
destHash, err := hex.DecodeString(destHashHex)
|
|
if err != nil {
|
|
return js.ValueOf(map[string]interface{}{
|
|
"error": fmt.Sprintf("Invalid destination hash: %v", err),
|
|
})
|
|
}
|
|
|
|
remoteIdentity, err := identity.Recall(destHash)
|
|
if err != nil {
|
|
return js.ValueOf(map[string]interface{}{
|
|
"error": fmt.Sprintf("Identity not found. Wait for an announce from this peer!"),
|
|
})
|
|
}
|
|
|
|
targetDest, err := destination.FromHash(destHash, remoteIdentity, destination.SINGLE, reticulumTransport)
|
|
if err != nil {
|
|
return js.ValueOf(map[string]interface{}{
|
|
"error": fmt.Sprintf("Failed to create target destination: %v", err),
|
|
})
|
|
}
|
|
|
|
encrypted, err := targetDest.Encrypt([]byte(message))
|
|
if err != nil {
|
|
return js.ValueOf(map[string]interface{}{
|
|
"error": fmt.Sprintf("Encryption failed: %v", err),
|
|
})
|
|
}
|
|
|
|
pkt := packet.NewPacket(
|
|
packet.DestinationSingle,
|
|
encrypted,
|
|
packet.PacketTypeData,
|
|
packet.ContextNone,
|
|
packet.PropagationBroadcast,
|
|
packet.HeaderType1,
|
|
nil,
|
|
true,
|
|
packet.FlagUnset,
|
|
)
|
|
pkt.DestinationHash = destHash
|
|
|
|
if err := pkt.Pack(); err != nil {
|
|
return js.ValueOf(map[string]interface{}{
|
|
"error": fmt.Sprintf("Packet packing failed: %v", err),
|
|
})
|
|
}
|
|
|
|
if err := reticulumTransport.SendPacket(pkt); err != nil {
|
|
return js.ValueOf(map[string]interface{}{
|
|
"error": fmt.Sprintf("Packet sending failed: %v", err),
|
|
})
|
|
}
|
|
|
|
stats.packetsSent++
|
|
stats.bytesSent += len(message)
|
|
|
|
return js.ValueOf(map[string]interface{}{
|
|
"success": true,
|
|
})
|
|
}
|
|
|