skip to Main Content

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


  1. Chosen as BEST ANSWER

    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');
          };
        }
      }
    

  2. 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.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search