UNPKG

@ankimcp/anki-mcp-server

Version:

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

230 lines • 10.2 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); var __metadata = (this && this.__metadata) || function (k, v) { if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); }; var DeviceFlowService_1; Object.defineProperty(exports, "__esModule", { value: true }); exports.DeviceFlowService = exports.DeviceFlowError = void 0; const common_1 = require("@nestjs/common"); const ky_1 = __importStar(require("ky")); const app_config_service_1 = require("../app-config.service.js"); class DeviceFlowError extends Error { code; description; constructor(message, code, description) { super(message); this.code = code; this.description = description; this.name = "DeviceFlowError"; } } exports.DeviceFlowError = DeviceFlowError; function isDeviceFlowErrorResponse(data) { return (typeof data === "object" && data !== null && "error" in data && typeof data.error === "string"); } let DeviceFlowService = DeviceFlowService_1 = class DeviceFlowService { config; client; logger = new common_1.Logger(DeviceFlowService_1.name); deviceEndpoint; tokenEndpoint; constructor(config) { this.config = config; const tunnelUrl = this.config.tunnelServerUrl; const httpUrl = tunnelUrl .replace(/^wss:/, "https:") .replace(/^ws:/, "http:"); this.deviceEndpoint = `${httpUrl}/auth/device`; this.tokenEndpoint = `${httpUrl}/auth/token`; this.client = ky_1.default.create({ timeout: 10000, headers: { "Content-Type": "application/x-www-form-urlencoded", }, retry: { limit: 0, }, hooks: { beforeRequest: [ ({ request }) => { this.logger.debug(`Device Flow request: ${request.method} ${request.url}`); }, ], }, }); } async requestDeviceCode() { try { this.logger.log("Requesting device code from tunnel service"); const response = await this.client .post(this.deviceEndpoint, { body: new URLSearchParams({ client_id: this.config.authClientId, scope: "openid offline_access", }).toString(), }) .json(); this.logger.log("Device code received successfully"); this.logger.debug(`User code: ${response.user_code}`); return response; } catch (error) { throw this.handleError(error, "requestDeviceCode"); } } async pollForToken(deviceCode, interval, expiresIn) { const startTime = Date.now(); const expiresAt = startTime + expiresIn * 1000; let currentInterval = interval * 1000; this.logger.log("Starting token polling"); this.logger.debug(`Polling interval: ${interval}s, expires in: ${expiresIn}s`); while (Date.now() < expiresAt) { await this.sleep(currentInterval); try { const response = await this.client .post(this.tokenEndpoint, { timeout: 5000, body: new URLSearchParams({ grant_type: "urn:ietf:params:oauth:grant-type:device_code", device_code: deviceCode, client_id: this.config.authClientId, }).toString(), }) .json(); this.logger.log("Token received successfully"); return response; } catch (error) { if (error instanceof ky_1.HTTPError && isDeviceFlowErrorResponse(error.data)) { const errorResponse = error.data; switch (errorResponse.error) { case "authorization_pending": this.logger.debug("Authorization pending, continuing poll..."); continue; case "slow_down": currentInterval += 5000; this.logger.debug(`Slow down requested, new interval: ${currentInterval / 1000}s`); continue; case "expired_token": throw new DeviceFlowError("Device code has expired. Please restart the login process.", "expired_token", errorResponse.error_description); case "access_denied": throw new DeviceFlowError("User denied authorization.", "access_denied", errorResponse.error_description); default: throw new DeviceFlowError(`Authorization failed: ${errorResponse.error}`, errorResponse.error, errorResponse.error_description); } } throw this.handleError(error, "pollForToken"); } } throw new DeviceFlowError("Polling timeout: device code expired before user authorization.", "expired_token"); } async refreshToken(refreshToken) { try { this.logger.log("Refreshing access token via tunnel service"); const response = await this.client .post(this.tokenEndpoint, { body: new URLSearchParams({ grant_type: "refresh_token", refresh_token: refreshToken, client_id: this.config.authClientId, }).toString(), }) .json(); this.logger.log("Token refreshed successfully"); return response; } catch (error) { if (error instanceof ky_1.HTTPError && error.response.status === 400 && isDeviceFlowErrorResponse(error.data) && error.data.error === "invalid_grant") { throw new DeviceFlowError("Refresh token is invalid or expired. Please login again.", "invalid_grant", error.data.error_description); } throw this.handleError(error, "refreshToken"); } } sleep(ms) { return new Promise((resolve) => setTimeout(resolve, ms)); } handleError(error, operation) { this.logger.error(`Error in ${operation}:`, error); if (error instanceof ky_1.HTTPError) { const status = error.response.status; if (status === 401 || status === 403) { return new DeviceFlowError("Authentication failed. Invalid client configuration.", "auth_failed"); } if (status >= 500) { return new DeviceFlowError("Tunnel service error. Please try again later.", "server_error"); } return new DeviceFlowError(`HTTP error ${status}: ${error.message}`, "http_error"); } if (error instanceof ky_1.TimeoutError) { return new DeviceFlowError("Request timeout. Please check your network connection.", "timeout"); } if (error instanceof Error) { if (error.message.includes("fetch") || error.message.includes("network")) { return new DeviceFlowError(`Cannot connect to tunnel service (${this.config.tunnelServerUrl}). Please check your internet connection.`, "network_error"); } } if (error instanceof DeviceFlowError) { return error; } return new DeviceFlowError(`Unexpected error during ${operation}: ${error instanceof Error ? error.message : String(error)}`, "unknown_error"); } getConfig() { return { tunnelUrl: this.config.tunnelServerUrl, clientId: this.config.authClientId, }; } }; exports.DeviceFlowService = DeviceFlowService; exports.DeviceFlowService = DeviceFlowService = DeviceFlowService_1 = __decorate([ (0, common_1.Injectable)(), __metadata("design:paramtypes", [app_config_service_1.AppConfigService]) ], DeviceFlowService); //# sourceMappingURL=device-flow.service.js.map