Files
Mr¤KayJayDee a48df5509c feat(webhook): ajout de la fonctionnalité d'envoi de webhooks
- Remplacement de l'action de compteur par une action d'envoi de webhook
- Ajout d'une interface utilisateur pour configurer les paramètres du webhook (URL, méthode, headers, corps)
- Implémentation des services de validation, de construction de requêtes et d'exécution de requêtes
- Ajout de la gestion des erreurs et des messages de validation
- Mise à jour du fichier README avec des instructions d'utilisation et des exemples de configuration
- Ajout de nouveaux icônes pour l'action d'envoi de webhook
2025-07-10 15:15:12 +02:00

333 lines
11 KiB
JavaScript

/**
* 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();
}