Search code examples
javascriptgoogle-chrome-extensionphishing

Unchecked runtime.lastError: The message port closed before a response was received. Chrome Extension


I'm getting this error over and over again and still can't figure out how to solve it. The sendResponse function is being called before the analyzePageGuest function has completed its execution, but I'm not able to fix it. The project I'm doing is an extension to detect phishing using the Virus Total API. This is what I tried: Can someone help me?

background.js

async function analyzePageGuest(tabId, changeInfo, tab) {
  const url = tab.url
  console.log('URL:', url)
  let analysisIdValue
  try {
    const options = {
      method: 'POST',
      headers: {
        accept: 'application/json',
        'x-apikey':
          'apikey',
        'content-type': 'application/x-www-form-urlencoded',
      },
      body: new URLSearchParams({ url: url }),
    }

    fetch(
      'https://cors-anywhere.herokuapp.com/https://www.virustotal.com/api/v3/urls',
      options
    )
      .then((response) => response.json())
      .then((response) => {
        console.log(response)
        analysisIdValue = response.data.id
        console.log(analysisIdValue)
      })
      .catch((err) => console.error(err))

    // Wait for the analysis to complete using a polling mechanism
    let resultResponse
    let retries = 0
    let resultJson
    while (retries < 5) {
      await new Promise((resolve) => setTimeout(resolve, 2000)) // wait for 2 seconds
      resultResponse = await fetch(
        `https://cors-anywhere.herokuapp.com/https://www.virustotal.com/api/v3/analyses/${analysisIdValue}`,
        {
          method: 'GET',
          headers: {
            'x-apikey':
              'apikey',
          },
        }
      )
      resultJson = await resultResponse.json()
      if (resultJson.data.attributes.status === 'completed') {
        break
      }
      retries++
    }

    if (resultResponse.ok) {
      const malicious = resultJson.data.attributes.stats.malicious > 0
      const resultText = malicious
        ? 'Phishing detected!'
        : 'Safe from phishing :)'
      return resultText // Return the result text
    } else {
      return 'Error: Analysis failed' // Return an error message
    }
  } catch (error) {
    console.error('Error analyzing page:', error)
    return 'Error: Analysis failed' // Return an error message
  }
}

chrome.runtime.onMessage.addListener(async (request, sender, sendResponse) => {
  if (request.action === 'analyzePageGuest') {
    let tabId
    if (sender.tab) {
      tabId = sender.tab.id
    } else {
      const tabs = await chrome.tabs.query({
        active: true,
        currentWindow: true,
      })
      if (tabs.length > 0) {
        tabId = tabs[0].id
      } else {
        console.log('No tabs found!')
        sendResponse({ result: 'No tabs found!' }) // Return an error message
        return false
      }
    }
    if (tabId) {
      console.log('Tab ID:', tabId)
      const tab = await chrome.tabs.get(tabId)
      console.log('Tab URL:', tab.url)
      try {
        const resultText = await analyzePageGuest(tabId, {}, tab)
        console.log('Result text:', resultText)
        sendResponse({ result: resultText })
      } catch (error) {
        console.error('Error analyzing page:', error)
        sendResponse({ result: 'Error analyzing page' })
      }
    }
    return true
  }
})

popup.js

const button = document.querySelector('.button')
const resultElement = document.getElementById('result')

button.addEventListener('click', () => {
  chrome.runtime.sendMessage({ action: 'analyzePageGuest' }, (response) => {
    if (response && response.result) {
      console.log('Received response:', response)
      resultElement.innerText = response.result
    }
  })

  setTimeout(() => {
    if (!resultElement.innerText) {
      resultElement.innerText =
        'Analysis not completed. Please try again later.'
    }
  }, 4000)
})

popup.html

<div class="popup-container">
        <span id="current-url"></span>
      <button class="button">
        <span class="submit">Analyze page</span>
        <span class="loading"><i class="fas fa-circle-notch fa-spin"></i></i></span>
        <span class="check"><i class="fa fa-check"></i></span>
      </button>
      <span id="result"></span>
    </div>

I've already added return true to the asynchronous function, but it's not working..


Solution

  • So. To make your code work, you need to make a few very important changes.

    Your listener function should not be asynchronous. According to the documentation:

    Warning: Do not prepend async to the function. Prepending async changes the meaning to sending an asynchronous response using a promise, which is effectively the same as sendResponse(true).

    It should look like this:

    chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {})
    

    And most importantly! You do everything right, You return true to process the asynchronous response, but you do not call the sendResponse function correctly. sendResponse function should be called after receiving the result in Promise.then(response => sendResponse(response)).

    I haven't looked at the logic behind your analyzePageGuest function, but the final listener function should look something like this:

    async function getActiveTab() {
      const tabs = await chrome.tabs.query({ active: true });
      let activeTab = null;
      tabs?.forEach((tab) => {
        if (!activeTab || tab.lastAccessed > activeTab.lastAccessed) {
          activeTab = tab;
        }
      });
      return activeTab;
    }
    
    chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
      switch (request.action) {
        case 'analyzePageGuest':
          getActiveTab().then(activeTab => {
            analyzePageGuest(activeTab.id, {}, activeTab)
              .then(response => sendResponse(response))
              .catch(() => sendResponse({ result: 'Error analyzing page' }));
          });
          return true;
      }
    });
    

    To get the url address of the active tab, you must have tabs permission in manifest.json:

    // manifest.json
    ...
    "permissions": [
      "tabs"
    ]
    ...