maplestorysea-mcp-server
Version:
NEXON MapleStory SEA API MCP Server for Claude Desktop - Complete character info, union details, guild data, rankings optimized for SEA servers
363 lines • 13.5 kB
JavaScript
;
/**
* Health check system for monitoring application status
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.defaultHealthManager = exports.HealthCheckManager = exports.ProcessHealthChecker = exports.MemoryHealthChecker = exports.CacheHealthChecker = exports.ApiHealthChecker = exports.HealthChecker = void 0;
exports.createDefaultHealthManager = createDefaultHealthManager;
const logger_1 = require("./logger");
const cache_1 = require("./cache");
class HealthChecker {
}
exports.HealthChecker = HealthChecker;
class ApiHealthChecker extends HealthChecker {
apiClient;
name = 'nexon-api';
constructor(apiClient) {
super();
this.apiClient = apiClient;
}
async check(options = {}) {
const startTime = Date.now();
const timeout = options.timeout || 5000;
// Create a timeout promise with cleanup
let timeoutId;
const timeoutPromise = new Promise((_, reject) => {
timeoutId = setTimeout(() => reject(new Error('Health check timeout')), timeout);
});
try {
// Check API client basic configuration
// This validates the client is properly initialized
if (!this.apiClient) {
throw new Error('NEXON API client is not initialized');
}
// Check environment variable for API key presence
const envApiKey = process.env.NEXON_API_KEY;
if (!envApiKey) {
throw new Error('NEXON_API_KEY environment variable is not set');
}
if (envApiKey.length < 50) {
throw new Error('NEXON API key appears to be invalid (too short)');
}
// Simple timeout to simulate async check
await new Promise(resolve => setTimeout(resolve, 50));
// Clear the timeout if the API call succeeded
if (timeoutId) {
clearTimeout(timeoutId);
}
const responseTime = Date.now() - startTime;
return {
status: 'healthy',
lastCheck: new Date().toISOString(),
responseTime,
details: {
endpoint: 'configuration_check',
apiKeyPresent: true,
timeout,
},
};
}
catch (error) {
// Clear the timeout if it exists
if (timeoutId) {
clearTimeout(timeoutId);
}
const responseTime = Date.now() - startTime;
let errorMessage;
if (error instanceof Error) {
errorMessage = error.message;
// Include more context for NEXON API errors
if ('code' in error || 'statusCode' in error) {
const extra = [];
if ('statusCode' in error)
extra.push(`Status: ${error.statusCode}`);
if ('code' in error)
extra.push(`Code: ${error.code}`);
if (extra.length > 0) {
errorMessage += ` (${extra.join(', ')})`;
}
}
}
else if (error && typeof error === 'object') {
// Handle structured error objects
if ('error' in error) {
const nestedError = error.error;
if (nestedError && typeof nestedError === 'object') {
errorMessage = nestedError.message || nestedError.name || 'API Error';
if (nestedError.name) {
errorMessage = `${nestedError.name}: ${nestedError.message || 'Unknown error'}`;
}
}
else {
errorMessage = String(nestedError);
}
}
else {
errorMessage = JSON.stringify(error);
}
}
else {
errorMessage = String(error);
}
return {
status: 'unhealthy',
lastCheck: new Date().toISOString(),
responseTime,
error: errorMessage,
details: {
endpoint: 'configuration_check',
apiKeyPresent: true,
timeout,
},
};
}
}
}
exports.ApiHealthChecker = ApiHealthChecker;
class CacheHealthChecker extends HealthChecker {
cache;
name = 'cache';
constructor(cache) {
super();
this.cache = cache;
}
async check() {
const startTime = Date.now();
try {
// Test cache operations
const testKey = 'health-check-test';
const testValue = { timestamp: Date.now() };
// Test set operation
this.cache.set(testKey, testValue, 1000);
// Test get operation
const retrieved = this.cache.get(testKey);
if (!retrieved || retrieved.timestamp !== testValue.timestamp) {
throw new Error('Cache read/write verification failed');
}
// Test delete operation
this.cache.delete(testKey);
const afterDelete = this.cache.get(testKey);
if (afterDelete !== null) {
throw new Error('Cache delete verification failed');
}
const responseTime = Date.now() - startTime;
return {
status: 'healthy',
lastCheck: new Date().toISOString(),
responseTime,
details: {
operations: ['set', 'get', 'delete'],
cacheSize: this.cache.size(),
},
};
}
catch (error) {
const responseTime = Date.now() - startTime;
return {
status: 'unhealthy',
lastCheck: new Date().toISOString(),
responseTime,
error: error instanceof Error ? error.message : String(error),
};
}
}
}
exports.CacheHealthChecker = CacheHealthChecker;
class MemoryHealthChecker extends HealthChecker {
name = 'memory';
async check() {
const startTime = Date.now();
try {
const memUsage = process.memoryUsage();
const responseTime = Date.now() - startTime;
// Convert bytes to MB
const memoryMB = {
rss: Math.round(memUsage.rss / 1024 / 1024),
heapTotal: Math.round(memUsage.heapTotal / 1024 / 1024),
heapUsed: Math.round(memUsage.heapUsed / 1024 / 1024),
external: Math.round(memUsage.external / 1024 / 1024),
};
// Determine status based on memory usage
const heapUsagePercent = (memUsage.heapUsed / memUsage.heapTotal) * 100;
let status = 'healthy';
if (heapUsagePercent > 90) {
status = 'unhealthy';
}
else if (heapUsagePercent > 80) {
status = 'degraded';
}
return {
status,
lastCheck: new Date().toISOString(),
responseTime,
details: {
memory: memoryMB,
heapUsagePercent: Math.round(heapUsagePercent),
},
};
}
catch (error) {
const responseTime = Date.now() - startTime;
return {
status: 'unhealthy',
lastCheck: new Date().toISOString(),
responseTime,
error: error instanceof Error ? error.message : String(error),
};
}
}
}
exports.MemoryHealthChecker = MemoryHealthChecker;
class ProcessHealthChecker extends HealthChecker {
name = 'process';
startTime = Date.now();
async check() {
const checkStartTime = Date.now();
try {
const uptime = process.uptime();
const responseTime = Date.now() - checkStartTime;
return {
status: 'healthy',
lastCheck: new Date().toISOString(),
responseTime,
details: {
uptime: Math.round(uptime),
uptimeFormatted: this.formatUptime(uptime),
pid: process.pid,
nodeVersion: process.version,
platform: process.platform,
arch: process.arch,
},
};
}
catch (error) {
const responseTime = Date.now() - checkStartTime;
return {
status: 'unhealthy',
lastCheck: new Date().toISOString(),
responseTime,
error: error instanceof Error ? error.message : String(error),
};
}
}
formatUptime(seconds) {
const days = Math.floor(seconds / 86400);
const hours = Math.floor((seconds % 86400) / 3600);
const minutes = Math.floor((seconds % 3600) / 60);
const secs = Math.floor(seconds % 60);
return `${days}d ${hours}h ${minutes}m ${secs}s`;
}
}
exports.ProcessHealthChecker = ProcessHealthChecker;
class HealthCheckManager {
checkers = new Map();
logger;
lastCheck;
constructor(logger = logger_1.defaultLogger) {
this.logger = logger;
}
registerChecker(checker) {
this.checkers.set(checker.name, checker);
this.logger.debug(`Registered health checker: ${checker.name}`);
}
async runHealthChecks(options = {}) {
const startTime = Date.now();
const details = {};
// Run all health checks in parallel
const checkPromises = Array.from(this.checkers.entries()).map(async ([name, checker]) => {
try {
const result = await checker.check(options);
details[name] = result;
this.logger.logHealthCheck(name, result.status, {
responseTime: result.responseTime,
error: result.error,
});
return result;
}
catch (error) {
const errorResult = {
status: 'unhealthy',
lastCheck: new Date().toISOString(),
error: error instanceof Error ? error.message : String(error),
};
details[name] = errorResult;
this.logger.logHealthCheck(name, 'unhealthy', { error: errorResult.error });
return errorResult;
}
});
await Promise.all(checkPromises);
// Determine overall status
const statuses = Object.values(details).map((d) => d.status);
let overallStatus = 'healthy';
if (statuses.includes('unhealthy')) {
overallStatus = 'unhealthy';
}
else if (statuses.includes('degraded')) {
overallStatus = 'degraded';
}
const healthStatus = {
status: overallStatus,
timestamp: new Date().toISOString(),
uptime: process.uptime(),
version: process.env.npm_package_version || '1.0.0',
details,
};
this.lastCheck = healthStatus;
const checkDuration = Date.now() - startTime;
this.logger.info(`Health check completed in ${checkDuration}ms`, {
operation: 'health_check_summary',
overallStatus,
checkDuration,
checkerCount: this.checkers.size,
});
return healthStatus;
}
getLastCheck() {
return this.lastCheck;
}
async getQuickStatus() {
return {
status: this.lastCheck?.status || 'unknown',
uptime: process.uptime(),
timestamp: new Date().toISOString(),
};
}
// Start periodic health checks
startPeriodicChecks(interval = 60000) {
// Default 1 minute
this.logger.info(`Starting periodic health checks every ${interval}ms`);
return setInterval(async () => {
try {
await this.runHealthChecks({ timeout: 5000 });
}
catch (error) {
this.logger.error('Periodic health check failed', {
error: error instanceof Error ? error.message : String(error),
});
}
}, interval);
}
}
exports.HealthCheckManager = HealthCheckManager;
// Create default health check manager with all checkers
function createDefaultHealthManager(apiClient, cache) {
const manager = new HealthCheckManager();
// Always add process and memory checkers
manager.registerChecker(new ProcessHealthChecker());
manager.registerChecker(new MemoryHealthChecker());
// Add cache checker if available
if (cache) {
manager.registerChecker(new CacheHealthChecker(cache));
}
else {
manager.registerChecker(new CacheHealthChecker(cache_1.defaultCache));
}
// Add API checker if available
if (apiClient) {
manager.registerChecker(new ApiHealthChecker(apiClient));
}
return manager;
}
// Export default health manager
exports.defaultHealthManager = createDefaultHealthManager();
//# sourceMappingURL=health-check.js.map