|
|
|
@ -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<PuttySession> 类型的对象 partial,用于存储会话的部分信息
|
|
|
|
|
const partial: Partial<PuttySession> = {};
|
|
|
|
|
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<PuttySession[]> {
|
|
|
|
|
// 记录日志,表明正在从注册表中获取 PuTTY 会话信息
|
|
|
|
|
Logging.info`Fetching PuTTY sessions from registry`;
|
|
|
|
|
// 使用 winreg 模块的 keys 方法获取所有 PuTTY 会话的注册表项,并将其转换为 Promise
|
|
|
|
|
const values = await toPromise<Winreg.Registry[]>(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<Winreg.RegistryItem[]>(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<PuttySession | undefined> {
|
|
|
|
|
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<PuttySession | undefined> {
|
|
|
|
|
// 从注册表中获取所有 PuTTY 会话信息
|
|
|
|
|
const sessions = await getSessions();
|
|
|
|
|
// 在获取的会话数组中查找匹配的会话
|
|
|
|
|
return findSession(sessions, name, host, username, nameOnly);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 获取一个缓存的查找器函数,该函数使用预加载的会话数据进行查找
|
|
|
|
|
* @returns 一个函数,该函数接受与 getSession 相同的参数,并返回匹配的会话对象
|
|
|
|
|
*/
|
|
|
|
|
export async function getCachedFinder(): Promise<typeof getSession> {
|
|
|
|
|
// 从注册表中获取所有 PuTTY 会话信息
|
|
|
|
|
const sessions = await getSessions();
|
|
|
|
|
// 返回一个函数,该函数接受与 getSession 相同的参数,并使用预加载的会话数据进行查找
|
|
|
|
|
return (...args) => findSession(sessions, ...args);
|
|
|
|
|
}
|
|
|
|
|