mirror of https://github.com/elisescu/tty-share
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
160 lines
3.3 KiB
Go
160 lines
3.3 KiB
Go
package main
|
|
|
|
import (
|
|
"encoding/json"
|
|
"io"
|
|
"net/http"
|
|
"net/url"
|
|
"os"
|
|
"fmt"
|
|
|
|
ttyServer "github.com/elisescu/tty-share/server"
|
|
"github.com/gorilla/websocket"
|
|
"github.com/moby/term"
|
|
log "github.com/sirupsen/logrus"
|
|
"golang.org/x/crypto/ssh/terminal"
|
|
)
|
|
|
|
type ttyShareClient struct {
|
|
url string
|
|
connection *websocket.Conn
|
|
detachKeys string
|
|
}
|
|
|
|
func newTtyShareClient(url string, detachKeys string) *ttyShareClient {
|
|
return &ttyShareClient{
|
|
url: url,
|
|
connection: nil,
|
|
detachKeys: detachKeys,
|
|
}
|
|
}
|
|
|
|
func clearScreen() {
|
|
fmt.Fprintf(os.Stdout, "\033[H\033[2J")
|
|
}
|
|
|
|
type wsTextWriter struct {
|
|
conn *websocket.Conn
|
|
}
|
|
|
|
|
|
func (w *wsTextWriter) Write(data []byte) (n int, err error) {
|
|
err = w.conn.WriteMessage(websocket.TextMessage, data)
|
|
return len(data), err
|
|
}
|
|
|
|
type keyListener struct {
|
|
wrappedReader io.Reader
|
|
}
|
|
|
|
func (kl *keyListener) Read(data []byte) (n int, err error) {
|
|
n, err = kl.wrappedReader.Read(data)
|
|
if _, ok := err.(term.EscapeError); ok {
|
|
log.Debug("Escape code detected.")
|
|
}
|
|
return
|
|
}
|
|
|
|
func (c *ttyShareClient) Run() (err error) {
|
|
log.Debugf("Connecting as a client to %s ..", c.url)
|
|
|
|
resp, err := http.Get(c.url)
|
|
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
// Get the path of the websockts route from the header
|
|
wsPath := resp.Header.Get("TTYSHARE-WSPATH")
|
|
|
|
// Build the WS URL from the host part of the given http URL and the wsPath
|
|
httpURL, err := url.Parse(c.url)
|
|
if err != nil {
|
|
return
|
|
}
|
|
wsScheme := "ws"
|
|
if httpURL.Scheme == "https" {
|
|
wsScheme = "wss"
|
|
}
|
|
wsURL := wsScheme + "://" + httpURL.Host + wsPath
|
|
|
|
log.Debugf("Built the WS URL from the headers: %s", wsURL)
|
|
|
|
conn, _, err := websocket.DefaultDialer.Dial(wsURL, nil)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
detachBytes, err := term.ToBytes(c.detachKeys)
|
|
if err != nil {
|
|
log.Errorf("Invalid dettaching keys: %s", c.detachKeys)
|
|
return
|
|
}
|
|
|
|
state, err := terminal.MakeRaw(0)
|
|
defer terminal.Restore(0, state)
|
|
|
|
c.connection = conn
|
|
// Clear the screen before processing any incoming data
|
|
clearScreen()
|
|
|
|
readLoop := func() {
|
|
for {
|
|
var msg ttyServer.MsgAll
|
|
_, r, err := conn.NextReader()
|
|
if err != nil {
|
|
log.Debugf("Connection closed")
|
|
return
|
|
}
|
|
err = json.NewDecoder(r).Decode(&msg)
|
|
if err != nil {
|
|
log.Errorf("Cannot read JSON: %s", err.Error())
|
|
}
|
|
|
|
switch msg.Type {
|
|
case ttyServer.MsgIDWrite:
|
|
var msgWrite ttyServer.MsgTTYWrite
|
|
err := json.Unmarshal(msg.Data, &msgWrite)
|
|
|
|
if err != nil {
|
|
log.Errorf("Cannot read JSON: %s", err.Error())
|
|
}
|
|
|
|
os.Stdout.Write(msgWrite.Data)
|
|
case ttyServer.MsgIDWinSize:
|
|
log.Infof("Remote window changed its size")
|
|
// We ignore the window size changes - can't do much about that for
|
|
// now.
|
|
|
|
// TODO: Maybe just clear the screen, and display an error message
|
|
// if the remote window gets bigger than this terminal window - when
|
|
// it does, it usually messes up the output
|
|
}
|
|
}
|
|
}
|
|
|
|
writeLoop := func() {
|
|
ww := &wsTextWriter{
|
|
conn: conn,
|
|
}
|
|
kl := &keyListener{
|
|
wrappedReader: term.NewEscapeProxy(os.Stdin, detachBytes),
|
|
}
|
|
_, err := io.Copy(ttyServer.NewTTYProtocolWriter(ww), kl)
|
|
|
|
if err != nil {
|
|
log.Debugf("Connection closed")
|
|
c.Stop()
|
|
return
|
|
}
|
|
}
|
|
go writeLoop()
|
|
readLoop()
|
|
clearScreen()
|
|
return
|
|
}
|
|
|
|
func (c *ttyShareClient) Stop() {
|
|
c.connection.Close()
|
|
}
|