- 新增 SESSION_STATE 查询通道(契约先行):PTY_EXIT 只广播一次不重放, 绑定晚于退出的窗格订阅后补查一次退出状态,快速退出的 shell 不再留下 永远空白、无死亡遮罩的窗格(退出码登记表上限 512 条 FIFO) - openSession 在 connectSsh 返回前登记 pendingOpens(owner + onClient 最早引用),killPtysByOwner/killAllPtys 可中止飞行中的握手,renderer 崩溃后不再留下无主孤儿 SSH 会话 - whenReady 的 createWindow 加已有窗口守卫,堵住与 second-instance showOrCreate 的双窗口竞态 - killAllPtys 补 replayBuffers.clear(),与 killSession 拆卸清单对齐 - promptUser 注释 30s 改为实际 120s;pty.ts 顺带注入 zmodem 背压钩子 (见下一提交)
150 lines
6.8 KiB
TypeScript
150 lines
6.8 KiB
TypeScript
import type {
|
|
AppInfo,
|
|
LayoutMeta,
|
|
PtyCreateOptions,
|
|
PtyCreateResult,
|
|
PtyDataEvent,
|
|
PtyExitEvent,
|
|
SessionOpenResult,
|
|
SessionSnapshot,
|
|
SessionStateResult
|
|
} from './ipc'
|
|
import type { AppSettings } from './settings'
|
|
import type { ReleaseNote, UpdateState } from './ipc'
|
|
import type { LockOperationResult, LockPasswordInput, LockSettingsState } from './ipc'
|
|
import type {
|
|
HostKeyAction,
|
|
HostKeyPromptEvent,
|
|
SessionOpenOptions,
|
|
SshConnection,
|
|
SshConnectionInput
|
|
} from './connections'
|
|
import type { SysinfoMeta, SysinfoSample } from './sysinfo'
|
|
import type { SftpEntry, TransferProgressEvent } from './sftp'
|
|
import type { CommandItem, SessionLogMeta } from './commands'
|
|
import type { ZmodemDoneEvent, ZmodemOfferEvent, ZmodemResponse } from './ipc'
|
|
|
|
/**
|
|
* The single API surface exposed on `window.api` by the preload script.
|
|
* Main process implements the handlers; renderer consumes this contract.
|
|
*/
|
|
export interface AppApi {
|
|
appInfo(): Promise<AppInfo>
|
|
|
|
// ---- sessions (pty data plane is shared by local and ssh sessions) ----
|
|
createPty(opts?: PtyCreateOptions): Promise<PtyCreateResult>
|
|
/** open a local or ssh session; resolves once the session is ready to stream */
|
|
openSession(opts: SessionOpenOptions): Promise<SessionOpenResult>
|
|
/** output a session produced before this renderer subscribed (capped ring buffer) */
|
|
getSessionReplay(id: string): Promise<string>
|
|
/**
|
|
* Compensating query for a missed PTY_EXIT: that broadcast fires exactly once
|
|
* and is never replayed, so a pane subscribing after its shell died would
|
|
* wait forever. `exited` (with `exitCode`) is the death state PTY_EXIT would
|
|
* have set.
|
|
*/
|
|
getSessionLiveState(id: string): Promise<SessionStateResult>
|
|
writePty(id: string, data: string): void
|
|
resizePty(id: string, cols: number, rows: number): void
|
|
killPty(id: string): void
|
|
/** subscribe to all session output; returns unsubscribe */
|
|
onPtyData(cb: (e: PtyDataEvent) => void): () => void
|
|
onPtyExit(cb: (e: PtyExitEvent) => void): () => void
|
|
|
|
// ---- sysinfo (M3: ssh sessions only; main polls the remote and broadcasts) ----
|
|
/** start polling for an ssh session; stop happens automatically on session close */
|
|
sysinfoStart(id: string): void
|
|
sysinfoStop(id: string): void
|
|
onSysinfoMeta(cb: (e: { id: string; meta: SysinfoMeta }) => void): () => void
|
|
onSysinfoSample(cb: (e: { id: string; sample: SysinfoSample }) => void): () => void
|
|
|
|
// ---- sftp (M4: bound to an established ssh session) ----
|
|
listRemote(sessionId: string, dir: string): Promise<SftpEntry[]>
|
|
mkdirRemote(sessionId: string, dir: string, name: string): Promise<void>
|
|
renameRemote(sessionId: string, from: string, to: string): Promise<void>
|
|
deleteRemote(sessionId: string, paths: string[]): Promise<void>
|
|
/** mode = octal string, e.g. "755" or "0644" */
|
|
chmodRemote(sessionId: string, path: string, mode: string): Promise<void>
|
|
chownRemote(sessionId: string, path: string, uid: number, gid: number): Promise<void>
|
|
/** upload local files into remote dir; returns transferId */
|
|
uploadRemote(sessionId: string, localPaths: string[], remoteDir: string): Promise<string>
|
|
/** download remote paths into local dir; returns transferId */
|
|
downloadRemote(sessionId: string, remotePaths: string[], localDir: string): Promise<string>
|
|
cancelTransfer(transferId: string): void
|
|
onTransferProgress(cb: (e: TransferProgressEvent) => void): () => void
|
|
/** native open dialog; returns picked file paths (empty when cancelled) */
|
|
pickFiles(): Promise<string[]>
|
|
pickDirectory(): Promise<string>
|
|
|
|
// ---- command history / library + session logs (M5) ----
|
|
/** record one executed command (deduped, newest first, capped) */
|
|
recordCommand(cmd: string): Promise<void>
|
|
listHistory(): Promise<CommandItem[]>
|
|
clearHistory(): Promise<void>
|
|
listLibrary(): Promise<CommandItem[]>
|
|
saveLibraryItem(item: CommandItem): Promise<CommandItem>
|
|
deleteLibraryItem(id: string): Promise<void>
|
|
/** session output logging */
|
|
logStart(sessionId: string): Promise<SessionLogMeta>
|
|
logStop(sessionId: string): Promise<void>
|
|
listSessionLogs(): Promise<SessionLogMeta[]>
|
|
openLogsDir(): void
|
|
/** open a local directory in the OS file manager; false if not a directory */
|
|
openDirectory(dir: string): Promise<boolean>
|
|
|
|
// ---- ssh connections ----
|
|
listConnections(): Promise<SshConnection[]>
|
|
saveConnection(input: SshConnectionInput): Promise<SshConnection>
|
|
deleteConnection(id: string): Promise<void>
|
|
onHostKeyPrompt(cb: (e: HostKeyPromptEvent) => void): () => void
|
|
respondHostKey(promptId: string, action: HostKeyAction): void
|
|
|
|
// ---- zmodem (M6: main-process engine over ssh sessions; progress reuses transfer events) ----
|
|
onZmodemOffer(cb: (e: ZmodemOfferEvent) => void): () => void
|
|
zmodemRespond(resp: ZmodemResponse): void
|
|
onZmodemDone(cb: (e: ZmodemDoneEvent) => void): () => void
|
|
|
|
// ---- settings ----
|
|
getSettings(): Promise<AppSettings>
|
|
saveSettings(next: Partial<AppSettings>): Promise<AppSettings>
|
|
onSettingsChanged(cb: (s: AppSettings) => void): () => void
|
|
|
|
// ---- lock screen (main-window overlay; main owns the lock state) ----
|
|
getLockState(): Promise<LockSettingsState>
|
|
/** set or replace the password; verifies currentPassword when one exists */
|
|
setLockPassword(input: LockPasswordInput): Promise<LockOperationResult>
|
|
/** remove the password; verifies currentPassword when one exists */
|
|
clearLockPassword(input: { currentPassword?: string }): Promise<LockOperationResult>
|
|
/** answer the lock screen; resets the failure cooldown on success */
|
|
unlockLock(input: { password?: string }): Promise<LockOperationResult>
|
|
lockNow(): Promise<LockSettingsState>
|
|
onLockStateChanged(cb: (state: LockSettingsState) => void): () => void
|
|
|
|
// ---- fonts ----
|
|
listFonts(): Promise<string[]>
|
|
|
|
// ---- layout templates ----
|
|
listLayouts(): Promise<LayoutMeta[]>
|
|
getLayout(id: string): Promise<string | null>
|
|
/** json = serialized dockview layout (DockviewApi.toJSON()) */
|
|
saveLayout(meta: LayoutMeta, json: string): Promise<void>
|
|
deleteLayout(id: string): Promise<void>
|
|
|
|
// ---- updater (domestic feed first, GitHub fallback) ----
|
|
updateCheck(): Promise<UpdateState>
|
|
/** read the updater's current state without triggering a network check */
|
|
getUpdateState(): Promise<UpdateState>
|
|
updateDownload(): Promise<void>
|
|
updateInstall(): Promise<void>
|
|
updateChangelog(): Promise<ReleaseNote[]>
|
|
onUpdateState(cb: (s: UpdateState) => void): () => void
|
|
|
|
// ---- session snapshot (restore last layout + per-pane cwd) ----
|
|
getSessionState(): Promise<SessionSnapshot | null>
|
|
saveSessionState(snapshot: SessionSnapshot): Promise<SessionSnapshot | null>
|
|
/** resolve a cd-style argument against the current cwd (platform-aware) */
|
|
resolveCwd(current: string | undefined, arg: string): Promise<string | null>
|
|
/** normalize a shell-reported cwd payload (OSC 7 / OSC 9;9) */
|
|
reportCwd(payload: string): Promise<string | null>
|
|
}
|