diff --git a/src/putty.ts b/src/putty.ts index c8f70f5..60c4c09 100644 --- a/src/putty.ts +++ b/src/putty.ts @@ -1,89 +1,204 @@ - +// 导入 winreg 模块,用于操作 Windows 注册表 import * as Winreg from 'winreg'; +// 导入 Logging 模块,用于记录日志 import { Logging } from './logging'; +// 导入 toPromise 函数,用于将回调函数转换为 Promise import { toPromise } from './utils'; +// 创建一个 Winreg 实例,用于访问 Windows 注册表中的 PuTTY 会话信息 const winreg = new Winreg({ + // 指定要访问的注册表 hive,这里是 HKCU(当前用户) hive: Winreg.HKCU, + // 指定要访问的注册表键路径,这里是 \Software\SimonTatham\PuTTY\Sessions\ key: `\\Software\\SimonTatham\\PuTTY\\Sessions\\`, }); +// 定义一个类型别名 NumberAsBoolean,它表示一个数字,其值只能是 0 或 1 export type NumberAsBoolean = 0 | 1; +/** + * 定义一个 PuttySession 接口,它描述了 PuTTY 会话的配置信息 + */ export interface PuttySession { //[key: string]: string | number | undefined; // General settings + // 会话的名称 name: string; + // 远程主机的名称或 IP 地址 hostname: string; + // 连接协议,可以是 'ssh'、'telnet' 等 protocol: string; + // 远程主机的端口号 portnumber: number; + // 用户名,可选 username?: string; + // 是否从环境变量中获取用户名,0 或 1 usernamefromenvironment: NumberAsBoolean; + // 是否尝试使用代理,0 或 1 tryagent: NumberAsBoolean; + // 公钥文件路径,可选 publickeyfile?: string; // Proxy settings + // 代理主机,可选 proxyhost?: string; + // 代理端口号 proxyport: number; + // 是否使用本地代理,0 或 1 proxylocalhost: NumberAsBoolean; - proxymethod: number; // Key of ['None', 'SOCKS 4', 'SOCKS 5', 'HTTP', 'Telnet', 'Local'] + // 代理方法,值为 ['None', 'SOCKS 4', 'SOCKS 5', 'HTTP', 'Telnet', 'Local'] 中的一个 Key of ['None', 'SOCKS 4', 'SOCKS 5', 'HTTP', 'Telnet', 'Local'] + proxymethod: number; } +/** + * 从注册表项中提取值 + * @param item - 注册表项 + * @returns 提取的值,如果类型未知则抛出错误 + */ function valueFromItem(item: Winreg.RegistryItem) { switch (item.type) { case 'REG_DWORD': + // 将十六进制字符串转换为整数 return parseInt(item.value, 16); case 'REG_SZ': + // 返回字符串值 return item.value; } + // 如果注册表项的类型未知,则抛出错误 throw new Error(`Unknown RegistryItem type: '${item.type}'`); } +/** + * 定义一个包含 PuttySession 接口中所有属性的数组,用于格式化会话信息 + */ const FORMATTED_FIELDS: (keyof PuttySession)[] = [ - 'name', 'hostname', 'protocol', 'portnumber', - 'username', 'usernamefromenvironment', 'tryagent', 'publickeyfile', - 'proxyhost', 'proxyport', 'proxylocalhost', 'proxymethod', + // 会话的名称 + 'name', + // 远程主机的名称或 IP 地址 + 'hostname', + // 连接协议,可以是 'ssh'、'telnet' 等 + 'protocol', + // 远程主机的端口号 + 'portnumber', + // 用户名,可选 + 'username', + // 是否从环境变量中获取用户名,0 或 1 + 'usernamefromenvironment', + // 是否尝试使用代理,0 或 1 + 'tryagent', + // 公钥文件路径,可选 + 'publickeyfile', + // 代理主机,可选 + 'proxyhost', + // 代理端口号 + 'proxyport', + // 是否使用本地代理,0 或 1 + 'proxylocalhost', + // 代理方法,值为 ['None', 'SOCKS 4', 'SOCKS 5', 'HTTP', 'Telnet', 'Local'] 中的一个 + 'proxymethod', ]; + +/** + * 将 PuTTY 会话对象格式化为字符串 + * @param session - 要格式化的 PuTTY 会话对象 + * @returns 格式化后的字符串,包含会话的名称、主机名、协议、端口号、用户名、用户名是否从环境变量获取、是否尝试使用代理、公钥文件路径、代理主机、代理端口号、是否使用本地代理和代理方法 + */ export function formatSession(session: PuttySession): string { + // 创建一个 Partial 类型的对象 partial,用于存储会话的部分信息 const partial: Partial = {}; - for (const field of FORMATTED_FIELDS) partial[field] = session[field] as any; + // 遍历 FORMATTED_FIELDS 数组中的每个字段 + for (const field of FORMATTED_FIELDS) { + // 将 session 对象中的每个字段的值赋给 partial 对象中的对应字段 + partial[field] = session[field] as any; + } + // 将 partial 对象转换为 JSON 字符串并返回 return JSON.stringify(partial); } -export async function getSessions() { +/** + * 从注册表中获取所有 PuTTY 会话信息 + * @returns 包含所有 PuTTY 会话信息的数组 + */ +export async function getSessions(): Promise { + // 记录日志,表明正在从注册表中获取 PuTTY 会话信息 Logging.info`Fetching PuTTY sessions from registry`; + // 使用 winreg 模块的 keys 方法获取所有 PuTTY 会话的注册表项,并将其转换为 Promise const values = await toPromise(cb => winreg.keys(cb)); + // 初始化一个空数组,用于存储解析后的 PuTTY 会话对象 const sessions: PuttySession[] = []; + // 遍历所有注册表项,解析每个会话的信息并添加到 sessions 数组中 await Promise.all(values.map(regSession => (async () => { + // 从注册表项的键名中提取会话名称,并进行 URI 解码 const name = decodeURIComponent(regSession.key.substr(winreg.key.length)); + // 使用 values 方法获取当前会话的所有值,并将其转换为 Promise const props = await toPromise(cb => regSession.values(cb)); + // 初始化一个对象,用于存储当前会话的属性 const properties: { [key: string]: string | number } = {}; + // 遍历所有属性,将其值添加到 properties 对象中 props.forEach(prop => properties[prop.name.toLowerCase()] = valueFromItem(prop)); - sessions.push({ name, ...(properties as any) }); + // 将解析后的会话信息添加到 sessions 数组中 + sessions.push({ name,...(properties as any) }); })())); + // 记录日志,表明已找到的会话数量 Logging.debug`\tFound ${sessions.length} sessions:`; + // 遍历所有会话,记录日志并输出格式化后的会话信息 sessions.forEach(s => Logging.debug`\t- ${formatSession(s)}`); + // 返回包含所有 PuTTY 会话信息的数组 return sessions; } +/** + * 在给定的会话数组中查找匹配的会话 + * @param sessions - 要搜索的会话数组 + * @param name - 要匹配的会话名称,可选 + * @param host - 要匹配的主机名或 IP 地址,可选 + * @param username - 要匹配的用户名,可选 + * @param nameOnly - 是否只根据名称进行匹配,默认为 true + * @returns 匹配的会话对象,如果没有找到则返回 undefined + */ export async function findSession(sessions: PuttySession[], name?: string, host?: string, username?: string, nameOnly = true): Promise { if (name) { + // 将名称转换为小写,以便不区分大小写地进行搜索 name = name.toLowerCase(); + // 在会话数组中查找名称匹配的会话 const session = sessions.find(s => s.name.toLowerCase() === name); + // 如果只根据名称进行匹配,或者找到了匹配的会话,则返回该会话 if (nameOnly || session) return session; } + // 如果没有提供主机名,则返回未定义 if (!host) return undefined; + // 将主机名转换为小写,以便不区分大小写地进行搜索 host = host.toLowerCase(); + // 在会话数组中查找主机名匹配的会话 const hosts = sessions.filter(s => s.hostname && s.hostname.toLowerCase() === host); + // 如果没有提供用户名,则返回第一个匹配的会话或 null if (!username) return hosts[0] || null; + // 将用户名转换为小写,以便不区分大小写地进行搜索 username = username.toLowerCase(); + // 在主机名匹配的会话中查找用户名匹配的会话 return hosts.find(s => !s.username || s.username.toLowerCase() === username); } +/** + * 获取指定名称、主机或用户名的 PuTTY 会话 + * @param name - 要匹配的会话名称,可选 + * @param host - 要匹配的主机名或 IP 地址,可选 + * @param username - 要匹配的用户名,可选 + * @param nameOnly - 是否只根据名称进行匹配,默认为 true + * @returns 匹配的会话对象,如果没有找到则返回 undefined + */ export async function getSession(name?: string, host?: string, username?: string, nameOnly = true): Promise { + // 从注册表中获取所有 PuTTY 会话信息 const sessions = await getSessions(); + // 在获取的会话数组中查找匹配的会话 return findSession(sessions, name, host, username, nameOnly); } +/** + * 获取一个缓存的查找器函数,该函数使用预加载的会话数据进行查找 + * @returns 一个函数,该函数接受与 getSession 相同的参数,并返回匹配的会话对象 + */ export async function getCachedFinder(): Promise { + // 从注册表中获取所有 PuTTY 会话信息 const sessions = await getSessions(); + // 返回一个函数,该函数接受与 getSession 相同的参数,并使用预加载的会话数据进行查找 return (...args) => findSession(sessions, ...args); }