mirror of
https://github.com/tale/headplane.git
synced 2026-07-26 07:48:14 +00:00
b18147fa82
we also have added the necessary logic to auto prune ephemeral nodes because headscale doesn't seem to automatically remove them. this change made use of a database which is now stored in the persistent headplane directory.
107 lines
2.9 KiB
Go
107 lines
2.9 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) {
|
|
// We need to build a JSON representation of the NetworkMap
|
|
// For now we just pass the NodeKey since that's what we need.
|
|
jsObj := js.ValueOf(map[string]any{
|
|
"NodeKey": nm.NodeKey.String(),
|
|
})
|
|
|
|
obj.Get("NotifyNetMap").Invoke(jsObj)
|
|
},
|
|
|
|
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
|
|
}
|