feat(tsconnect): expose exit node selection to JS
Add exit node support to the wasm JS bridge: - Include `exitNodeOption` and `stableNodeID` on each peer in the notifyNetMap payload so callers can identify which peers are exit nodes and reference them by stable ID. - Call `notifyExitNode(stableNodeID)` whenever prefs change, so callers can track which exit node (if any) is currently active. - Expose `setExitNode(stableNodeID)` — sets ExitNodeID via EditPrefs. - Expose `setExitNodeEnabled(enabled)` — toggles the last-used exit node on/off via SetUseExitNodeEnabled. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -45,6 +45,7 @@ import (
|
||||
"tailscale.com/logtail"
|
||||
"tailscale.com/net/bakedroots"
|
||||
"tailscale.com/net/netns"
|
||||
"tailscale.com/net/tsaddr"
|
||||
"tailscale.com/net/tsdial"
|
||||
"tailscale.com/safesocket"
|
||||
"tailscale.com/tailcfg"
|
||||
@@ -252,6 +253,20 @@ func newIPN(jsConfig js.Value) map[string]any {
|
||||
}
|
||||
return jsIPN.dialTLS(args[0].String(), opts)
|
||||
}),
|
||||
"setExitNode": js.FuncOf(func(this js.Value, args []js.Value) any {
|
||||
if len(args) != 1 {
|
||||
log.Printf("Usage: setExitNode(stableNodeID)")
|
||||
return nil
|
||||
}
|
||||
return jsIPN.setExitNode(args[0].String())
|
||||
}),
|
||||
"setExitNodeEnabled": js.FuncOf(func(this js.Value, args []js.Value) any {
|
||||
if len(args) != 1 {
|
||||
log.Printf("Usage: setExitNodeEnabled(enabled)")
|
||||
return nil
|
||||
}
|
||||
return jsIPN.setExitNodeEnabled(args[0].Bool())
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -333,6 +348,8 @@ func (i *jsIPN) run(jsCallbacks js.Value) {
|
||||
},
|
||||
Online: p.Online().Clone(),
|
||||
TailscaleSSHEnabled: p.Hostinfo().TailscaleSSHEnabled(),
|
||||
ExitNodeOption: tsaddr.ContainsExitRoutes(p.AllowedIPs()),
|
||||
StableNodeID: string(p.StableID()),
|
||||
}
|
||||
}),
|
||||
LockedOut: nm.TKAEnabled && nm.SelfNode.KeySignature().Len() == 0,
|
||||
@@ -343,6 +360,9 @@ func (i *jsIPN) run(jsCallbacks js.Value) {
|
||||
log.Printf("Could not generate JSON netmap: %v", err)
|
||||
}
|
||||
}
|
||||
if n.Prefs.Valid() {
|
||||
jsCallbacks.Call("notifyExitNode", string(n.Prefs.ExitNodeID()))
|
||||
}
|
||||
if n.BrowseToURL != nil {
|
||||
jsCallbacks.Call("notifyBrowseToURL", *n.BrowseToURL)
|
||||
}
|
||||
@@ -576,6 +596,24 @@ func (i *jsIPN) fetch(url string) js.Value {
|
||||
})
|
||||
}
|
||||
|
||||
func (i *jsIPN) setExitNode(stableNodeID string) js.Value {
|
||||
return makePromise(func() (any, error) {
|
||||
mp := &ipn.MaskedPrefs{
|
||||
ExitNodeIDSet: true,
|
||||
Prefs: ipn.Prefs{ExitNodeID: tailcfg.StableNodeID(stableNodeID)},
|
||||
}
|
||||
_, err := i.lb.EditPrefs(mp)
|
||||
return nil, err
|
||||
})
|
||||
}
|
||||
|
||||
func (i *jsIPN) setExitNodeEnabled(enabled bool) js.Value {
|
||||
return makePromise(func() (any, error) {
|
||||
_, err := i.lb.SetUseExitNodeEnabled(ipnauth.Self, enabled)
|
||||
return nil, err
|
||||
})
|
||||
}
|
||||
|
||||
func (i *jsIPN) dial(network, addr string) js.Value {
|
||||
return makePromise(func() (any, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
@@ -875,8 +913,10 @@ type jsNetMapSelfNode struct {
|
||||
|
||||
type jsNetMapPeerNode struct {
|
||||
jsNetMapNode
|
||||
Online *bool `json:"online,omitempty"`
|
||||
TailscaleSSHEnabled bool `json:"tailscaleSSHEnabled"`
|
||||
Online *bool `json:"online,omitempty"`
|
||||
TailscaleSSHEnabled bool `json:"tailscaleSSHEnabled"`
|
||||
ExitNodeOption bool `json:"exitNodeOption"`
|
||||
StableNodeID string `json:"stableNodeID"`
|
||||
}
|
||||
|
||||
type jsStateStore struct {
|
||||
|
||||
Reference in New Issue
Block a user