mirror of
https://github.com/profullstack/agentbbs.git
synced 2026-08-13 22:37:28 +00:00
join@: fix onboarding freeze — read raw PTY input instead of ReadString('\n')
ssh join@host requests a PTY by default, so the client's terminal goes into
raw mode: it sends keystrokes as they're typed (Enter is '\r', not '\n') and
does no local echo. The onboarding prompts (username, email, code) read with
bufio.ReadString('\n'), which never sees a '\n' and blocks forever — the user
sees a dead prompt at "Email:". The earlier "require a PTY" change made this
guaranteed rather than intermittent.
Add a readLine helper that reads byte-by-byte, treats '\r' or '\n' as the
line terminator, handles backspace, and echoes printable bytes back so the
user can see what they type. Route the three onboarding prompts through it.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
ac4b0873d9
commit
f44f0c9ae8
1 changed files with 39 additions and 3 deletions
|
|
@ -28,6 +28,7 @@ import (
|
|||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
|
|
@ -313,6 +314,41 @@ func (a *app) teaHandler(s ssh.Session) (tea.Model, []tea.ProgramOption) {
|
|||
return hub.New(u, ctx, a.enabledPlugins()), []tea.ProgramOption{tea.WithAltScreen()}
|
||||
}
|
||||
|
||||
// readLine reads one line of interactive input from an SSH session that is
|
||||
// running under a client-allocated PTY. That detail is the whole reason this
|
||||
// helper exists: when the client requests a PTY (which `ssh join@host` does by
|
||||
// default) it puts its OWN terminal into raw mode, so it sends raw keystrokes —
|
||||
// Enter arrives as '\r', not '\n' — and does NO local echo. bufio.ReadString
|
||||
// ('\n') therefore blocks forever (the '\n' never comes) and the user sees a
|
||||
// dead prompt. So we read byte-by-byte, accept either '\r' or '\n' as the line
|
||||
// terminator, handle backspace, and echo printable bytes back ourselves.
|
||||
func readLine(s ssh.Session, in *bufio.Reader) (string, error) {
|
||||
var b []byte
|
||||
for {
|
||||
c, err := in.ReadByte()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
switch c {
|
||||
case '\r', '\n':
|
||||
wish.Print(s, "\r\n")
|
||||
return string(b), nil
|
||||
case 0x03, 0x04: // Ctrl-C / Ctrl-D: treat as abort
|
||||
return "", io.EOF
|
||||
case 0x7f, '\b': // DEL / backspace: erase last char on screen too
|
||||
if len(b) > 0 {
|
||||
b = b[:len(b)-1]
|
||||
wish.Print(s, "\b \b")
|
||||
}
|
||||
default:
|
||||
if c >= 0x20 { // printable byte; ignore other control codes
|
||||
b = append(b, c)
|
||||
wish.Print(s, string(c))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// handleJoin runs onboarding interactively in one SSH session: register the
|
||||
// visitor's key, confirm their email with a code we email them, then offer the
|
||||
// $10 lifetime Premium membership (CoinPay). It then disconnects.
|
||||
|
|
@ -391,7 +427,7 @@ func (a *app) registerNewMember(s ssh.Session, in *bufio.Reader, fp string) (sto
|
|||
|
||||
for tries := 0; tries < 5; tries++ {
|
||||
wish.Print(s, "\n Username ["+def+"]: ")
|
||||
line, err := in.ReadString('\n')
|
||||
line, err := readLine(s, in)
|
||||
if err != nil {
|
||||
return store.User{}, err
|
||||
}
|
||||
|
|
@ -426,7 +462,7 @@ func (a *app) verifyEmailInteractive(s ssh.Session, in *bufio.Reader, u *store.U
|
|||
var email string
|
||||
for tries := 0; tries < 3; tries++ {
|
||||
wish.Print(s, "\n Email: ")
|
||||
line, err := in.ReadString('\n')
|
||||
line, err := readLine(s, in)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
|
@ -465,7 +501,7 @@ func (a *app) verifyEmailInteractive(s ssh.Session, in *bufio.Reader, u *store.U
|
|||
|
||||
for tries := 0; tries < 3; tries++ {
|
||||
wish.Print(s, " Enter the code: ")
|
||||
line, err := in.ReadString('\n')
|
||||
line, err := readLine(s, in)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue