This commit is contained in:
Baobhan Sith
2025-05-10 01:47:16 +08:00
parent 61e7b64333
commit 037af032f5
7 changed files with 230 additions and 37 deletions
+69 -5
View File
@@ -16,8 +16,10 @@ import {
SshSuspendEntryRemovedResponse, SshSuspendEntryRemovedResponse,
SshSuspendNameEditedResponse, SshSuspendNameEditedResponse,
SshSuspendAutoTerminatedNotification, SshSuspendAutoTerminatedNotification,
SshMarkForSuspendRequest, // +++ 新增导入 SshMarkForSuspendRequest,
SshMarkedForSuspendAck, // +++ 新增导入 SshMarkedForSuspendAck,
SshUnmarkForSuspendRequest, // +++ 新增导入 +++
SshUnmarkedForSuspendAck, // +++ 新增导入 +++
ClientState ClientState
} from './types'; } from './types';
import { SshSuspendService } from '../services/ssh-suspend.service'; import { SshSuspendService } from '../services/ssh-suspend.service';
@@ -317,7 +319,8 @@ export function initializeConnectionHandler(wss: WebSocketServer, sshSuspendServ
case 'SSH_MARK_FOR_SUSPEND': { case 'SSH_MARK_FOR_SUSPEND': {
const markPayload = payload as SshMarkForSuspendRequest['payload']; const markPayload = payload as SshMarkForSuspendRequest['payload'];
const sessionToMarkId = markPayload.sessionId; const sessionToMarkId = markPayload.sessionId;
console.log(`[WebSocket Handler] Received SSH_MARK_FOR_SUSPEND. UserID: ${ws.userId}, TargetSessionID: ${sessionToMarkId}`); const initialBuffer = markPayload.initialBuffer; // +++ 获取 initialBuffer +++
console.log(`[WebSocket Handler] Received SSH_MARK_FOR_SUSPEND. UserID: ${ws.userId}, TargetSessionID: ${sessionToMarkId}, InitialBuffer provided: ${!!initialBuffer}`);
if (!ws.userId) { if (!ws.userId) {
console.error(`[SSH_MARK_FOR_SUSPEND] 用户 ID 未定义。`); console.error(`[SSH_MARK_FOR_SUSPEND] 用户 ID 未定义。`);
@@ -346,8 +349,16 @@ export function initializeConnectionHandler(wss: WebSocketServer, sshSuspendServ
// 确保日志目录存在 (服务内部通常会做,但这里也可以调用一次) // 确保日志目录存在 (服务内部通常会做,但这里也可以调用一次)
await temporaryLogStorageService.ensureLogDirectoryExists(); await temporaryLogStorageService.ensureLogDirectoryExists();
// 可以在这里预先写入一个标记,表明日志开始记录
await temporaryLogStorageService.writeToLog(logPathSuffix, `--- Log recording started for session ${sessionToMarkId} at ${new Date().toISOString()} ---\n`); // +++ 如果有 initialBuffer,先写入它 +++
if (initialBuffer) {
// 确保 initialBuffer 后有一个换行符,以便后续日志在新行开始
const formattedInitialBuffer = initialBuffer.endsWith('\n') ? initialBuffer : `${initialBuffer}\n`;
await temporaryLogStorageService.writeToLog(logPathSuffix, formattedInitialBuffer);
console.log(`[SSH_MARK_FOR_SUSPEND] 已将初始缓冲区写入日志 (会话: ${sessionToMarkId})。`);
}
// --- 移除自动添加的日志标记行 ---
// await temporaryLogStorageService.writeToLog(logPathSuffix, `--- Log recording continued for session ${sessionToMarkId} at ${new Date().toISOString()} ---\n`);
console.log(`[SSH_MARK_FOR_SUSPEND] 会话 ${sessionToMarkId} 已成功标记待挂起。日志将记录到与 ${logPathSuffix} 关联的文件。`); console.log(`[SSH_MARK_FOR_SUSPEND] 会话 ${sessionToMarkId} 已成功标记待挂起。日志将记录到与 ${logPathSuffix} 关联的文件。`);
const response: SshMarkedForSuspendAck = { const response: SshMarkedForSuspendAck = {
@@ -365,6 +376,59 @@ export function initializeConnectionHandler(wss: WebSocketServer, sshSuspendServ
} }
break; break;
} }
case 'SSH_UNMARK_FOR_SUSPEND': {
const unmarkPayload = payload as SshUnmarkForSuspendRequest['payload'];
const sessionToUnmarkId = unmarkPayload.sessionId;
console.log(`[WebSocket Handler] Received SSH_UNMARK_FOR_SUSPEND. UserID: ${ws.userId}, TargetSessionID: ${sessionToUnmarkId}`);
const ackPayloadBase = { sessionId: sessionToUnmarkId };
if (!ws.userId) {
console.error(`[SSH_UNMARK_FOR_SUSPEND] 用户 ID 未定义。`);
if (ws.readyState === WebSocket.OPEN) ws.send(JSON.stringify({ type: 'SSH_UNMARKED_FOR_SUSPEND_ACK', payload: { ...ackPayloadBase, success: false, error: '用户认证失败' } as SshUnmarkedForSuspendAck['payload'] }));
break;
}
const activeSessionState = clientStates.get(sessionToUnmarkId);
if (!activeSessionState) {
console.warn(`[SSH_UNMARK_FOR_SUSPEND] 未找到会话: ${sessionToUnmarkId}`);
if (ws.readyState === WebSocket.OPEN) ws.send(JSON.stringify({ type: 'SSH_UNMARKED_FOR_SUSPEND_ACK', payload: { ...ackPayloadBase, success: false, error: '未找到要取消标记的会话' } as SshUnmarkedForSuspendAck['payload'] }));
break;
}
if (!activeSessionState.isMarkedForSuspend) {
console.warn(`[SSH_UNMARK_FOR_SUSPEND] 会话 ${sessionToUnmarkId} 并未被标记为待挂起。`);
// 即使未标记,也回复成功,因为最终状态是“未标记”
if (ws.readyState === WebSocket.OPEN) ws.send(JSON.stringify({ type: 'SSH_UNMARKED_FOR_SUSPEND_ACK', payload: { ...ackPayloadBase, success: true, error: '会话本就未标记' } as SshUnmarkedForSuspendAck['payload'] }));
break;
}
try {
activeSessionState.isMarkedForSuspend = false;
const logPathToDelete = activeSessionState.suspendLogPath;
activeSessionState.suspendLogPath = undefined; // 清除日志路径
if (logPathToDelete) {
await temporaryLogStorageService.deleteLog(logPathToDelete);
console.log(`[SSH_UNMARK_FOR_SUSPEND] 已删除会话 ${sessionToUnmarkId} 的临时挂起日志: ${logPathToDelete}`);
}
console.log(`[SSH_UNMARK_FOR_SUSPEND] 会话 ${sessionToUnmarkId} 已成功取消标记。`);
const response: SshUnmarkedForSuspendAck = {
type: 'SSH_UNMARKED_FOR_SUSPEND_ACK',
payload: { ...ackPayloadBase, success: true }
};
if (ws.readyState === WebSocket.OPEN) ws.send(JSON.stringify(response));
} catch (error: any) {
console.error(`[SSH_UNMARK_FOR_SUSPEND] 取消标记会话 ${sessionToUnmarkId} 失败:`, error);
// 尝试回滚状态(尽管可能意义不大,因为错误可能在删除日志时发生)
if (activeSessionState) {
activeSessionState.isMarkedForSuspend = true; // 保持标记状态
// activeSessionState.suspendLogPath = logPathToDelete; // 如果需要,可以恢复路径,但删除失败更可能是问题
}
if (ws.readyState === WebSocket.OPEN) ws.send(JSON.stringify({ type: 'SSH_UNMARKED_FOR_SUSPEND_ACK', payload: { ...ackPayloadBase, success: false, error: error.message || '取消标记会话失败' } as SshUnmarkedForSuspendAck['payload'] }));
}
break;
}
default: default:
console.warn(`WebSocket:收到来自 ${ws.username} (会话: ${sessionId}) 的未知消息类型: ${type}`); console.warn(`WebSocket:收到来自 ${ws.username} (会话: ${sessionId}) 的未知消息类型: ${type}`);
if (ws.readyState === WebSocket.OPEN) ws.send(JSON.stringify({ type: 'error', payload: `不支持的消息类型: ${type}` })); if (ws.readyState === WebSocket.OPEN) ws.send(JSON.stringify({ type: 'error', payload: `不支持的消息类型: ${type}` }));
+23 -4
View File
@@ -23,8 +23,8 @@ export interface ClientState { // 导出以便 Service 可以导入
isShellReady?: boolean; // 新增:标记 Shell 是否已准备好处理输入和调整大小 isShellReady?: boolean; // 新增:标记 Shell 是否已准备好处理输入和调整大小
isSuspendedByService?: boolean; // 新增:标记此会话是否已被 SshSuspendService 接管 isSuspendedByService?: boolean; // 新增:标记此会话是否已被 SshSuspendService 接管
isMarkedForSuspend?: boolean; // 新增:标记此会话是否已被用户请求挂起(等待断开连接) isMarkedForSuspend?: boolean; // 新增:标记此会话是否已被用户请求挂起(等待断开连接)
suspendLogPath?: string; // 新增:如果标记挂起,则存储日志路径 suspendLogPath?: string; // 新增:如果标记挂起,则存储日志路径 (基于原始 sessionId)
suspendLogWritableStream?: NodeJS.WritableStream; // 新增:用于写入挂起日志的流 // suspendLogWritableStream?: NodeJS.WritableStream; // 移除,将直接使用 temporaryLogStorageService.writeToLog
} }
export interface PortInfo { export interface PortInfo {
@@ -110,6 +110,14 @@ export interface SshMarkForSuspendRequest {
type: "SSH_MARK_FOR_SUSPEND"; type: "SSH_MARK_FOR_SUSPEND";
payload: { payload: {
sessionId: string; // The ID of the active SSH session to be marked sessionId: string; // The ID of the active SSH session to be marked
initialBuffer?: string; // +++ 新增:可选的初始屏幕缓冲区内容 +++
};
}
export interface SshUnmarkForSuspendRequest {
type: "SSH_UNMARK_FOR_SUSPEND";
payload: {
sessionId: string; // The ID of the active SSH session to be unmarked
}; };
} }
@@ -197,6 +205,15 @@ export interface SshMarkedForSuspendAck {
}; };
} }
export interface SshUnmarkedForSuspendAck { // +++ 新增 S2C 类型 +++
type: "SSH_UNMARKED_FOR_SUSPEND_ACK";
payload: {
sessionId: string; // The ID of the session that was unmarked
success: boolean;
error?: string;
};
}
export interface SshSuspendAutoTerminatedNotification { export interface SshSuspendAutoTerminatedNotification {
type: "SSH_SUSPEND_AUTO_TERMINATED"; type: "SSH_SUSPEND_AUTO_TERMINATED";
payload: { payload: {
@@ -213,7 +230,8 @@ export type SshSuspendClientToServerMessages =
| SshSuspendTerminateRequest | SshSuspendTerminateRequest
| SshSuspendRemoveEntryRequest | SshSuspendRemoveEntryRequest
| SshSuspendEditNameRequest | SshSuspendEditNameRequest
| SshMarkForSuspendRequest; // Added new request type | SshMarkForSuspendRequest
| SshUnmarkForSuspendRequest; // +++ 新增到联合类型 +++
// Union type for all server-to-client messages for SSH Suspend // Union type for all server-to-client messages for SSH Suspend
export type SshSuspendServerToClientMessages = export type SshSuspendServerToClientMessages =
@@ -225,7 +243,8 @@ export type SshSuspendServerToClientMessages =
| SshSuspendEntryRemovedResponse | SshSuspendEntryRemovedResponse
| SshSuspendNameEditedResponse | SshSuspendNameEditedResponse
| SshSuspendAutoTerminatedNotification | SshSuspendAutoTerminatedNotification
| SshMarkedForSuspendAck; // Added new response type | SshMarkedForSuspendAck
| SshUnmarkedForSuspendAck; // +++ 新增到联合类型 +++
// It might be useful to have a general type for incoming messages if not already present // It might be useful to have a general type for incoming messages if not already present
// For example, if you have a main message handler: // For example, if you have a main message handler:
@@ -171,13 +171,20 @@ const handleContextMenuAction = (payload: { action: string; targetId: string | n
// 注意:关闭左侧通常不包括当前标签本身 // 注意:关闭左侧通常不包括当前标签本身
emitWorkspaceEvent('session:closeToLeft', { targetSessionId: targetId }); emitWorkspaceEvent('session:closeToLeft', { targetSessionId: targetId });
break; break;
case 'suspend-session': // 新增处理 suspend-session 动作 case 'mark-for-suspend': // +++ 修改 action 名称 +++
// 确保 targetId 是字符串类型的 sessionId
if (typeof targetId === 'string') { if (typeof targetId === 'string') {
console.log(`[TabBar] Context menu action 'suspend-session' requested for session ID: ${targetId}`); console.log(`[TabBar] Context menu action 'mark-for-suspend' requested for session ID: ${targetId}`);
sessionStore.requestStartSshSuspend(targetId); sessionStore.requestStartSshSuspend(targetId); // 这个 action 现在是标记
} else { } else {
console.warn(`[TabBar] 'suspend-session' action called with invalid targetId:`, targetId); console.warn(`[TabBar] 'mark-for-suspend' action called with invalid targetId:`, targetId);
}
break;
case 'unmark-for-suspend': // +++ 新增 case +++
if (typeof targetId === 'string') {
console.log(`[TabBar] Context menu action 'unmark-for-suspend' requested for session ID: ${targetId}`);
sessionStore.requestUnmarkSshSuspend(targetId);
} else {
console.warn(`[TabBar] 'unmark-for-suspend' action called with invalid targetId:`, targetId);
} }
break; break;
default: default:
@@ -201,14 +208,17 @@ const contextMenuItems = computed(() => {
const currentIndex = props.sessions.findIndex(s => s.sessionId === targetSessionIdValue); const currentIndex = props.sessions.findIndex(s => s.sessionId === targetSessionIdValue);
const totalTabs = props.sessions.length; const totalTabs = props.sessions.length;
// 添加挂起会话菜单项(如果适用) // 添加标记/取消标记挂起会话菜单项(如果适用)
if (connectionInfo && connectionInfo.type === 'SSH') { if (connectionInfo && connectionInfo.type === 'SSH') {
// 仅对活动的SSH会话显示 (可以进一步判断会话是否真的已连接等) const isActiveSession = targetSessionState.wsManager.isConnected.value;
const isActiveSession = targetSessionState.wsManager.isConnected.value; // 检查 WebSocket 是否连接 if (isActiveSession) { // 只对活动的SSH会话显示相关操作
if (isActiveSession) { if (targetSessionState.isMarkedForSuspend) {
items.push({ label: 'tabs.contextMenu.suspendSession', action: 'suspend-session' }); items.push({ label: 'tabs.contextMenu.unmarkForSuspend', action: 'unmark-for-suspend' });
// 为分隔符提供空的 label 和 action 以满足 MenuItem 类型 } else {
items.push({ label: '', action: '', isSeparator: true }); // 当未标记时,显示原来的“挂起”文本,但 action 触发新的标记流程
items.push({ label: 'tabs.contextMenu.suspendSession', action: 'mark-for-suspend' });
}
items.push({ label: '', action: '', isSeparator: true }); // 分隔符
} }
} }
+2 -1
View File
@@ -1137,7 +1137,8 @@
"closeOthers": "Close Other Tabs", "closeOthers": "Close Other Tabs",
"closeRight": "Close Tabs to the Right", "closeRight": "Close Tabs to the Right",
"closeLeft": "Close Tabs to the Left", "closeLeft": "Close Tabs to the Left",
"suspendSession": "Suspend Session" "suspendSession": "Suspend Session",
"unmarkForSuspend": "Unmark Suspend"
}, },
"closeTabTooltip": "Close Tab", "closeTabTooltip": "Close Tab",
"newTabTooltip": "New Connection Tab" "newTabTooltip": "New Connection Tab"
+2 -1
View File
@@ -1139,7 +1139,8 @@
"closeOthers": "关闭其他标签页", "closeOthers": "关闭其他标签页",
"closeRight": "关闭右侧标签页", "closeRight": "关闭右侧标签页",
"closeLeft": "关闭左侧标签页", "closeLeft": "关闭左侧标签页",
"suspendSession": "挂起会话" "suspendSession": "挂起会话",
"unmarkForSuspend": "取消挂起"
}, },
"closeTabTooltip": "关闭标签页", "closeTabTooltip": "关闭标签页",
"newTabTooltip": "新建连接标签页" "newTabTooltip": "新建连接标签页"
@@ -3,13 +3,15 @@ import { v4 as uuidv4 } from 'uuid';
import { sessions, suspendedSshSessions, isLoadingSuspendedSessions, activeSessionId } from '../state'; import { sessions, suspendedSshSessions, isLoadingSuspendedSessions, activeSessionId } from '../state';
import type { import type {
MessagePayload, MessagePayload,
SshMarkForSuspendReqMessage, // +++ 修改:导入新的请求类型 +++ SshMarkForSuspendReqMessage,
SshUnmarkForSuspendReqMessage, // +++ 新增导入 +++
SshSuspendResumeReqMessage, SshSuspendResumeReqMessage,
SshSuspendTerminateReqMessage, SshSuspendTerminateReqMessage,
SshSuspendRemoveEntryReqMessage, SshSuspendRemoveEntryReqMessage,
SshSuspendEditNameReqMessage, SshSuspendEditNameReqMessage,
// S2C Payloads // S2C Payloads
SshMarkedForSuspendAckPayload, // +++ 新增:导入新的响应类型 +++ SshMarkedForSuspendAckPayload,
SshUnmarkedForSuspendAckPayload, // +++ 新增导入 +++
SshSuspendListResponsePayload, SshSuspendListResponsePayload,
SshSuspendResumedNotifPayload, SshSuspendResumedNotifPayload,
SshOutputCachedChunkPayload, SshOutputCachedChunkPayload,
@@ -77,14 +79,28 @@ export const requestStartSshSuspend = (sessionId: string): void => {
return; return;
} }
// 不再需要获取 initialBuffer let initialBuffer = ''; // +++ 恢复 initialBuffer 的获取 +++
if (session.terminalManager && session.terminalManager.terminalInstance && session.terminalManager.terminalInstance.value) {
const term = session.terminalManager.terminalInstance.value;
const buffer = term.buffer.active;
for (let i = 0; i < buffer.length; i++) {
initialBuffer += (buffer.getLine(i)?.translateToString(true) || '') + '\n';
}
// 移除可能多余的最后一个换行符
if (initialBuffer.endsWith('\n')) {
initialBuffer = initialBuffer.slice(0, -1);
}
console.log(`[${t('term.sshSuspend')}] 已获取会话 ${sessionId} 的初始屏幕缓冲区内容,长度: ${initialBuffer.length}`);
} else {
console.warn(`[${t('term.sshSuspend')}] 未能获取会话 ${sessionId} 的终端实例以提取初始缓冲区。`);
}
const message: SshMarkForSuspendReqMessage = { // +++ 修改:使用新的消息类型 +++ const message: SshMarkForSuspendReqMessage = {
type: 'SSH_MARK_FOR_SUSPEND', type: 'SSH_MARK_FOR_SUSPEND',
payload: { sessionId }, payload: { sessionId, initialBuffer: initialBuffer || undefined }, // +++ 将 initialBuffer 添加到 payload +++
}; };
session.wsManager.sendMessage(message); session.wsManager.sendMessage(message);
console.log(`[${t('term.sshSuspend')}] 已发送 SSH_MARK_FOR_SUSPEND 请求 (会话 ID: ${sessionId})`); console.log(`[${t('term.sshSuspend')}] 已发送 SSH_MARK_FOR_SUSPEND 请求 (会话 ID: ${sessionId}, 包含初始缓冲区: ${!!initialBuffer})`);
// 前端在发送此请求后,会话应保持活动状态,直到用户关闭标签页或网络断开。 // 前端在发送此请求后,会话应保持活动状态,直到用户关闭标签页或网络断开。
// 后端会在 WebSocket 关闭时处理实际的挂起。 // 后端会在 WebSocket 关闭时处理实际的挂起。
// 用户界面上可以给一个提示,表明“此会话已标记,关闭后将尝试挂起”。 // 用户界面上可以给一个提示,表明“此会话已标记,关闭后将尝试挂起”。
@@ -100,6 +116,38 @@ export const requestStartSshSuspend = (sessionId: string): void => {
} }
}; };
/**
* 请求取消标记一个会话为待挂起
* @param sessionId 要取消标记的活动会话 ID
*/
export const requestUnmarkSshSuspend = (sessionId: string): void => {
const session = sessions.value.get(sessionId);
if (session && session.wsManager) {
if (!session.wsManager.isConnected.value) {
console.warn(`[${t('term.sshSuspend')}] WebSocket 未连接,无法请求取消标记挂起 (会话 ID: ${sessionId})。`);
useUiNotificationsStore().addNotification({ type: 'error', message: t('sshSuspend.notifications.wsNotConnectedError') });
return;
}
if (!session.isMarkedForSuspend) {
console.warn(`[${t('term.sshSuspend')}] 会话 ${sessionId} 并未被标记为待挂起,无需取消。`);
// 可以选择不发送请求或发送一个让后端确认的请求
// 为保持简单,如果前端状态已经是未标记,则不执行操作或仅给用户提示
useUiNotificationsStore().addNotification({ type: 'info', message: t('sshSuspend.notifications.notMarkedWarning') });
return;
}
const message: SshUnmarkForSuspendReqMessage = {
type: 'SSH_UNMARK_FOR_SUSPEND',
payload: { sessionId },
};
session.wsManager.sendMessage(message);
console.log(`[${t('term.sshSuspend')}] 已发送 SSH_UNMARK_FOR_SUSPEND 请求 (会话 ID: ${sessionId})`);
} else {
console.warn(`[${t('term.sshSuspend')}] 未找到会话或 WebSocket 管理器 (会话 ID: ${sessionId}),无法请求取消标记挂起。`);
useUiNotificationsStore().addNotification({ type: 'error', message: t('sshSuspend.notifications.sessionNotFoundError') });
}
};
/** /**
* 获取挂起的 SSH 会话列表 (通过 HTTP API) * 获取挂起的 SSH 会话列表 (通过 HTTP API)
*/ */
@@ -331,6 +379,32 @@ const handleSshMarkedForSuspendAck = (payload: SshMarkedForSuspendAckPayload): v
} }
}; };
const handleSshUnmarkedForSuspendAck = (payload: SshUnmarkedForSuspendAckPayload): void => {
const uiNotificationsStore = useUiNotificationsStore();
console.log(`[${t('term.sshSuspend')}] 接到 SSH_UNMARKED_FOR_SUSPEND_ACK:`, payload);
const session = sessions.value.get(payload.sessionId);
if (payload.success) {
if (session) {
session.isMarkedForSuspend = false;
}
uiNotificationsStore.addNotification({
type: 'success',
message: t('sshSuspend.notifications.unmarkedSuccess', { id: payload.sessionId.slice(0,8) }),
});
} else {
// 即便后端失败,如果前端之前是标记状态,也最好保持一致或提示用户检查
// 但通常后端失败意味着前端状态可能与后端不一致,提示错误让用户知晓
uiNotificationsStore.addNotification({
type: 'error',
message: t('sshSuspend.notifications.unmarkError', { error: payload.error || t('term.unknownError') }),
});
console.error(`[${t('term.sshSuspend')}] 取消标记会话 ${payload.sessionId} 失败: ${payload.error}`);
// 此处不自动回滚前端的 isMarkedForSuspend 状态,因为后端是权威源。
// 如果后端说操作失败,那么会话可能仍然被后端认为是标记的(尽管这不应该发生,因为后端会先清除标记)。
}
};
const handleSshSuspendListResponse = (payload: SshSuspendListResponsePayload): void => { const handleSshSuspendListResponse = (payload: SshSuspendListResponsePayload): void => {
console.log(`[${t('term.sshSuspend')}] 接到 SSH_SUSPEND_LIST_RESPONSE,数量: ${payload.suspendSessions.length}`); console.log(`[${t('term.sshSuspend')}] 接到 SSH_SUSPEND_LIST_RESPONSE,数量: ${payload.suspendSessions.length}`);
suspendedSshSessions.value = payload.suspendSessions; suspendedSshSessions.value = payload.suspendSessions;
@@ -536,8 +610,9 @@ export const registerSshSuspendHandlers = (wsManager: WsManagerInstance): void =
// 注意:wsManager.onMessage 返回一个注销函数,如果需要,可以收集它们并在会话关闭时调用。 // 注意:wsManager.onMessage 返回一个注销函数,如果需要,可以收集它们并在会话关闭时调用。
// 但通常这些处理器会随 wsManager 实例的生命周期一起存在。 // 但通常这些处理器会随 wsManager 实例的生命周期一起存在。
// wsManager.onMessage('SSH_SUSPEND_STARTED_RESP', (p: MessagePayload) => handleSshSuspendStartedResp(p as SshSuspendStartedRespPayload)); // 已移除 // wsManager.onMessage('SSH_SUSPEND_STARTED_RESP', (p: MessagePayload) => handleSshSuspendStartedResp(p as SshSuspendStartedRespPayload));
wsManager.onMessage('SSH_MARKED_FOR_SUSPEND_ACK', (p: MessagePayload) => handleSshMarkedForSuspendAck(p as SshMarkedForSuspendAckPayload)); // +++ 新增处理器 +++ wsManager.onMessage('SSH_MARKED_FOR_SUSPEND_ACK', (p: MessagePayload) => handleSshMarkedForSuspendAck(p as SshMarkedForSuspendAckPayload));
wsManager.onMessage('SSH_UNMARKED_FOR_SUSPEND_ACK', (p: MessagePayload) => handleSshUnmarkedForSuspendAck(p as SshUnmarkedForSuspendAckPayload)); // +++ 新增处理器 +++
wsManager.onMessage('SSH_SUSPEND_LIST_RESPONSE', (p: MessagePayload) => handleSshSuspendListResponse(p as SshSuspendListResponsePayload)); wsManager.onMessage('SSH_SUSPEND_LIST_RESPONSE', (p: MessagePayload) => handleSshSuspendListResponse(p as SshSuspendListResponsePayload));
wsManager.onMessage('SSH_SUSPEND_RESUMED_NOTIF', (p: MessagePayload) => handleSshSuspendResumedNotif(p as SshSuspendResumedNotifPayload)); wsManager.onMessage('SSH_SUSPEND_RESUMED_NOTIF', (p: MessagePayload) => handleSshSuspendResumedNotif(p as SshSuspendResumedNotifPayload));
wsManager.onMessage('SSH_OUTPUT_CACHED_CHUNK', (p: MessagePayload) => handleSshOutputCachedChunk(p as SshOutputCachedChunkPayload)); wsManager.onMessage('SSH_OUTPUT_CACHED_CHUNK', (p: MessagePayload) => handleSshOutputCachedChunk(p as SshOutputCachedChunkPayload));
+29 -6
View File
@@ -45,12 +45,23 @@ export interface SshSuspendEditNameReqPayload {
customName: string; customName: string;
} }
export interface SshMarkForSuspendReqPayload { // +++ 新增 +++ export interface SshMarkForSuspendReqPayload {
sessionId: string;
initialBuffer?: string; // +++ 新增:可选的初始屏幕缓冲区内容 +++
}
export interface SshUnmarkForSuspendReqPayload {
sessionId: string; sessionId: string;
} }
// --- Server to Client (S2C) Message Payloads --- // --- Server to Client (S2C) Message Payloads ---
export interface SshMarkedForSuspendAckPayload { // +++ 新增 +++ export interface SshMarkedForSuspendAckPayload {
sessionId: string;
success: boolean;
error?: string;
}
export interface SshUnmarkedForSuspendAckPayload { // +++ 新增 +++
sessionId: string; sessionId: string;
success: boolean; success: boolean;
error?: string; error?: string;
@@ -135,17 +146,27 @@ export interface SshSuspendEditNameReqMessage extends WebSocketMessage {
payload: SshSuspendEditNameReqPayload; payload: SshSuspendEditNameReqPayload;
} }
export interface SshMarkForSuspendReqMessage extends WebSocketMessage { // +++ 新增 +++ export interface SshMarkForSuspendReqMessage extends WebSocketMessage {
type: 'SSH_MARK_FOR_SUSPEND'; type: 'SSH_MARK_FOR_SUSPEND';
payload: SshMarkForSuspendReqPayload; payload: SshMarkForSuspendReqPayload;
} }
export interface SshUnmarkForSuspendReqMessage extends WebSocketMessage { // +++ 新增 +++
type: 'SSH_UNMARK_FOR_SUSPEND';
payload: SshUnmarkForSuspendReqPayload;
}
// --- Specific S2C Message Interfaces --- // --- Specific S2C Message Interfaces ---
export interface SshMarkedForSuspendAckMessage extends WebSocketMessage { // +++ 新增 +++ export interface SshMarkedForSuspendAckMessage extends WebSocketMessage {
type: 'SSH_MARKED_FOR_SUSPEND_ACK'; type: 'SSH_MARKED_FOR_SUSPEND_ACK';
payload: SshMarkedForSuspendAckPayload; payload: SshMarkedForSuspendAckPayload;
} }
export interface SshUnmarkedForSuspendAckMessage extends WebSocketMessage { // +++ 新增 +++
type: 'SSH_UNMARKED_FOR_SUSPEND_ACK';
payload: SshUnmarkedForSuspendAckPayload;
}
export interface SshSuspendStartedRespMessage extends WebSocketMessage { export interface SshSuspendStartedRespMessage extends WebSocketMessage {
type: 'SSH_SUSPEND_STARTED'; type: 'SSH_SUSPEND_STARTED';
payload: SshSuspendStartedRespPayload; payload: SshSuspendStartedRespPayload;
@@ -194,10 +215,12 @@ export type SshSuspendC2SMessage =
| SshSuspendTerminateReqMessage | SshSuspendTerminateReqMessage
| SshSuspendRemoveEntryReqMessage | SshSuspendRemoveEntryReqMessage
| SshSuspendEditNameReqMessage | SshSuspendEditNameReqMessage
| SshMarkForSuspendReqMessage; // +++ 新增 +++ | SshMarkForSuspendReqMessage
| SshUnmarkForSuspendReqMessage; // +++ 新增 +++
export type SshSuspendS2CMessage = export type SshSuspendS2CMessage =
| SshMarkedForSuspendAckMessage // +++ 新增 +++ | SshMarkedForSuspendAckMessage
| SshUnmarkedForSuspendAckMessage // +++ 新增 +++
| SshSuspendStartedRespMessage | SshSuspendStartedRespMessage
| SshSuspendListResponseMessage | SshSuspendListResponseMessage
| SshSuspendResumedNotifMessage | SshSuspendResumedNotifMessage