fix: batch of review findings — dead install button, history pollution, TUI completion interference, ssh split session kill, scrollback live apply, zmodem second transfer, sftp shell quoting, replay buffer leak, release guards
This commit is contained in:
1 parent
e6fc021903
commit
6c100542c5
11 files changed
+130
-19
No files matched your search
@@ -173,6 +173,9 @@ export function createPty(opts: PtyCreateOptions = {}): PtyCreateResult {
|
||||
pty.onExit(({ exitCode }) => {
|
||||
try {
|
||||
sessions.delete(id)
|
||||
// The session is gone: drop its replay buffer too (killPty was the only
|
||||
// path that did, so naturally-exiting shells leaked up to 64KB each).
|
||||
replayBuffers.delete(id)
|
||||
safeStopLog(id)
|
||||
broadcast(Ipc.PTY_EXIT, { id, exitCode })
|
||||
} catch {
|
||||
@@ -258,6 +261,7 @@ export async function openSession(opts: SessionOpenOptions): Promise<{ id: strin
|
||||
detachZmodem(handle.id)
|
||||
safeStopLog(handle.id)
|
||||
sessions.delete(handle.id)
|
||||
replayBuffers.delete(handle.id)
|
||||
deps.broadcast(Ipc.PTY_EXIT, { id: handle.id, exitCode: 0 })
|
||||
} catch {
|
||||
// never crash the event loop
|
||||
|
||||
@@ -21,7 +21,9 @@ const DEFAULT_SYSTEM: SystemSettings = {
|
||||
launchAtLogin: false,
|
||||
preventSleep: false,
|
||||
globalShowHide: '',
|
||||
closeAction: 'ask',
|
||||
// Must match DEFAULT_SETTINGS.system in @shared/settings — an "ask" here made
|
||||
// a fresh install prompt on close while the docs and UI promised tray.
|
||||
closeAction: 'tray',
|
||||
autoCheckUpdate: true
|
||||
}
|
||||
|
||||
@@ -43,9 +45,11 @@ function isHighlightRule(value: unknown): value is HighlightRule {
|
||||
}
|
||||
|
||||
function sanitizeRules(value: unknown): HighlightRule[] {
|
||||
// An explicit empty array is a valid choice ("no highlighting"); only
|
||||
// malformed data falls back to the built-in rules. Returning the defaults for
|
||||
// [] made deleting the last rule look like it silently failed.
|
||||
if (!Array.isArray(value)) return DEFAULT_HIGHLIGHT_RULES
|
||||
const rules = value.filter(isHighlightRule)
|
||||
return rules.length > 0 ? rules : DEFAULT_HIGHLIGHT_RULES
|
||||
return value.filter(isHighlightRule)
|
||||
}
|
||||
|
||||
function deepMerge(raw: unknown): { settings: AppSettings; errors: string[] } {
|
||||
|
||||
+12
-2
@@ -195,13 +195,23 @@ export function deleteRemote(sessionId: string, paths: string[]): Promise<void>
|
||||
* chmod/chown run over an exec channel: the JD test server's sftp subsystem
|
||||
* accepts SETSTAT but silently ignores it, while shell chmod/chown work.
|
||||
*/
|
||||
/**
|
||||
* POSIX single-quote escaping. `JSON.stringify` only escapes `"`, so a remote
|
||||
* file name containing `$`, a backtick or a quote would still be expanded by the
|
||||
* far-side shell — that is remote command execution triggered by a file name.
|
||||
*/
|
||||
function shQuote(value: string): string {
|
||||
return `'${value.replace(/'/g, `'\\''`)}'`
|
||||
}
|
||||
|
||||
export function chmodRemote(sessionId: string, path: string, mode: string): Promise<void> {
|
||||
if (!/^[0-7]{1,4}$/.test(mode)) throw new Error(`非法权限值: ${mode}`)
|
||||
return execQuiet(sessionId, `chmod ${mode} ${JSON.stringify(path)}`)
|
||||
return execQuiet(sessionId, `chmod ${mode} ${shQuote(path)}`)
|
||||
}
|
||||
|
||||
export function chownRemote(sessionId: string, path: string, uid: number, gid: number): Promise<void> {
|
||||
return execQuiet(sessionId, `chown ${uid}:${gid} ${JSON.stringify(path)}`)
|
||||
if (!Number.isInteger(uid) || !Number.isInteger(gid)) throw new Error('非法 uid/gid')
|
||||
return execQuiet(sessionId, `chown ${uid}:${gid} ${shQuote(path)}`)
|
||||
}
|
||||
|
||||
/** Run a command on the session's shell channel and wait for it to finish. */
|
||||
|
||||
@@ -309,6 +309,17 @@ export function attachZmodem(sessionId: string, deps: ZmodemDeps): void {
|
||||
engine.mode = role === 'receive' ? 'receive' : 'send'
|
||||
engine.detection = detection
|
||||
engine.active = true
|
||||
// A detect is the start of a fresh transfer on this session, so the
|
||||
// one-shot flags of the previous one must go with it. Leaving `confirmed`
|
||||
// set made the *second* `sz`/`rz` on a session a no-op: respondZmodem
|
||||
// bailed out, the engine stayed `active` (swallowing every keystroke and
|
||||
// all terminal data) until the stall timer finally fired.
|
||||
engine.confirmed = false
|
||||
engine.progressEmitted = false
|
||||
engine.doneEmitted = false
|
||||
engine.dir = null
|
||||
engine.session = null
|
||||
engine.receiveStream = null
|
||||
engine.transferId = `zm-${sessionId}`
|
||||
emitProgress(engine, { file: '', bytes: 0, totalBytes: 0 })
|
||||
try {
|
||||
|
||||
Reference in new issue
Block a user