Files
headplane/internal/hp_ipn/jsmap.go
T
2025-09-27 12:10:15 -04:00

167 lines
4.4 KiB
Go

//go:build js && wasm
package hp_ipn
import (
"errors"
"log"
"syscall/js"
)
// Represents the options needed to initialize the TsWasmNet module.
type TsWasmNetOptions struct {
// Tailscale control URL, e.g., "https://controlplane.tailscale.com"
ControlURL string
// Pre-authentication key for the Tailnet
PreAuthKey string
// Optional hostname, autogenerated if not provided.
Hostname string
}
// Parses the provided JS object to validate and extract the TsWasmNetOptions.
func ParseTsWasmNetOptions(obj js.Value) (*TsWasmNetOptions, error) {
if obj.IsUndefined() || obj.IsNull() {
return nil, errors.New("TsWasmNetOptions cannot be undefined or null")
}
cUrl := safeString("ControlURL", obj)
preAuthKey := safeString("PreAuthKey", obj)
hostname := safeString("Hostname", obj)
if cUrl == "" || preAuthKey == "" || hostname == "" {
return nil, errors.New("missing required fields in TsWasmNetOptions")
}
return &TsWasmNetOptions{
ControlURL: cUrl,
PreAuthKey: preAuthKey,
Hostname: hostname,
}, nil
}
// Options passed from the JS side to pass data to xterm.js.
type SSHXtermConfig struct {
Timeout int // Timeout in seconds for the PTY connection.
Rows int // Number of rows in the PTY.
Cols int // Number of columns in the PTY.
OnStdout func(data string) // Fires when the PTY has output.
OnStderr func(error string) // Fires when the PTY has an error.
OnStdin js.Value // Passes a function to the JS side to provide input.
OnConnect func() // Fires when the PTY is opened.
OnDisconnect func() // Fires when the PTY is closed.
}
// Parses the provided JS object to validate and extract SSHXtermConfig.
func ParseSSHXtermConfig(obj js.Value) (*SSHXtermConfig, error) {
if obj.IsUndefined() || obj.IsNull() {
return nil, errors.New("SSHXtermConfig cannot be undefined or null")
}
timeout := safeInt("timeout", obj)
rows := safeInt("rows", obj)
cols := safeInt("cols", obj)
if rows <= 0 || cols <= 0 {
return nil, errors.New("`rows` and `cols` must be positive integers")
}
if timeout <= 0 {
timeout = 30 // Default timeout to 30 seconds if not specified
}
config := &SSHXtermConfig{
Timeout: timeout,
Rows: rows,
Cols: cols,
}
onStdout := obj.Get("onStdout")
if onStdout.IsUndefined() || onStdout.IsNull() || (onStdout.Type() != js.TypeFunction) {
return nil, errors.New("`onStdout` is required and must be a function")
}
config.OnStdout = func(data string) {
onStdout.Invoke(data)
}
onStderr := obj.Get("onStderr")
if onStderr.IsUndefined() || onStderr.IsNull() || (onStderr.Type() != js.TypeFunction) {
return nil, errors.New("`onStderr` is required and must be a function")
}
config.OnStderr = func(error string) {
onStderr.Invoke(error)
}
onStdin := obj.Get("onStdin")
if onStdin.IsUndefined() || onStdin.IsNull() || (onStdin.Type() != js.TypeFunction) {
return nil, errors.New("`onStdin` is required and must be a function")
}
config.OnStdin = onStdin
onConnect := obj.Get("onConnect")
if onConnect.IsUndefined() || onConnect.IsNull() || (onConnect.Type() != js.TypeFunction) {
return nil, errors.New("`onConnect` is required and must be a function")
}
config.OnConnect = func() {
onConnect.Invoke()
}
onDisconnect := obj.Get("onDisconnect")
if onDisconnect.IsUndefined() || onDisconnect.IsNull() || (onDisconnect.Type() != js.TypeFunction) {
return nil, errors.New("`onDisconnect` is required and must be a function")
}
config.OnDisconnect = func() {
onDisconnect.Invoke()
}
return config, nil
}
func (t *SSHXtermConfig) PassStdinHandler(fn func(string)) {
handler := js.FuncOf(func(this js.Value, args []js.Value) any {
if len(args) != 1 || args[0].Type() != js.TypeString {
return nil
}
fn(args[0].String())
return nil
})
log.Printf("Passing stdin handler to JS: %v", handler)
t.OnStdin.Invoke(handler)
}
// Retrieves a string value from a JS object safely.
func safeString(key string, obj js.Value) string {
if obj.IsUndefined() || obj.IsNull() {
return ""
}
val := obj.Get(key)
if val.IsUndefined() || val.IsNull() {
return ""
}
return val.String()
}
// Retrieves an integer value from a JS object safely.
func safeInt(key string, obj js.Value) int {
if obj.IsUndefined() || obj.IsNull() {
return 0
}
val := obj.Get(key)
if val.IsUndefined() || val.IsNull() {
return 0
}
return val.Int()
}