Files
headplane/internal/hp_ipn/jsfunc.go
T
2025-06-16 11:45:23 -04:00

101 lines
2.7 KiB
Go

//go:build js && wasm
package hp_ipn
import (
"errors"
"fmt"
"syscall/js"
"tailscale.com/ipn"
"tailscale.com/types/netmap"
)
// Maps ipn.State values to their string representations for the frontend.
var BackendState = map[ipn.State]string{
ipn.NoState: "NoState",
ipn.Stopped: "Stopped",
ipn.Starting: "Starting",
ipn.Running: "Running",
ipn.InUseOtherUser: "InUseOtherUser",
ipn.NeedsMachineAuth: "NeedsMachineAuth",
ipn.NeedsLogin: "NeedsLogin",
}
// Represents the callbacks that the TsWasmNet module can invoke to register
// data retrieval and notifications on the frontend.
type TsWasmNetCallbacks struct {
// Changes in the backend state.
NotifyState func(ipn.State)
// Updates to the backend's network map.
NotifyNetMap func(*netmap.NetworkMap)
// If interactive login is required, this passes a login URL.
NotifyBrowseToURL func(string)
// If the process panics, this function is called in go.recover.
NotifyPanicRecover func(string)
}
// Parses a JavaScript object containing the necessary callbacks for the
// TsWasmNet module to properly interact with the frontend.
func ParseTsWasmNetCallbacks(obj js.Value) (*TsWasmNetCallbacks, error) {
if obj.IsUndefined() || obj.IsNull() {
return nil, errors.New("callbacks object is undefined or null")
}
state, err := validateCallback("NotifyState", obj)
if err != nil {
return nil, fmt.Errorf("invalid callback NotifyState: %w", err)
}
// TODO: This is complicated, as the NetworkMap is a complex type.
_, err = validateCallback("NotifyNetMap", obj)
if err != nil {
return nil, fmt.Errorf("invalid callback NotifyNetMap: %w", err)
}
browseURL, err := validateCallback("NotifyBrowseToURL", obj)
if err != nil {
return nil, fmt.Errorf("invalid callback NotifyBrowseToURL: %w", err)
}
panicRecover, err := validateCallback("NotifyPanicRecover", obj)
if err != nil {
return nil, fmt.Errorf("invalid callback NotifyPanicRecover: %w", err)
}
return &TsWasmNetCallbacks{
NotifyState: func(ipnState ipn.State) {
state.Invoke(BackendState[ipnState])
},
NotifyNetMap: func(nm *netmap.NetworkMap) {
// TODO: This is complicated
},
NotifyBrowseToURL: func(url string) {
browseURL.Invoke(url)
},
NotifyPanicRecover: func(msg string) {
panicRecover.Invoke(msg)
},
}, nil
}
// Validates the specified key is a JS function and returns it.
func validateCallback(key string, obj js.Value) (*js.Value, error) {
val := obj.Get(key)
if val.IsUndefined() || val.IsNull() {
return nil, errors.New("callback is undefined or null")
}
if val.Type() != js.TypeFunction {
return nil, errors.New("callback is not a function")
}
return &val, nil
}