Improve error handling in server and client

This commit is contained in:
Danijel Micic
2025-12-15 11:06:14 +11:00
parent 52365a9470
commit 6b30e3432e
2 changed files with 174 additions and 143 deletions

View File

@@ -4,7 +4,7 @@ function togglePassword() {
passInput.setAttribute('type', type); passInput.setAttribute('type', type);
} }
document.getElementById('smtpForm').addEventListener('submit', async function(e) { document.getElementById('smtpForm').addEventListener('submit', async function (e) {
e.preventDefault(); e.preventDefault();
const btn = document.getElementById('testBtn'); const btn = document.getElementById('testBtn');
@@ -19,7 +19,7 @@ document.getElementById('smtpForm').addEventListener('submit', async function(e)
statusDiv.className = 'status-message'; statusDiv.className = 'status-message';
statusDiv.textContent = ''; statusDiv.textContent = '';
logOutput.textContent = ''; logOutput.textContent = '';
// Set Loading State // Set Loading State
btn.disabled = true; btn.disabled = true;
spinner.classList.remove('hidden'); spinner.classList.remove('hidden');
@@ -37,6 +37,17 @@ document.getElementById('smtpForm').addEventListener('submit', async function(e)
}, },
body: JSON.stringify(data), body: JSON.stringify(data),
}); });
if (!response.ok) {
// Handle non-200 responses
const contentType = response.headers.get("content-type");
if (contentType && contentType.includes("application/json")) {
const errorResult = await response.json();
throw new Error(errorResult.message || errorResult.error || "Server Error");
} else {
const text = await response.text();
throw new Error(`Server returned ${response.status}: ${text.substring(0, 100)}...`);
}
}
const result = await response.json(); const result = await response.json();
@@ -48,6 +59,7 @@ document.getElementById('smtpForm').addEventListener('submit', async function(e)
statusDiv.textContent = '✅ Success! Email Sent Successfully.'; statusDiv.textContent = '✅ Success! Email Sent Successfully.';
logOutput.textContent = JSON.stringify(result.details, null, 2); logOutput.textContent = JSON.stringify(result.details, null, 2);
} else { } else {
// This block handles cases where response was 200 OK but success is false (business logic error)
statusDiv.classList.add('status-error'); statusDiv.classList.add('status-error');
statusDiv.textContent = '❌ Error: ' + result.message; statusDiv.textContent = '❌ Error: ' + result.message;
logOutput.textContent = result.error || 'Unknown error occurred.'; logOutput.textContent = result.error || 'Unknown error occurred.';
@@ -56,8 +68,8 @@ document.getElementById('smtpForm').addEventListener('submit', async function(e)
} catch (error) { } catch (error) {
resultsDiv.classList.remove('hidden'); resultsDiv.classList.remove('hidden');
statusDiv.classList.add('status-error'); statusDiv.classList.add('status-error');
statusDiv.textContent = '❌ Network Error'; statusDiv.textContent = '❌ Error Caught'; // Changed from "Network Error" to be more accurate
logOutput.textContent = error.toString(); logOutput.textContent = error.message; // Use error.message instead of error.toString()
} finally { } finally {
// Reset Button // Reset Button
btn.disabled = false; btn.disabled = false;
@@ -67,7 +79,7 @@ document.getElementById('smtpForm').addEventListener('submit', async function(e)
}); });
// Auto Discovery Test Handler // Auto Discovery Test Handler
document.getElementById('autoTestBtn').addEventListener('click', async function() { document.getElementById('autoTestBtn').addEventListener('click', async function () {
const btn = document.getElementById('autoTestBtn'); const btn = document.getElementById('autoTestBtn');
const spinner = btn.querySelector('.loading-spinner'); const spinner = btn.querySelector('.loading-spinner');
const btnText = btn.querySelector('.btn-text'); const btnText = btn.querySelector('.btn-text');
@@ -94,7 +106,7 @@ document.getElementById('autoTestBtn').addEventListener('click', async function(
statusDiv.className = 'status-message'; statusDiv.className = 'status-message';
statusDiv.textContent = ''; statusDiv.textContent = '';
logOutput.textContent = ''; logOutput.textContent = '';
// Set Loading State // Set Loading State
btn.disabled = true; btn.disabled = true;
spinner.classList.remove('hidden'); spinner.classList.remove('hidden');
@@ -108,6 +120,16 @@ document.getElementById('autoTestBtn').addEventListener('click', async function(
}, },
body: JSON.stringify(autoTestData), body: JSON.stringify(autoTestData),
}); });
if (!response.ok) {
const contentType = response.headers.get("content-type");
if (contentType && contentType.includes("application/json")) {
const errorResult = await response.json();
throw new Error(errorResult.message || errorResult.error || "Server Error");
} else {
const text = await response.text();
throw new Error(`Server returned ${response.status}: ${text.substring(0, 100)}...`);
}
}
const result = await response.json(); const result = await response.json();
@@ -117,28 +139,28 @@ document.getElementById('autoTestBtn').addEventListener('click', async function(
if (result.success) { if (result.success) {
statusDiv.classList.add('status-success'); statusDiv.classList.add('status-success');
statusDiv.textContent = `${result.message}`; statusDiv.textContent = `${result.message}`;
// Format detailed results // Format detailed results
let detailedOutput = `Total Tests: ${result.totalTests}\n`; let detailedOutput = `Total Tests: ${result.totalTests}\n`;
detailedOutput += `Successful: ${result.successfulConfigs}\n`; detailedOutput += `Successful: ${result.successfulConfigs}\n`;
detailedOutput += `Failed: ${result.totalTests - result.successfulConfigs}\n\n`; detailedOutput += `Failed: ${result.totalTests - result.successfulConfigs}\n\n`;
detailedOutput += '─'.repeat(50) + '\n\n'; detailedOutput += '─'.repeat(50) + '\n\n';
result.results.forEach((test, index) => { result.results.forEach((test, index) => {
detailedOutput += `Test ${index + 1}: ${test.config}\n`; detailedOutput += `Test ${index + 1}: ${test.config}\n`;
detailedOutput += ` Port: ${test.port}\n`; detailedOutput += ` Port: ${test.port}\n`;
detailedOutput += ` Encryption: ${test.secure ? 'SSL/TLS' : 'STARTTLS'}\n`; detailedOutput += ` Encryption: ${test.secure ? 'SSL/TLS' : 'STARTTLS'}\n`;
detailedOutput += ` Status: ${test.status === 'success' ? '✅ SUCCESS' : '❌ FAILED'}\n`; detailedOutput += ` Status: ${test.status === 'success' ? '✅ SUCCESS' : '❌ FAILED'}\n`;
if (test.status === 'success') { if (test.status === 'success') {
detailedOutput += ` Email Sent: ${test.messageId}\n`; detailedOutput += ` Email Sent: ${test.messageId}\n`;
} else { } else {
detailedOutput += ` Error: ${test.error}\n`; detailedOutput += ` Error: ${test.error}\n`;
} }
detailedOutput += '\n'; detailedOutput += '\n';
}); });
logOutput.textContent = detailedOutput; logOutput.textContent = detailedOutput;
} else { } else {
statusDiv.classList.add('status-error'); statusDiv.classList.add('status-error');
@@ -149,8 +171,8 @@ document.getElementById('autoTestBtn').addEventListener('click', async function(
} catch (error) { } catch (error) {
resultsDiv.classList.remove('hidden'); resultsDiv.classList.remove('hidden');
statusDiv.classList.add('status-error'); statusDiv.classList.add('status-error');
statusDiv.textContent = '❌ Network Error'; statusDiv.textContent = '❌ Error Caught';
logOutput.textContent = error.toString(); logOutput.textContent = error.message;
} finally { } finally {
// Reset Button // Reset Button
btn.disabled = false; btn.disabled = false;

269
server.js
View File

@@ -19,46 +19,45 @@ app.get('/', (req, res) => {
}); });
app.post('/api/test-smtp', async (req, res) => { app.post('/api/test-smtp', async (req, res) => {
const { host, port, secure, user, pass, from, to } = req.body; const { host, port, secure, user, pass, from, to } = req.body;
// Log intent // Log intent
console.log( console.log(
`Attempting SMTP connection to ${host}:${port} (${ `Attempting SMTP connection to ${host}:${port} (${secure ? "Secure" : "Insecure"
secure ? "Secure" : "Insecure" }) for ${user}`
}) for ${user}` );
);
// Create Transporter // Create Transporter
const transporterConfig = { const transporterConfig = {
host: host, host: host,
port: parseInt(port), port: parseInt(port),
secure: secure === true || secure === "true", // true for 465, false for other ports secure: secure === true || secure === "true", // true for 465, false for other ports
auth: { auth: {
user: user, user: user,
pass: pass, pass: pass,
}, },
};
// Only add TLS settings if secure is not explicitly "none"
if (secure !== "none") {
transporterConfig.tls = {
rejectUnauthorized: false, // Allow self-signed certs for testing flexibility
}; };
}
const transporter = nodemailer.createTransport(transporterConfig); // Only add TLS settings if secure is not explicitly "none"
if (secure !== "none") {
transporterConfig.tls = {
rejectUnauthorized: false, // Allow self-signed certs for testing flexibility
};
}
try { try {
// 1. Verify Connection const transporter = nodemailer.createTransport(transporterConfig);
await transporter.verify();
console.log("SMTP Connection Verified Successfully");
// 2. Send Test Email // 1. Verify Connection
const mailOptions = { await transporter.verify();
from: from || user, // Default to user if from not specified console.log("SMTP Connection Verified Successfully");
to: to,
subject: "SMTP Test - Advanced SMTP Tester", // 2. Send Test Email
html: ` const mailOptions = {
from: from || user, // Default to user if from not specified
to: to,
subject: "SMTP Test - Advanced SMTP Tester",
html: `
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
@@ -182,85 +181,85 @@ app.post('/api/test-smtp', async (req, res) => {
</body> </body>
</html> </html>
`, `,
}; };
const info = await transporter.sendMail(mailOptions); const info = await transporter.sendMail(mailOptions);
console.log("Message sent: %s", info.messageId); console.log("Message sent: %s", info.messageId);
res.json({ res.json({
success: true, success: true,
message: "Connection verified and email sent successfully!", message: "Connection verified and email sent successfully!",
details: { details: {
messageId: info.messageId, messageId: info.messageId,
response: info.response, response: info.response,
}, },
}); });
} catch (error) { } catch (error) {
console.error("SMTP Error:", error); console.error("SMTP Error:", error);
res.status(500).json({ res.status(500).json({
success: false, success: false,
message: "SMTP Test Failed", message: "SMTP Test Failed",
error: error.message, error: error.message,
}); });
} }
}); });
// Auto-Discovery Endpoint - Tests multiple port/encryption combinations // Auto-Discovery Endpoint - Tests multiple port/encryption combinations
app.post('/api/auto-test-smtp', async (req, res) => { app.post('/api/auto-test-smtp', async (req, res) => {
const { host, user, pass, from, to } = req.body; const { host, user, pass, from, to } = req.body;
// Common SMTP port/encryption combinations to test // Common SMTP port/encryption combinations to test
const configurations = [ const configurations = [
{ port: 587, secure: false, name: 'STARTTLS (587)', tls: true }, { port: 587, secure: false, name: 'STARTTLS (587)', tls: true },
{ port: 465, secure: true, name: 'SSL/TLS (465)', tls: true }, { port: 465, secure: true, name: 'SSL/TLS (465)', tls: true },
{ port: 25, secure: false, name: 'STARTTLS (25)', tls: true }, { port: 25, secure: false, name: 'STARTTLS (25)', tls: true },
{ port: 2525, secure: false, name: 'STARTTLS (2525)', tls: true }, { port: 2525, secure: false, name: 'STARTTLS (2525)', tls: true },
{ port: 25, secure: false, name: 'Unencrypted (25)', tls: false }, { port: 25, secure: false, name: 'Unencrypted (25)', tls: false },
{ port: 587, secure: false, name: 'Unencrypted (587)', tls: false }, { port: 587, secure: false, name: 'Unencrypted (587)', tls: false },
{ port: 2525, secure: false, name: 'Unencrypted (2525)', tls: false }, { port: 2525, secure: false, name: 'Unencrypted (2525)', tls: false },
]; ];
console.log(`Starting auto-discovery for ${host} with user ${user}`); console.log(`Starting auto-discovery for ${host} with user ${user}`);
const results = [];
let successCount = 0;
// Test each configuration const results = [];
for (const config of configurations) { let successCount = 0;
console.log(`Testing ${config.name}...`);
const transporterConfig = {
host: host,
port: config.port,
secure: config.secure,
auth: {
user: user,
pass: pass,
},
connectionTimeout: 10000, // 10 second timeout
greetingTimeout: 10000,
};
// Only add TLS settings if encryption is enabled // Test each configuration
if (config.tls !== false) { for (const config of configurations) {
transporterConfig.tls = { console.log(`Testing ${config.name}...`);
rejectUnauthorized: false,
};
}
const transporter = nodemailer.createTransport(transporterConfig); const transporterConfig = {
host: host,
port: config.port,
secure: config.secure,
auth: {
user: user,
pass: pass,
},
connectionTimeout: 10000, // 10 second timeout
greetingTimeout: 10000,
};
try { // Only add TLS settings if encryption is enabled
// Verify connection if (config.tls !== false) {
await transporter.verify(); transporterConfig.tls = {
console.log(`${config.name} - Connection successful!`); rejectUnauthorized: false,
};
}
// Send test email for this successful configuration try {
const mailOptions = { const transporter = nodemailer.createTransport(transporterConfig);
from: from || user,
to: to, // Verify connection
subject: `SMTP Auto-Discovery: ${config.name} - SUCCESS`, await transporter.verify();
html: ` console.log(`${config.name} - Connection successful!`);
// Send test email for this successful configuration
const mailOptions = {
from: from || user,
to: to,
subject: `SMTP Auto-Discovery: ${config.name} - SUCCESS`,
html: `
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
@@ -384,42 +383,52 @@ app.post('/api/auto-test-smtp', async (req, res) => {
</body> </body>
</html> </html>
`, `,
}; };
const info = await transporter.sendMail(mailOptions); const info = await transporter.sendMail(mailOptions);
successCount++; successCount++;
results.push({ results.push({
config: config.name, config: config.name,
port: config.port, port: config.port,
secure: config.secure, secure: config.secure,
status: 'success', status: 'success',
messageId: info.messageId, messageId: info.messageId,
}); });
console.log(`📧 Email sent for ${config.name}: ${info.messageId}`); console.log(`📧 Email sent for ${config.name}: ${info.messageId}`);
} catch (error) { } catch (error) {
console.log(`${config.name} - Failed: ${error.message}`); console.log(`${config.name} - Failed: ${error.message}`);
results.push({ results.push({
config: config.name, config: config.name,
port: config.port, port: config.port,
secure: config.secure, secure: config.secure,
status: 'failed', status: 'failed',
error: error.message, error: error.message,
}); });
}
} }
}
// Return summary of all tests // Return summary of all tests
res.json({ res.json({
success: true, success: true,
message: `Auto-discovery complete. Found ${successCount} working configuration(s).`, message: `Auto-discovery complete. Found ${successCount} working configuration(s).`,
totalTests: configurations.length, totalTests: configurations.length,
successfulConfigs: successCount, successfulConfigs: successCount,
results: results, results: results,
}); });
});
// Global Error Handler
app.use((err, req, res, next) => {
console.error("Unhandled Error:", err);
res.status(500).json({
success: false,
message: "Internal Server Error",
error: err.message || "Unknown error occurred"
});
}); });
app.listen(port, () => { app.listen(port, () => {
console.log(`Server running at http://localhost:${port}`); console.log(`Server running at http://localhost:${port}`);
}); });