UNPKG

@ankimcp/anki-mcp-server

Version:

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

126 lines • 4.7 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.NgrokService = void 0; const child_process_1 = require("child_process"); const http_1 = __importDefault(require("http")); const common_1 = require("@nestjs/common"); class NgrokService { logger = new common_1.Logger(NgrokService.name); process = null; cleanupHandlersRegistered = false; async start(port) { if (!(await this.isNgrokInstalled())) { throw new Error("ngrok is not installed.\n" + "Install it with: npm install -g ngrok\n" + "Or download from: https://ngrok.com/download\n" + "Then setup auth: ngrok config add-authtoken <your-token>"); } this.process = (0, child_process_1.spawn)("ngrok", ["http", "--host-header=rewrite", port.toString()], { stdio: "pipe", }); if (!this.cleanupHandlersRegistered) { this.registerCleanupHandlers(); this.cleanupHandlersRegistered = true; } this.process.on("error", (err) => { throw new Error(`Failed to start ngrok: ${err.message}`); }); this.process.on("exit", (code, _signal) => { if (code !== 0 && code !== null) { this.logger.error(`ngrok exited with code ${code}`); } }); await this.waitForNgrok(); const publicUrl = await this.getTunnelUrl(); return { publicUrl, apiUrl: "http://localhost:4040", }; } async isNgrokInstalled() { return new Promise((resolve) => { const isWindows = process.platform === "win32"; const command = isWindows ? "where" : "which"; const check = (0, child_process_1.spawn)(command, ["ngrok"], { shell: isWindows, }); check.on("close", (code) => resolve(code === 0)); check.on("error", () => resolve(false)); }); } async waitForNgrok(maxAttempts = 20, delayMs = 500) { for (let i = 0; i < maxAttempts; i++) { await new Promise((resolve) => setTimeout(resolve, delayMs)); try { await this.getTunnelUrl(); return; } catch { } } throw new Error("ngrok failed to start in time.\n" + "Make sure you have configured your auth token:\n" + "ngrok config add-authtoken <your-token>"); } async getTunnelUrl() { return new Promise((resolve, reject) => { http_1.default .get("http://localhost:4040/api/tunnels", (res) => { let data = ""; res.on("data", (chunk) => (data += chunk)); res.on("end", () => { try { const response = JSON.parse(data); const tunnel = response.tunnels?.find((t) => t.proto === "https"); const publicUrl = tunnel?.public_url; if (publicUrl) { resolve(publicUrl); } else { reject(new Error("No HTTPS tunnel found in ngrok API response")); } } catch (err) { reject(new Error(`Failed to parse ngrok API response: ${err}`)); } }); }) .on("error", (err) => { reject(new Error(`Failed to connect to ngrok API: ${err.message}`)); }); }); } registerCleanupHandlers() { process.on("SIGINT", () => { this.logger.log("\n\nšŸ›‘ Shutting down ngrok tunnel..."); this.cleanup(); process.exit(0); }); process.on("SIGTERM", () => { this.cleanup(); process.exit(0); }); process.on("exit", () => { this.cleanup(); }); process.on("uncaughtException", (err) => { this.logger.error("Uncaught exception:", err); this.cleanup(); process.exit(1); }); } cleanup() { if (this.process && !this.process.killed) { this.process.kill("SIGTERM"); this.process = null; } } getWebInterfaceUrl() { return "http://localhost:4040"; } } exports.NgrokService = NgrokService; //# sourceMappingURL=ngrok.service.js.map