Skip to main content
Glama
api-save-debugger.js13.8 kB
#!/usr/bin/env node /** * API Save Debugger - Targeted debugging for HTTP 500 errors * Focuses on actual root causes rather than assumptions */ export class APISaveDebugger { constructor() { this.debugLog = []; } /** * Comprehensive debugging of API save failures */ async debugAPISaveFailure(composerJSON, page, originalError) { console.error('[API_SAVE_DEBUGGER] Starting comprehensive debugging'); const analysis = { timestamp: new Date().toISOString(), originalError: originalError, checks: {}, recommendations: [] }; // 1. JSON Structure Validation analysis.checks.jsonStructure = await this.validateJSONStructure(composerJSON); // 2. Header Completeness Check (your past issue) analysis.checks.headerCompleteness = this.checkHeaderCompleteness(composerJSON); // 3. Content Encoding Issues analysis.checks.contentEncoding = this.checkContentEncoding(composerJSON); // 4. API Response Analysis analysis.checks.apiResponse = await this.analyzeAPIResponse(originalError); // 5. Authentication State analysis.checks.authentication = await this.checkAuthenticationState(page); // 6. FormData Structure analysis.checks.formData = this.validateFormDataStructure(composerJSON); // 7. Size vs Content Analysis analysis.checks.sizeAnalysis = this.analyzeSizeVsContent(composerJSON); // Generate actionable recommendations analysis.recommendations = this.generateRecommendations(analysis.checks); return analysis; } /** * 1. Validate JSON structure integrity */ async validateJSONStructure(composerJSON) { const check = { name: 'JSON Structure Validation', passed: true, issues: [], details: {} }; try { // Test JSON serialization/deserialization const serialized = JSON.stringify(composerJSON); const parsed = JSON.parse(serialized); check.details.serializationSize = serialized.length; check.details.canSerialize = true; check.details.canDeserialize = true; // Check for circular references try { JSON.stringify(composerJSON); } catch (error) { if (error.message.includes('circular')) { check.passed = false; check.issues.push('Circular reference detected in JSON structure'); } } // Check for undefined values const hasUndefined = serialized.includes('undefined'); if (hasUndefined) { check.passed = false; check.issues.push('Undefined values found in JSON'); } // Check for NaN values const hasNaN = serialized.includes('NaN'); if (hasNaN) { check.passed = false; check.issues.push('NaN values found in JSON'); } check.details.structureKeys = Object.keys(composerJSON); check.details.hasMetadata = !!composerJSON.metadata; check.details.hasStructure = !!composerJSON.structure; check.details.widgetCount = composerJSON.structure?.length || 0; } catch (error) { check.passed = false; check.issues.push(`JSON serialization failed: ${error.message}`); } return check; } /** * 2. Check header completeness (your past issue) */ checkHeaderCompleteness(composerJSON) { const check = { name: 'Header Completeness Check', passed: true, issues: [], details: {} }; // Check for required header fields const requiredFields = ['name', 'description', 'keywords', 'category', 'duration']; const metadata = composerJSON.metadata || {}; check.details.metadataPresent = !!composerJSON.metadata; check.details.metadataKeys = Object.keys(metadata); requiredFields.forEach(field => { if (!metadata[field]) { check.passed = false; check.issues.push(`Missing required metadata field: ${field}`); } }); // Check for incomplete header widget const headerWidget = composerJSON.structure?.find(w => w.type === 'head-1'); if (headerWidget) { check.details.hasHeaderWidget = true; check.details.headerContent = headerWidget; const requiredHeaderFields = ['category', 'author_name', 'author_office']; requiredHeaderFields.forEach(field => { if (!headerWidget[field]) { check.passed = false; check.issues.push(`Missing required header field: ${field}`); } }); } else { check.passed = false; check.issues.push('No header widget (head-1) found'); } return check; } /** * 3. Check content encoding issues */ checkContentEncoding(composerJSON) { const check = { name: 'Content Encoding Check', passed: true, issues: [], details: {} }; const problematicChars = []; const serialized = JSON.stringify(composerJSON); // Check for problematic characters const problematicPatterns = [ { pattern: /[\x00-\x1F]/, name: 'control characters' }, { pattern: /[\uFFFE\uFFFF]/, name: 'invalid Unicode' }, { pattern: /\uFEFF/, name: 'byte order mark' }, { pattern: /[\u0000-\u0008\u000B\u000C\u000E-\u001F]/, name: 'null/control chars' } ]; problematicPatterns.forEach(({ pattern, name }) => { if (pattern.test(serialized)) { check.passed = false; check.issues.push(`Found ${name} in content`); problematicChars.push(name); } }); // Check for truncated content (your reported issue) const widgets = composerJSON.structure || []; widgets.forEach((widget, index) => { if (widget.type === 'text-1' && widget.text) { // Check for incomplete HTML tags const openTags = (widget.text.match(/</g) || []).length; const closeTags = (widget.text.match(/>/g) || []).length; if (openTags !== closeTags) { check.passed = false; check.issues.push(`Widget ${index} has incomplete HTML tags`); } // Check for truncated sentences (more accurate) if (widget.text.includes('<p>') && !widget.text.endsWith('</p>') && !widget.text.endsWith('</ul>') && !widget.text.endsWith('</ol>') && !widget.text.endsWith('</div>') && !widget.text.endsWith('</h1>') && !widget.text.endsWith('</h2>') && !widget.text.endsWith('</h3>')) { check.passed = false; check.issues.push(`Widget ${index} may have truncated content`); } } }); check.details.totalSize = serialized.length; check.details.problematicChars = problematicChars; check.details.encoding = 'UTF-8'; return check; } /** * 4. Analyze API response details */ async analyzeAPIResponse(originalError) { const check = { name: 'API Response Analysis', passed: false, issues: [], details: {} }; if (originalError.status) { check.details.httpStatus = originalError.status; check.details.statusText = originalError.statusText; check.details.responseText = originalError.responseText; // Analyze HTML error response if (originalError.responseText && originalError.responseText.includes('<!DOCTYPE html>')) { check.issues.push('Server returned HTML error page instead of JSON'); // Try to extract error details from HTML const htmlContent = originalError.responseText; const titleMatch = htmlContent.match(/<title>([^<]+)<\/title>/i); const errorMatch = htmlContent.match(/<h1[^>]*>([^<]+)<\/h1>/i); if (titleMatch) { check.details.htmlTitle = titleMatch[1]; } if (errorMatch) { check.details.htmlError = errorMatch[1]; } } // Check for specific error patterns if (originalError.status === 500) { check.issues.push('HTTP 500 indicates server processing error, not client request issue'); } if (originalError.status === 413) { check.issues.push('HTTP 413 indicates payload too large'); } if (originalError.status === 400) { check.issues.push('HTTP 400 indicates malformed request'); } } return check; } /** * 5. Check authentication state */ async checkAuthenticationState(page) { const check = { name: 'Authentication State Check', passed: true, issues: [], details: {} }; try { const authData = await page.evaluate(() => { const activeProject = localStorage.getItem('rdp-composer-active-project'); const userData = localStorage.getItem('rdp-composer-user-data'); return { hasActiveProject: !!activeProject, hasUserData: !!userData, projectDataLength: activeProject?.length || 0, userDataLength: userData?.length || 0, currentURL: window.location.href }; }); check.details = authData; if (!authData.hasActiveProject) { check.passed = false; check.issues.push('Missing active project data in localStorage'); } if (!authData.hasUserData) { check.passed = false; check.issues.push('Missing user data in localStorage'); } } catch (error) { check.passed = false; check.issues.push(`Authentication check failed: ${error.message}`); } return check; } /** * 6. Validate FormData structure */ validateFormDataStructure(composerJSON) { const check = { name: 'FormData Structure Check', passed: true, issues: [], details: {} }; try { // Simulate FormData creation const blob = new Blob([JSON.stringify(composerJSON)], { type: 'application/json' }); const formData = new FormData(); formData.append('file', blob, 'test.rdpcomposer'); check.details.blobSize = blob.size; check.details.blobType = blob.type; check.details.formDataCreated = true; // Check filename validity const filename = `composition_${Date.now()}_test.rdpcomposer`; const invalidChars = /[<>:"/\\|?*]/.test(filename); if (invalidChars) { check.passed = false; check.issues.push('Filename contains invalid characters'); } } catch (error) { check.passed = false; check.issues.push(`FormData creation failed: ${error.message}`); } return check; } /** * 7. Size vs Content Analysis */ analyzeSizeVsContent(composerJSON) { const check = { name: 'Size vs Content Analysis', passed: true, issues: [], details: {} }; const serialized = JSON.stringify(composerJSON); const sizeKB = (serialized.length / 1024).toFixed(2); const widgetCount = composerJSON.structure?.length || 0; check.details.totalSize = serialized.length; check.details.sizeKB = sizeKB; check.details.widgetCount = widgetCount; check.details.averageWidgetSize = widgetCount > 0 ? Math.round(serialized.length / widgetCount) : 0; // Analyze size distribution const widgets = composerJSON.structure || []; const widgetSizes = widgets.map((widget, index) => ({ index, type: widget.type, size: JSON.stringify(widget).length })); widgetSizes.sort((a, b) => b.size - a.size); check.details.largestWidgets = widgetSizes.slice(0, 3); // Check for unusually large widgets const maxReasonableSize = 5000; // 5KB per widget const largeWidgets = widgetSizes.filter(w => w.size > maxReasonableSize); if (largeWidgets.length > 0) { check.issues.push(`${largeWidgets.length} widgets exceed reasonable size (>5KB)`); check.details.largeWidgets = largeWidgets; } return check; } /** * Generate actionable recommendations */ generateRecommendations(checks) { const recommendations = []; // JSON Structure issues if (!checks.jsonStructure.passed) { recommendations.push({ priority: 'HIGH', category: 'JSON Structure', action: 'Fix JSON serialization issues before API call', details: checks.jsonStructure.issues }); } // Header issues (your past problem) if (!checks.headerCompleteness.passed) { recommendations.push({ priority: 'HIGH', category: 'Header Completeness', action: 'Ensure all required header fields are present', details: checks.headerCompleteness.issues }); } // Content encoding issues if (!checks.contentEncoding.passed) { recommendations.push({ priority: 'MEDIUM', category: 'Content Encoding', action: 'Clean content encoding before API submission', details: checks.contentEncoding.issues }); } // API response analysis if (checks.apiResponse.details.httpStatus === 500) { recommendations.push({ priority: 'HIGH', category: 'Server Error', action: 'Server processing error - check content validity rather than size', details: ['Focus on malformed content, not payload size'] }); } // Authentication issues if (!checks.authentication.passed) { recommendations.push({ priority: 'HIGH', category: 'Authentication', action: 'Resolve authentication issues before retry', details: checks.authentication.issues }); } return recommendations; } } export function createAPISaveDebugger() { return new APISaveDebugger(); }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/rkm097git/euconquisto-composer-mcp-poc'

If you have feedback or need assistance with the MCP directory API, please join our Discord server