UNPKG

@ankimcp/anki-mcp-server

Version:

Model Context Protocol server for Anki - enables AI assistants to interact with your Anki flashcards

152 lines • 6.17 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.CredentialsService = void 0; const promises_1 = require("fs/promises"); const path_1 = require("path"); const os_1 = require("os"); const fs_1 = require("fs"); const common_1 = require("@nestjs/common"); class CredentialsService { logger = new common_1.Logger(CredentialsService.name); static CREDENTIALS_DIR = (0, path_1.join)((0, os_1.homedir)(), ".ankimcp"); static CREDENTIALS_FILE = (0, path_1.join)(CredentialsService.CREDENTIALS_DIR, "credentials.json"); getCredentialsPath() { return CredentialsService.CREDENTIALS_FILE; } async saveCredentials(credentials) { try { await this.ensureCredentialsDirectory(); const content = JSON.stringify(credentials, null, 2); await (0, promises_1.writeFile)(CredentialsService.CREDENTIALS_FILE, content, { encoding: "utf-8", mode: 0o600, }); await (0, promises_1.chmod)(CredentialsService.CREDENTIALS_FILE, 0o600); } catch (error) { throw new Error(`Failed to save credentials to ${CredentialsService.CREDENTIALS_FILE}: ${error instanceof Error ? error.message : String(error)}`, { cause: error }); } } async loadCredentials() { try { await (0, promises_1.access)(CredentialsService.CREDENTIALS_FILE, fs_1.constants.F_OK | fs_1.constants.R_OK); const content = await (0, promises_1.readFile)(CredentialsService.CREDENTIALS_FILE, { encoding: "utf-8", }); const credentials = JSON.parse(content); const validation = this.validateCredentials(credentials); if (!validation.valid) { this.logger.warn(`Corrupted credentials file at ${CredentialsService.CREDENTIALS_FILE}:`); validation.errors.forEach((error) => { this.logger.warn(` - ${error}`); }); return null; } return credentials; } catch (error) { if (error.code === "ENOENT") { return null; } this.logger.warn(`Failed to load credentials from ${CredentialsService.CREDENTIALS_FILE}: ${error instanceof Error ? error.message : String(error)}`); return null; } } async clearCredentials() { try { await (0, promises_1.unlink)(CredentialsService.CREDENTIALS_FILE); } catch (error) { if (error.code !== "ENOENT") { throw new Error(`Failed to delete credentials file: ${error instanceof Error ? error.message : String(error)}`, { cause: error }); } } } async hasCredentials() { try { await (0, promises_1.access)(CredentialsService.CREDENTIALS_FILE, fs_1.constants.F_OK); return true; } catch { return false; } } isTokenExpired(credentials) { const expiresAt = new Date(credentials.expires_at).getTime(); const now = Date.now(); const bufferMs = 60 * 1000; return now >= expiresAt - bufferMs; } async ensureCredentialsDirectory() { try { await (0, promises_1.access)(CredentialsService.CREDENTIALS_DIR, fs_1.constants.F_OK); } catch (error) { if (error.code === "ENOENT") { await (0, promises_1.mkdir)(CredentialsService.CREDENTIALS_DIR, { recursive: true, mode: 0o700, }); } else { throw error; } } } validateCredentials(obj) { const errors = []; if (!obj || typeof obj !== "object") { errors.push("Credentials must be an object"); return { valid: false, errors }; } const creds = obj; if (!("access_token" in creds)) { errors.push("Missing field: access_token"); } else if (typeof creds.access_token !== "string") { errors.push(`Invalid type for access_token: expected string, got ${typeof creds.access_token}`); } if (!("refresh_token" in creds)) { errors.push("Missing field: refresh_token"); } else if (typeof creds.refresh_token !== "string") { errors.push(`Invalid type for refresh_token: expected string, got ${typeof creds.refresh_token}`); } if (!("expires_at" in creds)) { errors.push("Missing field: expires_at"); } else if (typeof creds.expires_at !== "string") { errors.push(`Invalid type for expires_at: expected string, got ${typeof creds.expires_at}`); } if (!("user" in creds)) { errors.push("Missing field: user"); } else if (!creds.user || typeof creds.user !== "object") { errors.push(`Invalid type for user: expected object, got ${typeof creds.user}`); } else { const user = creds.user; if (!("id" in user)) { errors.push("Missing field: user.id"); } else if (typeof user.id !== "string") { errors.push(`Invalid type for user.id: expected string, got ${typeof user.id}`); } if (!("email" in user)) { errors.push("Missing field: user.email"); } else if (typeof user.email !== "string") { errors.push(`Invalid type for user.email: expected string, got ${typeof user.email}`); } if (!("tier" in user)) { errors.push("Missing field: user.tier"); } else if (user.tier !== "free" && user.tier !== "paid") { errors.push(`Invalid tier value: expected 'free' or 'paid', got '${String(user.tier)}'`); } } return { valid: errors.length === 0, errors }; } } exports.CredentialsService = CredentialsService; //# sourceMappingURL=credentials.service.js.map