mirror of
https://github.com/Eugeny/tabby.git
synced 2026-08-24 02:34:19 -05:00
@@ -0,0 +1,21 @@
|
||||
diff --git a/node_modules/zmodem.js/src/zsession.js b/node_modules/zmodem.js/src/zsession.js
|
||||
index 5f0b8f9..3f88b68 100644
|
||||
--- a/node_modules/zmodem.js/src/zsession.js
|
||||
+++ b/node_modules/zmodem.js/src/zsession.js
|
||||
@@ -572,7 +572,15 @@ Zmodem.Session.Receive = class ZmodemReceiveSession extends Zmodem.Session {
|
||||
else {
|
||||
parsed = this._parse_and_consume_header();
|
||||
}
|
||||
- } while (parsed && this._input_buffer.length);
|
||||
+ } while (parsed && this._input_buffer.length && !this._got_ZFIN);
|
||||
+
|
||||
+ //If consuming the ZFIN header left the trailing "OO" terminator in the
|
||||
+ //buffer (it arrived in the same chunk), handle it now. Otherwise the
|
||||
+ //loop above would feed "OO" to _trim_leading_garbage_until_header(),
|
||||
+ //which discards it, and the session would never end.
|
||||
+ if (this._got_ZFIN && this._input_buffer.length) {
|
||||
+ this._consume_first();
|
||||
+ }
|
||||
}
|
||||
|
||||
_consume_data(subpacket) {
|
||||
@@ -16,6 +16,24 @@ class ZModemMiddleware extends SessionMiddleware {
|
||||
private activeSession: any = null
|
||||
private cancelEvent: Observable<any>
|
||||
|
||||
// While non-null, terminal output is buffered here instead of being sent
|
||||
// straight to the terminal. Used to hold back a receive session's trailing
|
||||
// bytes (the shell prompt redrawn after sz exits) until after the final
|
||||
// "Received"/"Complete" messages have been printed, so the prompt is not
|
||||
// overwritten by showMessage()'s leading "\r".
|
||||
private trailingBuffer: Buffer[] | null = null
|
||||
|
||||
private flushTrailingBuffer () {
|
||||
const buffered = this.trailingBuffer
|
||||
this.trailingBuffer = null
|
||||
if (!buffered?.length) {
|
||||
return
|
||||
}
|
||||
for (const chunk of buffered) {
|
||||
this.outputToTerminal.next(chunk)
|
||||
}
|
||||
}
|
||||
|
||||
private log = inject(LogService)
|
||||
private translate = inject(TranslateService)
|
||||
private platform = inject(PlatformService)
|
||||
@@ -26,8 +44,20 @@ class ZModemMiddleware extends SessionMiddleware {
|
||||
|
||||
this.logger = this.log.create('zmodem')
|
||||
this.sentry = new ZModem.Sentry({
|
||||
// to_terminal is zmodem.js' single terminal-output channel. It
|
||||
// receives normal passthrough data (while no session is active),
|
||||
// protocol "garbage", and crucially the trailing bytes that follow
|
||||
// a session's "OO" terminator (e.g. the shell prompt redrawn after
|
||||
// sz/rz exits). These trailing bytes are emitted synchronously from
|
||||
// within the same consume() call that fires session_end, so any
|
||||
// guard based on isActive/activeSession would drop them on platforms
|
||||
// where "OO" and the prompt arrive in the same chunk (Linux).
|
||||
// While trailingBuffer is active they are queued so the final
|
||||
// status messages can be printed first; otherwise forward directly.
|
||||
to_terminal: data => {
|
||||
if (this.isActive && this.activeSession) {
|
||||
if (this.trailingBuffer) {
|
||||
this.trailingBuffer.push(Buffer.from(data))
|
||||
} else {
|
||||
this.outputToTerminal.next(Buffer.from(data))
|
||||
}
|
||||
},
|
||||
@@ -44,6 +74,15 @@ class ZModemMiddleware extends SessionMiddleware {
|
||||
defaultId: 0,
|
||||
cancelId: 1,
|
||||
})).response === 1) {
|
||||
// Accept the detection to get a session, then immediately
|
||||
// abort so that proper ZABORT frames are sent to the remote
|
||||
// side, causing the remote rz/sz process to terminate.
|
||||
try {
|
||||
const zsession = detection.confirm()
|
||||
zsession.abort()
|
||||
} catch { }
|
||||
// Clean up terminal output after rejection
|
||||
this.showMessage(colors.bgRed.black(' Rejected ') + ' ZMODEM session')
|
||||
return
|
||||
}
|
||||
|
||||
@@ -76,13 +115,16 @@ class ZModemMiddleware extends SessionMiddleware {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
// No active session: sentry.consume() routes everything straight
|
||||
// back through to_terminal, so we must not output here as well or
|
||||
// the data would be duplicated. Only on a consume() failure do we
|
||||
// forward the raw data as a fallback so nothing is lost.
|
||||
try {
|
||||
this.sentry.consume(data)
|
||||
} catch (e) {
|
||||
this.logger.error('zmodem detection error', e)
|
||||
this.outputToTerminal.next(data)
|
||||
}
|
||||
|
||||
this.outputToTerminal.next(data)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -105,17 +147,33 @@ class ZModemMiddleware extends SessionMiddleware {
|
||||
sizeRemaining -= transfer.getSize()
|
||||
}
|
||||
await zsession.close()
|
||||
|
||||
this.showMessage(colors.bgBlue.black(' ZMODEM ') + ' Complete')
|
||||
} else {
|
||||
const pendingReceives: Promise<void>[] = []
|
||||
zsession.on('offer', xfer => {
|
||||
this.receiveFile(xfer, zsession)
|
||||
pendingReceives.push(this.receiveFile(xfer, zsession))
|
||||
})
|
||||
|
||||
// session_end fires synchronously inside sentry.consume(),
|
||||
// immediately before the session's trailing bytes (the shell
|
||||
// prompt redrawn after sz exits) are flushed via to_terminal.
|
||||
// Start buffering here so those bytes are held back until after
|
||||
// all "Received" messages and the "Complete" message have been
|
||||
// printed; otherwise the prompt would be emitted first and then
|
||||
// overwritten by showMessage()'s leading "\r".
|
||||
zsession.on('session_end', () => {
|
||||
this.trailingBuffer = []
|
||||
})
|
||||
|
||||
zsession.start()
|
||||
|
||||
await new Promise(resolve => zsession.on('session_end', resolve))
|
||||
}
|
||||
await Promise.all(pendingReceives)
|
||||
|
||||
this.showMessage(colors.bgBlue.black(' ZMODEM ') + ' Complete')
|
||||
this.showMessage(colors.bgBlue.black(' ZMODEM ') + ' Complete')
|
||||
this.flushTrailingBuffer()
|
||||
}
|
||||
} catch (error) {
|
||||
this.logger.error('ZMODEM session error', error)
|
||||
this.showMessage(colors.bgRed.black(' ZMODEM ') + ` Session failed: ${error.message}`)
|
||||
@@ -124,6 +182,13 @@ class ZModemMiddleware extends SessionMiddleware {
|
||||
} catch { }
|
||||
} finally {
|
||||
this.activeSession = null
|
||||
|
||||
// Safety net: if an error left bytes buffered (e.g. session_end
|
||||
// started buffering but the flush above was skipped), release them
|
||||
// so terminal output is never permanently swallowed.
|
||||
if (this.trailingBuffer) {
|
||||
this.flushTrailingBuffer()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user