I’m creating an app that will solely run on local except for mail sending operations.I’ve wrote necessary codes that will get recipients’ mail adresses from excel file and send mail one by one. My problem is; if I select additional excel files after selecting first file, code sends mail to previously sent mails’ recipients as well. I’ll provide codes below:
main.js
// Handle file reading operation
ipcMain.on("openExcelFile", async (event, sender) => {
console.log(sender);
try {
const result = await dialog.showOpenDialog({
filters: [{ name: "Excel Dosyası", extensions: ["xlsx", "xls"] }],
properties: ["openFile"],
});
if (!result.canceled) {
const filePath = result.filePaths[0];
const workbook = XLSX.readFile(filePath);
const sheetName = workbook.SheetNames[0];
const sheet = workbook.Sheets[sheetName];
const jsonData = XLSX.utils.sheet_to_json(sheet, { header: 1 });
console.log(jsonData);
console.log(jsonData.length);
if (jsonData.length === 1 && jsonData[0].length === 0) {
event.reply("openExcelResponse", { error: "Alıcı dosyası geçersiz!" });
}
else {
if (sender === "mailSender") {
event.reply("recipientData", jsonData);
} else if (sender === "sertificateCreator") {
event.reply("participantData", jsonData);
}
}
}
} catch (error) {
console.error(error);
event.reply("recipientData", null); // Send null in case of error
}
});
ipcMain.on("sendEmail", async (event, emailOptions) => {
try {
await sendEmail(emailOptions);
event.reply("sendEmailResponse", { success: true });
} catch (error) {
event.reply("sendEmailResponse", {
success: false,
error: error.message,
});
}
});
async function sendEmail(emailOptions) {
// Create a Nodemailer transporter and send the email
var transporter = nodeMailer.createTransport(Credentials);
// Construct the email message
const mailOptions = {
from: Credentials.auth.user,
to: emailOptions.to,
subject: emailOptions.subject,
text: emailOptions.text,
};
// Check if attachments are present
if (emailOptions.attachments && emailOptions.attachments.length > 0) {
mailOptions.attachments = emailOptions.attachments.map((attachment) => ({
filename: attachment.filename,
path: attachment.path,
}));
}
// Send the email
await transporter.sendMail(mailOptions);
}
preload.js
contextBridge.exposeInMainWorld('api', {
openExcelFile: (sender) => {
ipcRenderer.send('openExcelFile', sender);
},
receiveRecipientData: (callback) => {
ipcRenderer.on('recipientData', (event, jsonData) => {
callback(jsonData);
});
},
getAttachments: () => {
ipcRenderer.send('getAttachments');
},
receiveAttachments: (callback) => {
ipcRenderer.on('attachments', (event, attachmentData) => {
callback(attachmentData);
});
},
sendEmail: (emailOptions) => {
ipcRenderer.send('sendEmail', emailOptions);
},
receiveEmailResponse: (callback) => {
ipcRenderer.on('sendEmailResponse', (event, response) => {
callback(response);
});
},
});
renderer.js
// Handle variables specific to mailSender.html
function handleMailSender() {
//Info buttons
const recipientInfoButton = document.getElementById("recipientInfoButton");
const recipientInfoBubble = document.querySelector("#recipientInfoButton .info-bubble");
const greetingInfoButton = document.getElementById("greetingInfoButton");
const greetingInfoBubble = document.querySelector("#greetingInfoButton .info-bubble");
if (recipientInfoButton && recipientInfoBubble) {
recipientInfoButton.addEventListener("click", toggleInfoBubble);
}
if (greetingInfoButton && greetingInfoBubble) {
greetingInfoButton.addEventListener("click", toggleInfoBubble);
}
function handleExcelFile() {
const recipientButton = document.getElementById('recipient');
if (recipientButton) {
recipientButton.addEventListener('click', () => {
window.api.openExcelFile(sender = 'mailSender');
});
}
}
function handleAttachments() {
const addAttachmentButton = document.getElementById('addAttachment');
if (addAttachmentButton) {
addAttachmentButton.addEventListener('click', () => {
window.api.getAttachments();
});
}
}
handleExcelFile();
handleAttachments();
//Reset form after submission
function resetForm() {
const form = document.querySelector('form');
form.reset();
const submitButton = document.querySelector('input[type="submit"]');
submitButton.disabled = true;
}
// Handle the email response
window.api.receiveEmailResponse((response) => {
if (response.success) {
showMessage('success', 'Mailler başarıyla gönderildi.');
resetForm();
} else {
const errorMessage = `Mail gönderilirken bir hata yaşandı:<br>${response.error}`;
showMessage('error', errorMessage);
console.error(response.error);
resetForm();
}
});
}
// Prepare the emails to be sent
function prepareEmail(mailSubject, recipientEmail, mailContent, attachmentData) {
const emailOptions = {
to: recipientEmail,
subject: mailSubject,
text: mailContent,
attachments: attachmentData
};
//console.log(emailOptions.attachments[0].filename);
// Send the email using nodemailer
window.api.sendEmail(emailOptions);
}
let attachmentData;
// Receive the attachments from the main process
window.api.receiveAttachments((data) => {
if (data) {
attachmentData = data;
console.log(attachmentData);
} else {
// Handle the case when an error occurred or no file was selected
console.error('Error occurred or no file selected');
}
});
// Receive the recipientData from the main process
window.api.receiveRecipientData((jsonData) => {
if (jsonData) {
console.log(jsonData);
const recipientData = jsonData.slice(1);
// Enable the submit button
const submitButton = document.querySelector('input[type="submit"]');
submitButton.disabled = false;
createMail(recipientData);
} else {
// Handle the case when an error occurred or no file was selected
console.error('Error occurred or no file selected');
}
});
function createMail(recipientData) {
// Add event listener for form submission
const form = document.querySelector('form');
form.addEventListener('submit', (event) => {
event.preventDefault(); // Prevent the default form submission behavior
const mailSubject = document.getElementById('mailSubject').value;
const mailContent = document.getElementById('mailContent').value;
const addGreeting = document.getElementById('greeting').checked;
recipientData.forEach((row) => {
const [name, email] = row;
// Construct the email content
let content = '';
if (addGreeting) {
content += `Merhaba ${name},n`;
}
content += mailContent;
// Process the recipient file data and send emails
prepareEmail(mailSubject, email, content, attachmentData);
});
});
}
I’ve tried printing variables to see if any data was getting appended but everything seemed to work as intended. I’ve also tried removing eventlistener and re-adding it after selecting file once but it didn’t work either.
I’m quite new to electron so I’m not quite sure what I’m doing wrong. Any help is appreciated.
2
Answers
I've managed to solve the problem with getting rid of eventlisteners and using Global Event Handlers.
The issue is likely to be related to reusing the
ipcRenderer
event handlers. In Electron, once an event handler has been created withipcRenderer.on
, it will continue to exist and respond to events until it is removed. This means that ifipcRenderer.on
is called multiple times without removing the previous handlers, multiple handlers will exist simultaneously and all respond to the same event.The function
window.api.receiveRecipientData
inpreload.js
and its usage inrenderer.js
seem to be causing the problem. Every time a new Excel file is opened, a new event listener is attached, which receives the recipient data and triggers the email sending. However, previous event listeners are not removed and also trigger the email sending. This is probably the reason why you are sending to previous emails as well.Try use
ipcRenderer.once
instead ofipcRenderer.on
for event handlers that should only run once. TheipcRenderer.once
method will automatically remove the event handler after it has been triggered for the first time.Replace the code in
preload.js
with:Alternatively,
An alternative would be to keep using
ipcRenderer.on
but manually remove the previous listener usingipcRenderer.removeListener
before attaching a new one. Here’s how you could do this:This way, you ensure that only one listener is active at a time, and you have more control over when the listener is created and destroyed.