cmd/tsconnect: remove obsolete wasm bridge APIs
Co-Authored-By: gpt-5.6-sol <noreply@openai.com>
This commit was merged in pull request #17.
This commit is contained in:
@@ -6,11 +6,10 @@
|
|||||||
//
|
//
|
||||||
// When run in the browser, a newIPN(config) function is added to the global JS
|
// When run in the browser, a newIPN(config) function is added to the global JS
|
||||||
// namespace. When called it returns an ipn object with the methods
|
// namespace. When called it returns an ipn object with the methods
|
||||||
// run(callbacks), login(), logout(), and ssh(...).
|
// run(callbacks), login(), and logout().
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"context"
|
"context"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
@@ -31,7 +30,6 @@ import (
|
|||||||
"syscall/js"
|
"syscall/js"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"golang.org/x/crypto/ssh"
|
|
||||||
"golang.org/x/net/dns/dnsmessage"
|
"golang.org/x/net/dns/dnsmessage"
|
||||||
"gvisor.dev/gvisor/pkg/tcpip"
|
"gvisor.dev/gvisor/pkg/tcpip"
|
||||||
"gvisor.dev/gvisor/pkg/tcpip/adapters/gonet"
|
"gvisor.dev/gvisor/pkg/tcpip/adapters/gonet"
|
||||||
@@ -233,25 +231,6 @@ func newIPN(jsConfig js.Value, shutdownCh chan struct{}) map[string]any {
|
|||||||
jsIPN.logout()
|
jsIPN.logout()
|
||||||
return nil
|
return nil
|
||||||
}),
|
}),
|
||||||
"ssh": js.FuncOf(func(this js.Value, args []js.Value) any {
|
|
||||||
if len(args) != 3 {
|
|
||||||
log.Printf("Usage: ssh(hostname, userName, termConfig)")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return jsIPN.ssh(
|
|
||||||
args[0].String(),
|
|
||||||
args[1].String(),
|
|
||||||
args[2])
|
|
||||||
}),
|
|
||||||
"fetch": js.FuncOf(func(this js.Value, args []js.Value) any {
|
|
||||||
if len(args) != 1 {
|
|
||||||
log.Printf("Usage: fetch(url)")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
url := args[0].String()
|
|
||||||
return jsIPN.fetch(url)
|
|
||||||
}),
|
|
||||||
"dial": js.FuncOf(func(this js.Value, args []js.Value) any {
|
"dial": js.FuncOf(func(this js.Value, args []js.Value) any {
|
||||||
if len(args) != 2 {
|
if len(args) != 2 {
|
||||||
log.Printf("Usage: dial(network, addr)")
|
log.Printf("Usage: dial(network, addr)")
|
||||||
@@ -291,13 +270,6 @@ func newIPN(jsConfig js.Value, shutdownCh chan struct{}) map[string]any {
|
|||||||
}
|
}
|
||||||
return jsIPN.setExitNode(args[0].String())
|
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())
|
|
||||||
}),
|
|
||||||
"listFileTargets": js.FuncOf(func(this js.Value, args []js.Value) any {
|
"listFileTargets": js.FuncOf(func(this js.Value, args []js.Value) any {
|
||||||
return jsIPN.listFileTargets()
|
return jsIPN.listFileTargets()
|
||||||
}),
|
}),
|
||||||
@@ -660,193 +632,6 @@ func (i *jsIPN) shutdown() js.Value {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *jsIPN) ssh(host, username string, termConfig js.Value) map[string]any {
|
|
||||||
jsSSHSession := &jsSSHSession{
|
|
||||||
jsIPN: i,
|
|
||||||
host: host,
|
|
||||||
username: username,
|
|
||||||
termConfig: termConfig,
|
|
||||||
}
|
|
||||||
|
|
||||||
go jsSSHSession.Run()
|
|
||||||
|
|
||||||
return map[string]any{
|
|
||||||
"close": js.FuncOf(func(this js.Value, args []js.Value) any {
|
|
||||||
return jsSSHSession.Close() != nil
|
|
||||||
}),
|
|
||||||
"resize": js.FuncOf(func(this js.Value, args []js.Value) any {
|
|
||||||
rows := args[0].Int()
|
|
||||||
cols := args[1].Int()
|
|
||||||
return jsSSHSession.Resize(rows, cols) != nil
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type jsSSHSession struct {
|
|
||||||
jsIPN *jsIPN
|
|
||||||
host string
|
|
||||||
username string
|
|
||||||
termConfig js.Value
|
|
||||||
session *ssh.Session
|
|
||||||
|
|
||||||
pendingResizeRows int
|
|
||||||
pendingResizeCols int
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *jsSSHSession) Run() {
|
|
||||||
writeFn := s.termConfig.Get("writeFn")
|
|
||||||
writeErrorFn := s.termConfig.Get("writeErrorFn")
|
|
||||||
setReadFn := s.termConfig.Get("setReadFn")
|
|
||||||
rows := s.termConfig.Get("rows").Int()
|
|
||||||
cols := s.termConfig.Get("cols").Int()
|
|
||||||
timeoutSeconds := 5.0
|
|
||||||
if jsTimeoutSeconds := s.termConfig.Get("timeoutSeconds"); jsTimeoutSeconds.Type() == js.TypeNumber {
|
|
||||||
timeoutSeconds = jsTimeoutSeconds.Float()
|
|
||||||
}
|
|
||||||
onConnectionProgress := s.termConfig.Get("onConnectionProgress")
|
|
||||||
onConnected := s.termConfig.Get("onConnected")
|
|
||||||
onDone := s.termConfig.Get("onDone")
|
|
||||||
defer onDone.Invoke()
|
|
||||||
|
|
||||||
writeError := func(label string, err error) {
|
|
||||||
writeErrorFn.Invoke(fmt.Sprintf("%s Error: %v\r\n", label, err))
|
|
||||||
}
|
|
||||||
reportProgress := func(message string) {
|
|
||||||
onConnectionProgress.Invoke(message)
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(timeoutSeconds*float64(time.Second)))
|
|
||||||
defer cancel()
|
|
||||||
reportProgress(fmt.Sprintf("Connecting to %s…", strings.Split(s.host, ".")[0]))
|
|
||||||
c, err := s.jsIPN.dialer.UserDial(ctx, "tcp", net.JoinHostPort(s.host, "22"))
|
|
||||||
if err != nil {
|
|
||||||
writeError("Dial", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer c.Close()
|
|
||||||
|
|
||||||
config := &ssh.ClientConfig{
|
|
||||||
HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error {
|
|
||||||
// Host keys are not used with Tailscale SSH, but we can use this
|
|
||||||
// callback to know that the connection has been established.
|
|
||||||
reportProgress("SSH connection established…")
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
User: s.username,
|
|
||||||
}
|
|
||||||
|
|
||||||
reportProgress("Starting SSH client…")
|
|
||||||
sshConn, _, _, err := ssh.NewClientConn(c, s.host, config)
|
|
||||||
if err != nil {
|
|
||||||
writeError("SSH Connection", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer sshConn.Close()
|
|
||||||
|
|
||||||
sshClient := ssh.NewClient(sshConn, nil, nil)
|
|
||||||
defer sshClient.Close()
|
|
||||||
|
|
||||||
session, err := sshClient.NewSession()
|
|
||||||
if err != nil {
|
|
||||||
writeError("SSH Session", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
s.session = session
|
|
||||||
defer session.Close()
|
|
||||||
|
|
||||||
stdin, err := session.StdinPipe()
|
|
||||||
if err != nil {
|
|
||||||
writeError("SSH Stdin", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
session.Stdout = termWriter{writeFn}
|
|
||||||
session.Stderr = termWriter{writeFn}
|
|
||||||
|
|
||||||
setReadFn.Invoke(js.FuncOf(func(this js.Value, args []js.Value) any {
|
|
||||||
input := args[0].String()
|
|
||||||
_, err := stdin.Write([]byte(input))
|
|
||||||
if err != nil {
|
|
||||||
writeError("Write Input", err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}))
|
|
||||||
|
|
||||||
// We might have gotten a resize notification since we started opening the
|
|
||||||
// session, pick up the latest size.
|
|
||||||
if s.pendingResizeRows != 0 {
|
|
||||||
rows = s.pendingResizeRows
|
|
||||||
}
|
|
||||||
if s.pendingResizeCols != 0 {
|
|
||||||
cols = s.pendingResizeCols
|
|
||||||
}
|
|
||||||
err = session.RequestPty("xterm", rows, cols, ssh.TerminalModes{})
|
|
||||||
if err != nil {
|
|
||||||
writeError("Pseudo Terminal", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
err = session.Shell()
|
|
||||||
if err != nil {
|
|
||||||
writeError("Shell", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
onConnected.Invoke()
|
|
||||||
err = session.Wait()
|
|
||||||
if err != nil {
|
|
||||||
writeError("Wait", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *jsSSHSession) Close() error {
|
|
||||||
if s.session == nil {
|
|
||||||
// We never had a chance to open the session, ignore the close request.
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return s.session.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *jsSSHSession) Resize(rows, cols int) error {
|
|
||||||
if s.session == nil {
|
|
||||||
s.pendingResizeRows = rows
|
|
||||||
s.pendingResizeCols = cols
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return s.session.WindowChange(rows, cols)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *jsIPN) fetch(url string) js.Value {
|
|
||||||
return makePromise(func() (any, error) {
|
|
||||||
c := &http.Client{
|
|
||||||
Transport: &http.Transport{
|
|
||||||
DialContext: i.dialer.UserDial,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
res, err := c.Get(url)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return map[string]any{
|
|
||||||
"status": res.StatusCode,
|
|
||||||
"statusText": res.Status,
|
|
||||||
"text": js.FuncOf(func(this js.Value, args []js.Value) any {
|
|
||||||
return makePromise(func() (any, error) {
|
|
||||||
defer res.Body.Close()
|
|
||||||
buf := new(bytes.Buffer)
|
|
||||||
if _, err := buf.ReadFrom(res.Body); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return buf.String(), nil
|
|
||||||
})
|
|
||||||
}),
|
|
||||||
// TODO: populate a more complete JS Response object
|
|
||||||
}, nil
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *jsIPN) setExitNode(stableNodeID string) js.Value {
|
func (i *jsIPN) setExitNode(stableNodeID string) js.Value {
|
||||||
return makePromise(func() (any, error) {
|
return makePromise(func() (any, error) {
|
||||||
mp := &ipn.MaskedPrefs{
|
mp := &ipn.MaskedPrefs{
|
||||||
@@ -858,13 +643,6 @@ func (i *jsIPN) setExitNode(stableNodeID string) js.Value {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
||||||
@@ -1336,10 +1114,10 @@ func (i *jsIPN) ping(ip string, pingType string, size int) js.Value {
|
|||||||
return nil, fmt.Errorf("ping: invalid IP %q: %w", ip, err)
|
return nil, fmt.Errorf("ping: invalid IP %q: %w", ip, err)
|
||||||
}
|
}
|
||||||
switch tailcfg.PingType(pingType) {
|
switch tailcfg.PingType(pingType) {
|
||||||
case tailcfg.PingDisco, tailcfg.PingTSMP, tailcfg.PingICMP, tailcfg.PingPeerAPI:
|
case tailcfg.PingTSMP, tailcfg.PingICMP, tailcfg.PingPeerAPI:
|
||||||
// valid
|
// valid
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("ping: unknown type %q, must be one of: disco, TSMP, ICMP, peerapi", pingType)
|
return nil, fmt.Errorf("ping: unknown type %q, must be one of: TSMP, ICMP, peerapi", pingType)
|
||||||
}
|
}
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
@@ -1664,16 +1442,6 @@ func resolveUDPAddr(s string) (*net.UDPAddr, error) {
|
|||||||
return &net.UDPAddr{IP: ip, Port: port}, nil
|
return &net.UDPAddr{IP: ip, Port: port}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type termWriter struct {
|
|
||||||
f js.Value
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w termWriter) Write(p []byte) (n int, err error) {
|
|
||||||
r := bytes.Replace(p, []byte("\n"), []byte("\n\r"), -1)
|
|
||||||
w.f.Invoke(string(r))
|
|
||||||
return len(p), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// jsIncomingFile is the JSON representation of an in-progress inbound file
|
// jsIncomingFile is the JSON representation of an in-progress inbound file
|
||||||
// transfer sent to the notifyIncomingFiles callback.
|
// transfer sent to the notifyIncomingFiles callback.
|
||||||
type jsIncomingFile struct {
|
type jsIncomingFile struct {
|
||||||
|
|||||||
Reference in New Issue
Block a user