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/logtail"
|
||||||
"tailscale.com/net/bakedroots"
|
"tailscale.com/net/bakedroots"
|
||||||
"tailscale.com/net/netns"
|
"tailscale.com/net/netns"
|
||||||
|
"tailscale.com/net/tsaddr"
|
||||||
"tailscale.com/net/tsdial"
|
"tailscale.com/net/tsdial"
|
||||||
"tailscale.com/safesocket"
|
"tailscale.com/safesocket"
|
||||||
"tailscale.com/tailcfg"
|
"tailscale.com/tailcfg"
|
||||||
@@ -252,6 +253,20 @@ func newIPN(jsConfig js.Value) map[string]any {
|
|||||||
}
|
}
|
||||||
return jsIPN.dialTLS(args[0].String(), opts)
|
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())
|
||||||
|
}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -338,6 +353,8 @@ func (i *jsIPN) run(jsCallbacks js.Value) {
|
|||||||
},
|
},
|
||||||
Online: p.Online().Clone(),
|
Online: p.Online().Clone(),
|
||||||
TailscaleSSHEnabled: p.Hostinfo().TailscaleSSHEnabled(),
|
TailscaleSSHEnabled: p.Hostinfo().TailscaleSSHEnabled(),
|
||||||
|
ExitNodeOption: tsaddr.ContainsExitRoutes(p.AllowedIPs()),
|
||||||
|
StableNodeID: string(p.StableID()),
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
LockedOut: nm.TKAEnabled && nm.SelfNode.KeySignature().Len() == 0,
|
LockedOut: nm.TKAEnabled && nm.SelfNode.KeySignature().Len() == 0,
|
||||||
@@ -349,6 +366,9 @@ func (i *jsIPN) run(jsCallbacks js.Value) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if n.Prefs.Valid() {
|
||||||
|
jsCallbacks.Call("notifyExitNode", string(n.Prefs.ExitNodeID()))
|
||||||
|
}
|
||||||
if n.BrowseToURL != nil {
|
if n.BrowseToURL != nil {
|
||||||
jsCallbacks.Call("notifyBrowseToURL", *n.BrowseToURL)
|
jsCallbacks.Call("notifyBrowseToURL", *n.BrowseToURL)
|
||||||
}
|
}
|
||||||
@@ -582,6 +602,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 {
|
func (i *jsIPN) dial(network, addr string) js.Value {
|
||||||
return makePromise(func() (any, error) {
|
return makePromise(func() (any, error) {
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||||
@@ -881,8 +919,10 @@ type jsNetMapSelfNode struct {
|
|||||||
|
|
||||||
type jsNetMapPeerNode struct {
|
type jsNetMapPeerNode struct {
|
||||||
jsNetMapNode
|
jsNetMapNode
|
||||||
Online *bool `json:"online,omitempty"`
|
Online *bool `json:"online,omitempty"`
|
||||||
TailscaleSSHEnabled bool `json:"tailscaleSSHEnabled"`
|
TailscaleSSHEnabled bool `json:"tailscaleSSHEnabled"`
|
||||||
|
ExitNodeOption bool `json:"exitNodeOption"`
|
||||||
|
StableNodeID string `json:"stableNodeID"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type jsStateStore struct {
|
type jsStateStore struct {
|
||||||
|
|||||||
Reference in New Issue
Block a user