update
This commit is contained in:
@@ -1,12 +1,25 @@
|
||||
import { ref, onUnmounted, type Ref } from 'vue';
|
||||
import { useWebSocketConnection } from './useWebSocketConnection'; // 只导入 hook 本身
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { ref, readonly, type Ref, ComputedRef } from 'vue'; // 修正导入,移除大写 Readonly
|
||||
// import { useWebSocketConnection } from './useWebSocketConnection'; // 移除全局导入
|
||||
import type { Terminal } from 'xterm';
|
||||
import type { WebSocketMessage, MessagePayload } from '../types/websocket.types'; // 从类型文件导入
|
||||
import type { WebSocketMessage, MessagePayload } from '../types/websocket.types';
|
||||
|
||||
export function useSshTerminal() {
|
||||
const { t } = useI18n();
|
||||
const { sendMessage, onMessage, isConnected } = useWebSocketConnection();
|
||||
// 定义与 WebSocket 相关的依赖接口
|
||||
export interface SshTerminalDependencies {
|
||||
sendMessage: (message: WebSocketMessage) => void;
|
||||
onMessage: (type: string, handler: (payload: any, fullMessage?: WebSocketMessage) => void) => () => void;
|
||||
isConnected: ComputedRef<boolean>;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建一个 SSH 终端管理器实例
|
||||
* @param sessionId 会话唯一标识符
|
||||
* @param wsDeps WebSocket 依赖对象
|
||||
* @param t i18n 翻译函数,从父组件传入
|
||||
* @returns SSH 终端管理器实例
|
||||
*/
|
||||
export function createSshTerminalManager(sessionId: string, wsDeps: SshTerminalDependencies, t: Function) {
|
||||
// 使用依赖注入的 WebSocket 函数
|
||||
const { sendMessage, onMessage, isConnected } = wsDeps;
|
||||
|
||||
const terminalInstance = ref<Terminal | null>(null);
|
||||
const terminalOutputBuffer = ref<string[]>([]); // 缓冲 WebSocket 消息直到终端准备好
|
||||
@@ -22,7 +35,7 @@ export function useSshTerminal() {
|
||||
// --- 终端事件处理 ---
|
||||
|
||||
const handleTerminalReady = (term: Terminal) => {
|
||||
console.log('[SSH终端模块] 终端实例已就绪。');
|
||||
console.log(`[会话 ${sessionId}][SSH终端模块] 终端实例已就绪。`);
|
||||
terminalInstance.value = term;
|
||||
// 将缓冲区的输出写入终端
|
||||
terminalOutputBuffer.value.forEach(data => term.write(data));
|
||||
@@ -32,31 +45,36 @@ export function useSshTerminal() {
|
||||
};
|
||||
|
||||
const handleTerminalData = (data: string) => {
|
||||
// console.debug('[SSH终端模块] 接收到终端输入:', data);
|
||||
sendMessage({ type: 'ssh:input', payload: { data } });
|
||||
// console.debug(`[会话 ${sessionId}][SSH终端模块] 接收到终端输入:`, data);
|
||||
sendMessage({ type: 'ssh:input', sessionId, payload: { data } });
|
||||
};
|
||||
|
||||
const handleTerminalResize = (dimensions: { cols: number; rows: number }) => {
|
||||
console.log('[SSH终端模块] 发送终端大小调整:', dimensions);
|
||||
sendMessage({ type: 'ssh:resize', payload: dimensions });
|
||||
console.log(`[会话 ${sessionId}][SSH终端模块] 发送终端大小调整:`, dimensions);
|
||||
sendMessage({ type: 'ssh:resize', sessionId, payload: dimensions });
|
||||
};
|
||||
|
||||
// --- WebSocket 消息处理 ---
|
||||
|
||||
const handleSshOutput = (payload: MessagePayload, message: WebSocketMessage) => {
|
||||
const handleSshOutput = (payload: MessagePayload, message?: WebSocketMessage) => {
|
||||
// 检查消息是否属于此会话
|
||||
if (message?.sessionId && message.sessionId !== sessionId) {
|
||||
return; // 忽略不属于此会话的消息
|
||||
}
|
||||
|
||||
let outputData = payload;
|
||||
// 检查是否为 Base64 编码 (需要后端配合发送 encoding 字段)
|
||||
if (message.encoding === 'base64' && typeof outputData === 'string') {
|
||||
if (message?.encoding === 'base64' && typeof outputData === 'string') {
|
||||
try {
|
||||
outputData = atob(outputData); // 在浏览器环境中使用 atob
|
||||
} catch (e) {
|
||||
console.error('[SSH终端模块] Base64 解码失败:', e, '原始数据:', message.payload);
|
||||
console.error(`[会话 ${sessionId}][SSH终端模块] Base64 解码失败:`, e, '原始数据:', message.payload);
|
||||
outputData = `\r\n[解码错误: ${e}]\r\n`; // 在终端显示解码错误
|
||||
}
|
||||
}
|
||||
// 如果不是 base64 或解码失败,确保它是字符串
|
||||
else if (typeof outputData !== 'string') {
|
||||
console.warn('[SSH终端模块] 收到非字符串 ssh:output payload:', outputData);
|
||||
console.warn(`[会话 ${sessionId}][SSH终端模块] 收到非字符串 ssh:output payload:`, outputData);
|
||||
try {
|
||||
outputData = JSON.stringify(outputData); // 尝试序列化
|
||||
} catch {
|
||||
@@ -64,6 +82,19 @@ export function useSshTerminal() {
|
||||
}
|
||||
}
|
||||
|
||||
// 尝试过滤掉非标准的 OSC 184 序列
|
||||
// 注意:这个正则表达式可能需要根据实际序列进行调整
|
||||
// 它尝试匹配 \x1b]184;... 直到 \x1b\\ 或 \x07
|
||||
const osc184Regex = /\x1b]184;[^\x1b\x07]*(\x1b\\|\x07)/g;
|
||||
if (typeof outputData === 'string') {
|
||||
const originalLength = outputData.length;
|
||||
outputData = outputData.replace(osc184Regex, '');
|
||||
if (outputData.length < originalLength) {
|
||||
console.warn(`[会话 ${sessionId}][SSH终端模块] 过滤掉 OSC 184 序列。`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (terminalInstance.value) {
|
||||
terminalInstance.value.write(outputData);
|
||||
} else {
|
||||
@@ -72,50 +103,80 @@ export function useSshTerminal() {
|
||||
}
|
||||
};
|
||||
|
||||
const handleSshConnected = () => {
|
||||
console.log('[SSH终端模块] SSH 会话已连接。');
|
||||
const handleSshConnected = (payload: MessagePayload, message?: WebSocketMessage) => {
|
||||
// 检查消息是否属于此会话
|
||||
if (message?.sessionId && message.sessionId !== sessionId) {
|
||||
return; // 忽略不属于此会话的消息
|
||||
}
|
||||
|
||||
console.log(`[会话 ${sessionId}][SSH终端模块] SSH 会话已连接。`);
|
||||
// 连接成功后聚焦终端
|
||||
terminalInstance.value?.focus();
|
||||
// 清空可能存在的旧缓冲(虽然理论上此时应该已经 ready 了)
|
||||
if (terminalOutputBuffer.value.length > 0) {
|
||||
console.warn('[SSH终端模块] SSH 连接时仍有缓冲数据,正在写入...');
|
||||
console.warn(`[会话 ${sessionId}][SSH终端模块] SSH 连接时仍有缓冲数据,正在写入...`);
|
||||
terminalOutputBuffer.value.forEach(data => terminalInstance.value?.write(data));
|
||||
terminalOutputBuffer.value = [];
|
||||
}
|
||||
};
|
||||
|
||||
const handleSshDisconnected = (payload: MessagePayload) => {
|
||||
const handleSshDisconnected = (payload: MessagePayload, message?: WebSocketMessage) => {
|
||||
// 检查消息是否属于此会话
|
||||
if (message?.sessionId && message.sessionId !== sessionId) {
|
||||
return; // 忽略不属于此会话的消息
|
||||
}
|
||||
|
||||
const reason = payload || t('workspace.terminal.unknownReason'); // 使用 i18n 获取未知原因文本
|
||||
console.log('[SSH终端模块] SSH 会话已断开:', reason);
|
||||
console.log(`[会话 ${sessionId}][SSH终端模块] SSH 会话已断开:`, reason);
|
||||
terminalInstance.value?.writeln(`\r\n\x1b[31m${getTerminalText('disconnectMsg', { reason })}\x1b[0m`);
|
||||
// 可以在这里添加其他清理逻辑,例如禁用输入
|
||||
};
|
||||
|
||||
const handleSshError = (payload: MessagePayload) => {
|
||||
const handleSshError = (payload: MessagePayload, message?: WebSocketMessage) => {
|
||||
// 检查消息是否属于此会话
|
||||
if (message?.sessionId && message.sessionId !== sessionId) {
|
||||
return; // 忽略不属于此会话的消息
|
||||
}
|
||||
|
||||
const errorMsg = payload || t('workspace.terminal.unknownSshError'); // 使用 i18n
|
||||
console.error('[SSH终端模块] SSH 错误:', errorMsg);
|
||||
console.error(`[会话 ${sessionId}][SSH终端模块] SSH 错误:`, errorMsg);
|
||||
terminalInstance.value?.writeln(`\r\n\x1b[31m${getTerminalText('genericErrorMsg', { message: errorMsg })}\x1b[0m`);
|
||||
};
|
||||
|
||||
const handleSshStatus = (payload: MessagePayload) => {
|
||||
const handleSshStatus = (payload: MessagePayload, message?: WebSocketMessage) => {
|
||||
// 检查消息是否属于此会话
|
||||
if (message?.sessionId && message.sessionId !== sessionId) {
|
||||
return; // 忽略不属于此会话的消息
|
||||
}
|
||||
|
||||
// 这个消息现在由 useWebSocketConnection 处理以更新全局状态栏消息
|
||||
// 这里可以保留日志或用于其他特定于终端的 UI 更新(如果需要)
|
||||
const statusKey = payload?.key || 'unknown';
|
||||
const statusParams = payload?.params || {};
|
||||
console.log('[SSH终端模块] 收到 SSH 状态更新:', statusKey, statusParams);
|
||||
console.log(`[会话 ${sessionId}][SSH终端模块] 收到 SSH 状态更新:`, statusKey, statusParams);
|
||||
// 可以在终端打印一些状态信息吗?
|
||||
// terminalInstance.value?.writeln(`\r\n\x1b[34m[状态: ${statusKey}]\x1b[0m`);
|
||||
};
|
||||
|
||||
const handleInfoMessage = (payload: MessagePayload) => {
|
||||
console.log('[SSH终端模块] 收到后端信息:', payload);
|
||||
const handleInfoMessage = (payload: MessagePayload, message?: WebSocketMessage) => {
|
||||
// 检查消息是否属于此会话
|
||||
if (message?.sessionId && message.sessionId !== sessionId) {
|
||||
return; // 忽略不属于此会话的消息
|
||||
}
|
||||
|
||||
console.log(`[会话 ${sessionId}][SSH终端模块] 收到后端信息:`, payload);
|
||||
terminalInstance.value?.writeln(`\r\n\x1b[34m${getTerminalText('infoPrefix')} ${payload}\x1b[0m`);
|
||||
};
|
||||
|
||||
const handleErrorMessage = (payload: MessagePayload) => {
|
||||
const handleErrorMessage = (payload: MessagePayload, message?: WebSocketMessage) => {
|
||||
// 检查消息是否属于此会话
|
||||
if (message?.sessionId && message.sessionId !== sessionId) {
|
||||
return; // 忽略不属于此会话的消息
|
||||
}
|
||||
|
||||
// 通用错误也可能需要显示在终端
|
||||
const errorMsg = payload || t('workspace.terminal.unknownGenericError'); // 使用 i18n
|
||||
console.error('[SSH终端模块] 收到后端通用错误:', errorMsg);
|
||||
console.error(`[会话 ${sessionId}][SSH终端模块] 收到后端通用错误:`, errorMsg);
|
||||
terminalInstance.value?.writeln(`\r\n\x1b[31m${getTerminalText('errorPrefix')} ${errorMsg}\x1b[0m`);
|
||||
};
|
||||
|
||||
@@ -131,30 +192,62 @@ export function useSshTerminal() {
|
||||
unregisterHandlers.push(onMessage('ssh:status', handleSshStatus));
|
||||
unregisterHandlers.push(onMessage('info', handleInfoMessage));
|
||||
unregisterHandlers.push(onMessage('error', handleErrorMessage)); // 也处理通用错误
|
||||
console.log('[SSH终端模块] 已注册 SSH 相关消息处理器。');
|
||||
console.log(`[会话 ${sessionId}][SSH终端模块] 已注册 SSH 相关消息处理器。`);
|
||||
};
|
||||
|
||||
const unregisterAllSshHandlers = () => {
|
||||
console.log('[SSH终端模块] 注销 SSH 相关消息处理器...');
|
||||
console.log(`[会话 ${sessionId}][SSH终端模块] 注销 SSH 相关消息处理器...`);
|
||||
unregisterHandlers.forEach(unregister => unregister?.());
|
||||
unregisterHandlers.length = 0; // 清空数组
|
||||
};
|
||||
|
||||
// --- 清理 ---
|
||||
onUnmounted(() => {
|
||||
// 初始化时自动注册处理程序
|
||||
registerSshHandlers();
|
||||
|
||||
// --- 清理函数 ---
|
||||
const cleanup = () => {
|
||||
unregisterAllSshHandlers();
|
||||
// terminalInstance.value?.dispose(); // 终端实例的销毁由 TerminalComponent 负责
|
||||
terminalInstance.value = null;
|
||||
console.log('[SSH终端模块] Composable 已卸载。');
|
||||
});
|
||||
console.log(`[会话 ${sessionId}][SSH终端模块] 已清理。`);
|
||||
};
|
||||
|
||||
// --- 暴露给组件的接口 ---
|
||||
// 返回工厂实例
|
||||
return {
|
||||
terminalInstance, // 暴露终端实例 ref,以便组件可以访问(如果需要)
|
||||
// 公共接口
|
||||
handleTerminalReady,
|
||||
handleTerminalData,
|
||||
handleTerminalResize,
|
||||
registerSshHandlers, // 暴露注册函数,由父组件在连接后调用
|
||||
unregisterAllSshHandlers, // 暴露注销函数,在断开或卸载时调用
|
||||
cleanup
|
||||
};
|
||||
}
|
||||
|
||||
// 保留兼容旧代码的函数(将在完全迁移后移除)
|
||||
export function useSshTerminal(t: (key: string) => string) {
|
||||
console.warn('⚠️ 使用已弃用的 useSshTerminal() 全局单例。请迁移到 createSshTerminalManager() 工厂函数。');
|
||||
|
||||
const terminalInstance = ref<Terminal | null>(null);
|
||||
|
||||
const handleTerminalReady = (term: Terminal) => {
|
||||
console.log('[SSH终端模块][旧] 终端实例已就绪,但使用了已弃用的单例模式。');
|
||||
terminalInstance.value = term;
|
||||
};
|
||||
|
||||
const handleTerminalData = (data: string) => {
|
||||
console.warn('[SSH终端模块][旧] 收到终端数据,但使用了已弃用的单例模式,无法发送。');
|
||||
};
|
||||
|
||||
const handleTerminalResize = (dimensions: { cols: number; rows: number }) => {
|
||||
console.warn('[SSH终端模块][旧] 收到终端大小调整,但使用了已弃用的单例模式,无法发送。');
|
||||
};
|
||||
|
||||
// 返回与旧接口兼容的空函数,以避免错误
|
||||
return {
|
||||
terminalInstance,
|
||||
handleTerminalReady,
|
||||
handleTerminalData,
|
||||
handleTerminalResize,
|
||||
registerSshHandlers: () => console.warn('[SSH终端模块][旧] 调用了已弃用的 registerSshHandlers'),
|
||||
unregisterAllSshHandlers: () => console.warn('[SSH终端模块][旧] 调用了已弃用的 unregisterAllSshHandlers'),
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user