0

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.

Ali Navidi
  • 693
  • 1
  • 5
  • 18
MKD
  • 1
  • 2

2 Answers2

0

The issue is likely to be related to reusing the ipcRenderer event handlers. In Electron, once an event handler has been created with ipcRenderer.on, it will continue to exist and respond to events until it is removed. This means that if ipcRenderer.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 in preload.js and its usage in renderer.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 of ipcRenderer.on for event handlers that should only run once. The ipcRenderer.once method will automatically remove the event handler after it has been triggered for the first time.

Replace the code in preload.js with:

receiveRecipientData: (callback) => {
    ipcRenderer.once('recipientData', (event, jsonData) => {
      callback(jsonData);
    });
  },

Alternatively,

An alternative would be to keep using ipcRenderer.on but manually remove the previous listener using ipcRenderer.removeListener before attaching a new one. Here's how you could do this:

// In preload.js
removeRecipientDataListener: () => {
    ipcRenderer.removeAllListeners('recipientData');
},

receiveRecipientData: (callback) => {
    ipcRenderer.on('recipientData', (event, jsonData) => {
      callback(jsonData);
    });
},

// In renderer.js
window.api.removeRecipientDataListener();
window.api.receiveRecipientData((jsonData) => {
  // Your existing code here...
});

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.

hans_ob1
  • 74
  • 5
  • I've changed ipcRenderer to "on" to "once" but for some reason "receiveRecipientData" isn't getting called when i select an excel file after sending mails once. Do I need to add another event listener after sending mails? – MKD Jun 23 '23 at 17:44
  • yes, ipcRenderer.once will remove the event listener after it's been triggered once. If you want to reattach the listener after sending the emails, you will need to call receiveRecipientData again. – hans_ob1 Jun 23 '23 at 17:51
  • 1
    Thanks for help! I guess I need to add functionality to let users remove selected file in case they want to change file, so I can call a function that will reattach listener, same for after sending mails as well. – MKD Jun 23 '23 at 17:57
  • hmmm, creating and destroying event listeners every time could lead to issues. I updated the answer with an alternative way. Basically remove the previous listener using `ipcRenderer.removeListener` before attaching a new one while keep your old code in place – hans_ob1 Jun 23 '23 at 17:58
  • I've placed removeListener like you said and placed a breakpoint at it. But it isn't getting called and mails are sent for every excel file. I forgot to mention all of the codes in renderer.js is in "document.addEventListener("DOMContentLoaded", function () {}" function. Might it be because of it? – MKD Jun 23 '23 at 18:06
  • The `document.addEventListener("DOMContentLoaded", function () {})` function is only called once when the DOM is fully loaded. If you are attaching and removing event listeners within this function, they will only be attached and removed when the page first loads. You probably need some kind of trigger, button or something to reload attach/remove operation per intent – hans_ob1 Jun 23 '23 at 18:22
  • Does it work? If you find this answer useful. Appreciate your upvote! – hans_ob1 Jun 24 '23 at 04:11
  • I couldn't get it to work with eventlisteners. I've tried a different approach and it seems like there's no problems so far. I'll be posting it below – MKD Jun 25 '23 at 05:37
0

I've managed to solve the problem with getting rid of eventlisteners and using Global Event Handlers.

// 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.onclick = toggleInfoBubble;
  }

  if (greetingInfoButton && greetingInfoBubble) {
    greetingInfoButton.onclick = toggleInfoBubble;
  }

  function handleExcelFile() {
    const recipientButton = document.getElementById('recipient');

    if (recipientButton) {
      recipientButton.onclick = () => {
        window.api.openExcelFile(sender = 'mailSender');
      };
    }
  }
MKD
  • 1
  • 2