fix: terminal input reaches the shell in the order it was typed
This commit is contained in:
1 parent
cb2673d941
commit
225b7ecfcc
3 files changed
+56
-7
No files matched your search
@@ -49,6 +49,9 @@ type panelInMsg struct {
|
||||
D string `json:"d,omitempty"`
|
||||
Cols uint16 `json:"cols,omitempty"`
|
||||
Rows uint16 `json:"rows,omitempty"`
|
||||
// Seq numbers the messages of a session from 1; 0 from a page that
|
||||
// predates it.
|
||||
Seq uint64 `json:"seq,omitempty"`
|
||||
}
|
||||
|
||||
// POST /api/panel/term/{sid}/in - send keystrokes/resize to the agent PTY.
|
||||
@@ -63,6 +66,10 @@ func (s *Server) handlePanelIn(w http.ResponseWriter, r *http.Request) {
|
||||
http.Error(w, "bad request", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
if m.Seq > 0 && !sess.TakeSeq(m.Seq) {
|
||||
w.WriteHeader(http.StatusNoContent) // a repeat of one already taken
|
||||
return
|
||||
}
|
||||
switch m.T {
|
||||
case "i":
|
||||
sess.PushIn(proto.InFrame{Type: proto.FrameStdin, Data: m.D})
|
||||
|
||||
@@ -45,6 +45,7 @@ type Session struct {
|
||||
mu sync.Mutex
|
||||
inBuf []proto.InFrame
|
||||
inSignal chan struct{} // size-1; pulsed when inBuf gains data
|
||||
inSeq uint64 // number of the last browser message taken
|
||||
|
||||
out chan proto.OutMessage // agent -> panel
|
||||
|
||||
@@ -250,6 +251,19 @@ func (s *Session) close() { s.closeOnce.Do(func() { close(s.closed) }) }
|
||||
// Done returns a channel closed when the session ends.
|
||||
func (s *Session) Done() <-chan struct{} { return s.closed }
|
||||
|
||||
// TakeSeq reports whether the browser message numbered seq is new, and takes
|
||||
// note of it. The browser sends them one at a time in rising order and sends
|
||||
// one again when it got no answer, so a number not above the last is a repeat.
|
||||
func (s *Session) TakeSeq(seq uint64) bool {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
if seq <= s.inSeq {
|
||||
return false
|
||||
}
|
||||
s.inSeq = seq
|
||||
return true
|
||||
}
|
||||
|
||||
// PushIn appends a downstream frame (panel -> agent) and wakes the agent poll.
|
||||
func (s *Session) PushIn(f proto.InFrame) {
|
||||
s.mu.Lock()
|
||||
|
||||
@@ -162,11 +162,42 @@
|
||||
term.write('\r\n\x1b[33m' + msg + '\x1b[0m\r\n');
|
||||
}
|
||||
|
||||
function send(data) {
|
||||
// Input goes out one request at a time, so on a slow link a keystroke can
|
||||
// never overtake the one before it; what is typed meanwhile is sent together
|
||||
// next. A request that fails goes again under the same number, and the
|
||||
// server drops one it already has.
|
||||
let inSeq = 0, inBusy = false;
|
||||
const inQueue = [];
|
||||
function queueIn(m) {
|
||||
if (closed || !sid) return;
|
||||
const bytes = new TextEncoder().encode(data);
|
||||
post(base + sid + '/in', { t: 'i', d: bytesToB64(bytes) });
|
||||
const last = inQueue[inQueue.length - 1];
|
||||
if (last && last.t === 'i' && m.t === 'i') last.text += m.text;
|
||||
else if (last && last.t === 'r' && m.t === 'r') inQueue[inQueue.length - 1] = m;
|
||||
else inQueue.push(m);
|
||||
pumpIn();
|
||||
}
|
||||
async function pumpIn() {
|
||||
if (inBusy || !inQueue.length) return;
|
||||
inBusy = true;
|
||||
while (inQueue.length && !closed) {
|
||||
const m = inQueue.shift();
|
||||
const body = m.t === 'i'
|
||||
? { t: 'i', d: bytesToB64(new TextEncoder().encode(m.text)), seq: ++inSeq }
|
||||
: { t: 'r', cols: m.cols, rows: m.rows, seq: ++inSeq };
|
||||
for (let wait = 300; !closed; wait = Math.min(wait * 2, 5000)) {
|
||||
let r = null;
|
||||
try {
|
||||
r = await fetch(base + sid + '/in', {
|
||||
method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body)
|
||||
});
|
||||
} catch (e) { /* no answer: send it again */ }
|
||||
if (r && r.status < 500) break; // taken, or refused for good
|
||||
await new Promise(res => setTimeout(res, wait));
|
||||
}
|
||||
}
|
||||
inBusy = false;
|
||||
}
|
||||
function send(data) { queueIn({ t: 'i', text: data }); }
|
||||
// What the keyboard types gets the CTRL / ALT of the extra keys, if armed.
|
||||
term.onData(function (data) { send(withMods(data)); });
|
||||
|
||||
@@ -240,10 +271,7 @@
|
||||
b.addEventListener('contextmenu', function (e) { e.preventDefault(); });
|
||||
});
|
||||
|
||||
function sendResize() {
|
||||
if (closed || !sid) return;
|
||||
post(base + sid + '/in', { t: 'r', cols: term.cols, rows: term.rows });
|
||||
}
|
||||
function sendResize() { queueIn({ t: 'r', cols: term.cols, rows: term.rows }); }
|
||||
function refit() {
|
||||
if (touch && window.visualViewport) {
|
||||
// Follow the visible area, so the extra keys sit right above the
|
||||
|
||||
Reference in new issue
Block a user