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
This commit is contained in:
@@ -1,19 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head lang="en">
|
||||
<title>Increment Counter Settings</title>
|
||||
<meta charset="utf-8" />
|
||||
<script src="https://sdpi-components.dev/releases/v4/sdpi-components.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<!--
|
||||
Learn more about property inspector components at https://sdpi-components.dev/docs/components
|
||||
-->
|
||||
<sdpi-item label="Increment By">
|
||||
<sdpi-range setting="incrementBy" min="1" max="5" step="1" default="1" showlabels></sdpi-range>
|
||||
</sdpi-item>
|
||||
</body>
|
||||
|
||||
</html>
|
137
com.mr-kayjaydee.webhooks-trigger.sdPlugin/ui/send-webhook.html
Normal file
137
com.mr-kayjaydee.webhooks-trigger.sdPlugin/ui/send-webhook.html
Normal file
@@ -0,0 +1,137 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head lang="en">
|
||||
<title>Send Webhook Settings</title>
|
||||
<meta charset="utf-8" />
|
||||
<script src="https://sdpi-components.dev/releases/v4/sdpi-components.js"></script>
|
||||
<style>
|
||||
.validation-error {
|
||||
color: #ff6b6b;
|
||||
font-size: 12px;
|
||||
margin-top: 4px;
|
||||
padding: 4px;
|
||||
background-color: #ffe6e6;
|
||||
border-left: 3px solid #ff6b6b;
|
||||
border-radius: 3px;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.validation-success {
|
||||
color: #51cf66;
|
||||
font-size: 12px;
|
||||
margin-top: 4px;
|
||||
padding: 4px;
|
||||
background-color: #e6ffe6;
|
||||
border-left: 3px solid #51cf66;
|
||||
border-radius: 3px;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.button-group {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.flex-item {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.json-container {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.verify-button-container {
|
||||
margin-top: 16px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.config-status {
|
||||
margin-top: 8px;
|
||||
padding: 8px;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.config-status.success {
|
||||
background-color: #e6ffe6;
|
||||
color: #51cf66;
|
||||
border: 1px solid #51cf66;
|
||||
}
|
||||
|
||||
.config-status.error {
|
||||
background-color: #ffe6e6;
|
||||
color: #ff6b6b;
|
||||
border: 1px solid #ff6b6b;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<!--
|
||||
Learn more about property inspector components at https://sdpi-components.dev/docs/components
|
||||
-->
|
||||
<sdpi-item label="URL">
|
||||
<sdpi-textfield setting="url" placeholder="https://webhook.site/your-unique-url" required></sdpi-textfield>
|
||||
<div id="url-error" class="validation-error"></div>
|
||||
</sdpi-item>
|
||||
|
||||
<sdpi-item label="HTTP Method">
|
||||
<sdpi-select setting="method" default="GET">
|
||||
<option value="GET">GET</option>
|
||||
<option value="POST">POST</option>
|
||||
<option value="PUT">PUT</option>
|
||||
<option value="PATCH">PATCH</option>
|
||||
<option value="DELETE">DELETE</option>
|
||||
</sdpi-select>
|
||||
</sdpi-item>
|
||||
|
||||
<sdpi-item label="Headers (JSON)">
|
||||
<div class="json-container">
|
||||
<sdpi-textarea id="headers-textarea" setting="headers" placeholder='{"Content-Type": "application/json"}'
|
||||
rows="4"></sdpi-textarea>
|
||||
<div class="button-group">
|
||||
<sdpi-button id="beautify-headers-btn" class="flex-item">
|
||||
Beautify JSON
|
||||
</sdpi-button>
|
||||
<sdpi-button id="validate-headers-btn" class="flex-item">
|
||||
Validate JSON
|
||||
</sdpi-button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="headers-error" class="validation-error"></div>
|
||||
<div id="headers-success" class="validation-success"></div>
|
||||
</sdpi-item>
|
||||
|
||||
<sdpi-item label="Body">
|
||||
<div class="json-container">
|
||||
<sdpi-textarea id="body-textarea" setting="body" placeholder='{"message": "Hello from Stream Deck!"}'
|
||||
rows="6"></sdpi-textarea>
|
||||
<div class="button-group">
|
||||
<sdpi-button id="beautify-body-btn" class="flex-item">
|
||||
Beautify JSON
|
||||
</sdpi-button>
|
||||
<sdpi-button id="validate-body-btn" class="flex-item">
|
||||
Validate JSON
|
||||
</sdpi-button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="body-error" class="validation-error"></div>
|
||||
<div id="body-success" class="validation-success"></div>
|
||||
</sdpi-item>
|
||||
|
||||
<div class="verify-button-container">
|
||||
<sdpi-item>
|
||||
<sdpi-button id="verify-config-btn">
|
||||
Verify Configuration
|
||||
</sdpi-button>
|
||||
</sdpi-item>
|
||||
<div id="config-status" class="config-status"></div>
|
||||
</div>
|
||||
|
||||
<script src="send-webhook.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
333
com.mr-kayjaydee.webhooks-trigger.sdPlugin/ui/send-webhook.js
Normal file
333
com.mr-kayjaydee.webhooks-trigger.sdPlugin/ui/send-webhook.js
Normal file
@@ -0,0 +1,333 @@
|
||||
/**
|
||||
* 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();
|
||||
}
|
Reference in New Issue
Block a user