/** * Stream Deck Property Inspector JavaScript for Send Webhook Action * Simple and focused - fixes timeout and duplicate alert issues */ // Store timeout IDs to clear them properly const validationTimeouts = {}; // Utility function to show validation messages with proper timeout handling function showValidationMessage(elementId, message, isError = true, duration = 5000) { const element = document.getElementById(elementId); if (!element) return; // Clear existing timeout for this element if (validationTimeouts[elementId]) { clearTimeout(validationTimeouts[elementId]); delete validationTimeouts[elementId]; } element.textContent = message; element.style.display = message ? 'block' : 'none'; // Set new timeout if duration > 0 if (duration > 0 && message) { validationTimeouts[elementId] = setTimeout(() => { element.style.display = 'none'; delete validationTimeouts[elementId]; }, duration); } } // Hide validation message immediately function hideValidationMessage(elementId) { showValidationMessage(elementId, '', true, 0); } // JSON validation function function validateJSON(jsonString, fieldName) { if (!jsonString || jsonString.trim() === '') { return { isValid: true, message: `${fieldName} is empty (optional)` }; } try { const parsed = JSON.parse(jsonString); // Additional validation for headers - must be an object if (fieldName === 'Headers' && (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed))) { return { isValid: false, message: 'Headers must be a JSON object (e.g., {"Content-Type": "application/json"})' }; } return { isValid: true, message: `Valid JSON`, data: parsed }; } catch (error) { return { isValid: false, message: `Invalid JSON: ${error.message}` }; } } // JSON beautification function function beautifyJSON(jsonString) { if (!jsonString || jsonString.trim() === '') { return { success: false, message: 'No content to beautify' }; } try { const parsed = JSON.parse(jsonString); const beautified = JSON.stringify(parsed, null, 2); return { success: true, beautified }; } catch (error) { return { success: false, message: `Cannot beautify: ${error.message}` }; } } // URL validation function function validateURL(urlString) { if (!urlString || urlString.trim() === '') { return { isValid: false, message: 'URL is required' }; } try { const url = new URL(urlString.trim()); // Check for supported protocols if (!['http:', 'https:'].includes(url.protocol)) { return { isValid: false, message: 'URL must use HTTP or HTTPS protocol' }; } return { isValid: true, message: 'Valid URL' }; } catch (error) { return { isValid: false, message: 'Invalid URL format' }; } } // Get current settings from the form function getCurrentSettings() { const urlField = document.querySelector('sdpi-textfield[setting="url"]'); const methodField = document.querySelector('sdpi-select[setting="method"]'); const headersField = document.querySelector('sdpi-textarea[setting="headers"]'); const bodyField = document.querySelector('sdpi-textarea[setting="body"]'); return { url: urlField ? urlField.value : '', method: methodField ? methodField.value : 'GET', headers: headersField ? headersField.value : '', body: bodyField ? bodyField.value : '' }; } // Handle field validation - shows only one alert at a time function handleFieldValidation(fieldName, displayName) { const field = document.querySelector(`sdpi-textarea[setting="${fieldName}"]`); if (!field) return; const validation = validateJSON(field.value, displayName); const errorId = `${fieldName}-error`; const successId = `${fieldName}-success`; if (validation.isValid) { showValidationMessage(successId, validation.message, false); hideValidationMessage(errorId); } else { showValidationMessage(errorId, validation.message, true); hideValidationMessage(successId); } } // Handle JSON beautification function handleJsonBeautify(fieldName) { const field = document.querySelector(`sdpi-textarea[setting="${fieldName}"]`); if (!field) return; const result = beautifyJSON(field.value); const errorId = `${fieldName}-error`; const successId = `${fieldName}-success`; if (result.success) { field.value = result.beautified; // Trigger change event to save settings field.dispatchEvent(new Event('change', { bubbles: true })); showValidationMessage(successId, 'JSON beautified successfully!', false); hideValidationMessage(errorId); } else { showValidationMessage(errorId, result.message, true); hideValidationMessage(successId); } } // Comprehensive configuration verification function verifyConfiguration() { const settings = getCurrentSettings(); const errors = []; const warnings = []; // Validate URL const urlValidation = validateURL(settings.url); if (!urlValidation.isValid) { errors.push(`URL: ${urlValidation.message}`); } // Validate Headers JSON const headersValidation = validateJSON(settings.headers, 'Headers'); if (!headersValidation.isValid) { errors.push(`Headers: ${headersValidation.message}`); } // Validate Body JSON (only if not empty) if (settings.body && settings.body.trim() !== '') { const bodyValidation = validateJSON(settings.body, 'Body'); if (!bodyValidation.isValid) { errors.push(`Body: ${bodyValidation.message}`); } } // Check method-specific requirements const methodsWithBody = ['POST', 'PUT', 'PATCH']; if (methodsWithBody.includes(settings.method)) { if (!settings.body || settings.body.trim() === '') { warnings.push(`${settings.method} requests typically include a body`); } } else if (settings.method === 'GET' && settings.body && settings.body.trim() !== '') { warnings.push('GET requests typically do not include a body'); } // Check for common header requirements if (settings.body && settings.body.trim() !== '') { try { const headers = settings.headers ? JSON.parse(settings.headers) : {}; if (!headers['Content-Type'] && !headers['content-type']) { warnings.push('Consider adding Content-Type header when sending a body'); } } catch (e) { // Headers validation already handled above } } showConfigurationResults(errors, warnings); } // Show configuration verification results function showConfigurationResults(errors, warnings) { const statusElement = document.getElementById('config-status'); if (!statusElement) return; let message = ''; let className = ''; if (errors.length > 0) { className = 'error'; message = `Configuration Issues:\n${errors.join('\n')}`; if (warnings.length > 0) { message += `\n\nWarnings:\n${warnings.join('\n')}`; } } else if (warnings.length > 0) { className = 'error'; // Show warnings as yellow/orange message = `Configuration Warnings:\n${warnings.join('\n')}`; } else { className = 'success'; message = '✓ Configuration is valid and ready to use!'; } statusElement.className = `config-status ${className}`; statusElement.textContent = message; statusElement.style.display = 'block'; // Hide after 10 seconds for success, keep visible for errors if (className === 'success') { setTimeout(() => { statusElement.style.display = 'none'; }, 10000); } } // Event handlers setup function setupEventHandlers() { // Headers validation button const validateHeadersBtn = document.getElementById('validate-headers-btn'); if (validateHeadersBtn) { validateHeadersBtn.addEventListener('click', () => { handleFieldValidation('headers', 'Headers'); }); } // Headers beautify button const beautifyHeadersBtn = document.getElementById('beautify-headers-btn'); if (beautifyHeadersBtn) { beautifyHeadersBtn.addEventListener('click', () => { handleJsonBeautify('headers'); }); } // Body validation button const validateBodyBtn = document.getElementById('validate-body-btn'); if (validateBodyBtn) { validateBodyBtn.addEventListener('click', () => { handleFieldValidation('body', 'Body'); }); } // Body beautify button const beautifyBodyBtn = document.getElementById('beautify-body-btn'); if (beautifyBodyBtn) { beautifyBodyBtn.addEventListener('click', () => { handleJsonBeautify('body'); }); } // Configuration verification button const verifyConfigBtn = document.getElementById('verify-config-btn'); if (verifyConfigBtn) { verifyConfigBtn.addEventListener('click', verifyConfiguration); } // Auto-validate URL on change const urlField = document.querySelector('sdpi-textfield[setting="url"]'); if (urlField) { urlField.addEventListener('change', () => { const validation = validateURL(urlField.value); if (!validation.isValid && urlField.value.trim() !== '') { showValidationMessage('url-error', validation.message, true, 3000); } else { hideValidationMessage('url-error'); } }); } // Auto-validate JSON fields on change const headersField = document.querySelector('sdpi-textarea[setting="headers"]'); if (headersField) { headersField.addEventListener('change', () => { if (headersField.value.trim() !== '') { const validation = validateJSON(headersField.value, 'Headers'); if (!validation.isValid) { showValidationMessage('headers-error', validation.message, true, 3000); hideValidationMessage('headers-success'); } else { hideValidationMessage('headers-error'); } } }); } const bodyField = document.querySelector('sdpi-textarea[setting="body"]'); if (bodyField) { bodyField.addEventListener('change', () => { if (bodyField.value.trim() !== '') { const validation = validateJSON(bodyField.value, 'Body'); if (!validation.isValid) { showValidationMessage('body-error', validation.message, true, 3000); hideValidationMessage('body-success'); } else { hideValidationMessage('body-error'); } } }); } } // Initialize when DOM is loaded if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', setupEventHandlers); } else { setupEventHandlers(); }