@gooin/garmin-connect
Version:
Makes it simple to interface with Garmin Connect to get or set any data point
247 lines • 9.6 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.FileMFASessionStorage = void 0;
const fs_1 = require("fs");
const path_1 = __importDefault(require("path"));
/**
* 文件系统实现的MFA会话存储
*/
class FileMFASessionStorage {
constructor(storageDir) {
this.DEFAULT_TIMEOUT = 5 * 60 * 1000; // 5分钟
this.storageDir = storageDir;
this.ensureStorageDir();
}
/**
* 确保存储目录存在
*/
async ensureStorageDir() {
try {
await fs_1.promises.mkdir(this.storageDir, { recursive: true });
}
catch (error) {
console.error('创建MFA存储目录失败:', error);
}
}
/**
* 获取会话文件路径
*/
getSessionFilePath(sessionId) {
return path_1.default.join(this.storageDir, `${sessionId}.json`);
}
/**
* 创建一个等待MFA验证码的Promise
* @param sessionId 会话ID
* @param timeout 超时时间(毫秒)
* @returns Promise<string> 返回验证码
*/
async waitForMFACode(sessionId, timeout = this.DEFAULT_TIMEOUT) {
// 确保存储目录存在
await this.ensureStorageDir();
// 清理可能存在的旧会话
await this.cleanupRequest(sessionId);
// 创建会话文件
const sessionData = {
sessionId,
status: 'waiting',
createdAt: new Date().toISOString(),
timeout: timeout
};
const sessionFilePath = this.getSessionFilePath(sessionId);
await fs_1.promises.writeFile(sessionFilePath, JSON.stringify(sessionData), 'utf8');
console.log(`MFA验证会话已创建: ${sessionId}`);
// 轮询检查验证码是否已提交
return new Promise((resolve, reject) => {
const startTime = Date.now();
const checkInterval = setInterval(async () => {
try {
// 检查是否超时
if (Date.now() - startTime > timeout) {
clearInterval(checkInterval);
await this.cleanupRequest(sessionId);
reject(new Error('MFA验证超时,请重新登录'));
return;
}
// 检查会话文件是否存在
try {
const data = await fs_1.promises.readFile(sessionFilePath, 'utf8');
const session = JSON.parse(data);
// 如果状态已更新为resolved,则返回验证码
if (session.status === 'resolved' && session.code) {
clearInterval(checkInterval);
await this.cleanupRequest(sessionId);
resolve(session.code);
return;
}
// 如果状态已更新为rejected,则抛出错误
if (session.status === 'rejected' && session.error) {
clearInterval(checkInterval);
await this.cleanupRequest(sessionId);
reject(new Error(session.error));
return;
}
}
catch (error) {
// 文件不存在或其他错误,继续等待
}
}
catch (error) {
clearInterval(checkInterval);
await this.cleanupRequest(sessionId);
reject(error instanceof Error
? error
: new Error(String(error)));
}
}, 1000); // 每秒检查一次
});
}
/**
* 提交MFA验证码
* @param sessionId 会话ID
* @param code 验证码
* @returns 是否成功提交
*/
async submitMFACode(sessionId, code) {
const sessionFilePath = this.getSessionFilePath(sessionId);
try {
// 检查会话文件是否存在
const data = await fs_1.promises.readFile(sessionFilePath, 'utf8');
const session = JSON.parse(data);
if (session.status !== 'waiting') {
console.log(`MFA验证会话状态不正确: ${sessionId}, 状态: ${session.status}`);
return false;
}
// 更新会话状态为已解决
session.status = 'resolved';
session.code = code;
session.resolvedAt = new Date().toISOString();
await fs_1.promises.writeFile(sessionFilePath, JSON.stringify(session), 'utf8');
console.log(`MFA验证码已提交: code ${code}, sessionId ${sessionId}`);
return true;
}
catch (error) {
console.log(`未找到MFA验证会话: ${sessionId}, 错误: ${error instanceof Error ? error.message : String(error)}`);
return false;
}
}
/**
* 取消MFA验证
* @param sessionId 会话ID
* @param reason 取消原因
* @returns 是否成功取消
*/
async cancelMFARequest(sessionId, reason = 'MFA验证已取消') {
const sessionFilePath = this.getSessionFilePath(sessionId);
try {
// 检查会话文件是否存在
const data = await fs_1.promises.readFile(sessionFilePath, 'utf8');
const session = JSON.parse(data);
// 更新会话状态为已拒绝
session.status = 'rejected';
session.error = reason;
session.rejectedAt = new Date().toISOString();
await fs_1.promises.writeFile(sessionFilePath, JSON.stringify(session), 'utf8');
console.log(`MFA验证已取消: ${sessionId}, 原因: ${reason}`);
return true;
}
catch (error) {
console.log(`未找到MFA验证会话: ${sessionId}, 错误: ${error instanceof Error ? error.message : String(error)}`);
return false;
}
}
/**
* 检查会话是否存在
* @param sessionId 会话ID
* @returns 是否存在
*/
async hasSession(sessionId) {
const sessionFilePath = this.getSessionFilePath(sessionId);
try {
await fs_1.promises.access(sessionFilePath);
return true;
}
catch (_a) {
return false;
}
}
/**
* 获取所有活跃的会话ID
* @returns 会话ID列表
*/
async getActiveSessions() {
try {
await this.ensureStorageDir();
const files = await fs_1.promises.readdir(this.storageDir);
return files
.filter((file) => file.endsWith('.json'))
.map((file) => file.replace('.json', ''));
}
catch (error) {
console.error('获取活跃会话失败:', error instanceof Error ? error : String(error));
return [];
}
}
/**
* 清理过期的请求
* @param maxAge 最大存活时间(毫秒)
*/
async cleanupExpiredRequests(maxAge = this.DEFAULT_TIMEOUT) {
try {
await this.ensureStorageDir();
const files = await fs_1.promises.readdir(this.storageDir);
const now = new Date();
let cleanedCount = 0;
for (const file of files) {
if (!file.endsWith('.json'))
continue;
try {
const filePath = path_1.default.join(this.storageDir, file);
const data = await fs_1.promises.readFile(filePath, 'utf8');
const session = JSON.parse(data);
const createdAt = new Date(session.createdAt);
// 检查是否过期
if (now.getTime() - createdAt.getTime() > maxAge) {
await fs_1.promises.unlink(filePath);
cleanedCount++;
}
}
catch (error) {
// 如果文件读取失败,直接删除
try {
await fs_1.promises.unlink(path_1.default.join(this.storageDir, file));
cleanedCount++;
}
catch (unlinkError) {
console.error(`删除过期会话文件失败: ${file}`, unlinkError instanceof Error
? unlinkError
: String(unlinkError));
}
}
}
if (cleanedCount > 0) {
console.log(`清理了 ${cleanedCount} 个过期的MFA验证会话`);
}
}
catch (error) {
console.error('清理过期请求失败:', error instanceof Error ? error : String(error));
}
}
/**
* 清理指定会话的资源
* @param sessionId 会话ID
*/
async cleanupRequest(sessionId) {
const sessionFilePath = this.getSessionFilePath(sessionId);
try {
await fs_1.promises.unlink(sessionFilePath);
}
catch (error) {
// 文件不存在,忽略错误
}
}
}
exports.FileMFASessionStorage = FileMFASessionStorage;
//# sourceMappingURL=FileMFASessionStorage.js.map