Chrome Extension message passing: Unchecked runtime.lastError: Could not establish connection. Receiving end does not exist

JavascriptGoogle Chrome-Extension

Javascript Problem Overview


My chrome extension has the following two javascripts:

background.js, running as background script:

chrome.runtime.onMessage.addListener(function(message, sender, sendResponse) {
    if (message.data == "takeScreenshot") {
		var resp = sendResponse;
		chrome.tabs.captureVisibleTab(function(screenshotUrl) {
			resp({
				screenshot: screenshotUrl
			});
		});
		return true; // Return true to tell that the response is sent asynchronously
    } else {
        return "TestReply";
    }
});

api.js, running as web accessible resource:

window.takeScreenshot = (function() {
	var isTakingScreenshot = false; // Semaphore
	return function() {
		if(isTakingScreenshot) return Promise.reject();
		isTakingScreenshot = true;
		return new Promise(function(resolve, reject) {
			chrome.runtime.sendMessage("eomfljlchjpefnempfimgminjnegpjod", "takeScreenshot", function(response) {
				console.log(response);
				isTakingScreenshot = false;
				resolve(response.screenshot);
			});
		});
	}
})()
window.test = (function() {
	return function() {
		return new Promise(function(resolve, reject) {
			chrome.runtime.sendMessage("eomfljlchjpefnempfimgminjnegpjod", "test", function(response) {
				console.log(response);
				resolve(response.length);
			});			
		});
	}
})();

When I execute in a tab's console either function (auto-complete knows them, so they are available), I get the error:

> Unchecked runtime.lastError: Could not establish connection. Receiving end does not exist.

and the respone returned is undefined.

I have checked that the id in sendMessage is the same as in the manifest and in the chrome://extensions page, and I have opened the background page DevTools of the extension and manually added the same listener there to make sure the listener is indeed registered.

My searches found that this error means the listener has not been correctly registered, but I don't find the underlying reason. Do you have an idea what causes this error?

Javascript Solutions


Solution 1 - Javascript

OK. I found out what the problem is. This is a change in chromes behavior since 72 I guess. The problem is if you try to call chrome.runtime.connect() before you have opened a channel on the other end in Background or popup page then you will get that error.

What Chrome docs say is that you can send a message immediately. In the past this would just work and the messages would get either delivered or dropped. But now it is failing. > chrome docs: Upon calling tabs.connect, runtime.connect or runtime.connectNative, a Port is created. This port can immediately be used for sending messages to the other end via postMessage.

So our workaround is to make sure the connection listener is set up first before calling connect() by just delaying the connect() call:

chrome.runtime.onConnect.addListener(port => {
  console.log('connected ', port);

  if (port.name === 'hi') {
    port.onMessage.addListener(this.processMessage);
  }
});

If you set up a listener for the disconnect event on the content script side, it actually gets called when you try to chrome.runtime.connect and you don't have anything listening on the other end. Which is correct behavior according the Port lifetime

port = chrome.runtime.connect(null, {name: 'hi'});      
port.onDisconnect.addListener(obj => {
  console.log('disconnected port');
})

I don't know if there is a way to avoid this other than with setTimeout and trying to get the chrome.runtime.connect to come after chrome.runtime.onConnect.addListener is called. This is not a good solution because it leads to timing errors. Maybe another workaround is to reverse the direction of the channel. And initiate the connect from popup instead of contentscript.

Update: I made a minimum repro extension for this issue.

Solution 2 - Javascript

Here's a pattern I use to solve this. Content script tries to reach background script. If background script is not available yet then we can see that from chrome.runtime.lastError being set (instead of being undefined). In this case try again in 1000 ms.

contentScript.js

function ping() {
  chrome.runtime.sendMessage('ping', response => {
    if(chrome.runtime.lastError) {
      setTimeout(ping, 1000);
    } else {
      // Do whatever you want, background script is ready now
    }
  });
}

ping();

backgroundScript.js

chrome.runtime.onConnect.addListener(port => {
  port.onMessage.addListener(msg => {
    // Handle message however you want
  });
});

chrome.runtime.onMessage.addListener((request, sender, sendResponse) => sendResponse('pong'));

Solution 3 - Javascript

Some security changes in chrome seems like you have to take a slightly different approach when messaging between content scripts and the background script.

The calls to use are

  1. From background page:

chrome.runtime.onMessageExternal.addListener Notice the External part.

  1. The manifest.json needs extra permissions
      "externally_connectable": {
        "ids": ["abcdefghijklmnopqrstuvwxyzabcdef"],
        "matches": ["https://example.com/*"],
        "accepts_tls_channel_id": false
      },

"abcdefghijklmnopqrstuvwxyzabcdef" is your extension id.

"https://example.com/*" is the domain the content script runs on.

  1. From the content script:

    chrome.runtime.sendMessage / chrome.runtime.connect with a extensionId as the first parameter.

Read more here https://developer.chrome.com/extensions/manifest/externally_connectable

Solution 4 - Javascript

If you're getting this error message while developing a Chrome Extension be sure to disable other Chrome extensions you have installed in your browser. In particular the "AdBlock" extension seemed to interfere with messaging and resulted in my extension throwing this error. Disabling AdBlock resolved this issue for me.

Solution 5 - Javascript

After study more the solution of my extension, I had consider change the Long-lived connection to Simple one-time requests, Serously, I had spend two days search for a solution and do not can found a solutin for this problem in my extension. The popup is make with ReactJs. After remove all runtime.connect and runtime.onConnect, change the api and use onMessage, and only use callback of addListner to handle messages, the problem desapeared.I did this, because I don't need more real time changes and change it with requests only, think about that, if you do not need Long-lived connection change it for Simple one-time requests, for more information. I know that it is not a solution, but for everyone that needs accelerate or finish the development this is what I did.

My solution:

// content_script.js
if (!chrome.runtime.onMessage.hasListeners()) {
  window.chrome.runtime.onMessage.addListener(
    (request, sender, sendResponse) => {
      if (request?.type === 'load_titles') {
        sendResponse({
          type: 'loaded_titles',
          titles,
        });
      }
      if (request?.type === 'anything_else') {
        // do anything else
      }
    },
  );
}

If you need use sendResponse asyncronous to use in another function, you need return true inside on callback of onMessage.addListner.

Function responsable for send and receive messages:

 // used in app.js on popup
 export function messager(data, callback) {
  window.chrome?.tabs?.query({ url: 'https://example.com/' }, tabs => {
    const tabId = tabs[0].id;
    window.chrome.tabs.sendMessage(tabId, data, callback);
  });
}

 // app.js
 const loadTitles = useCallback(() => {
    messager({ type: 'load_titles' }, response => {
      if (response?.type === 'loaded_titles') {
         console.log(response.titles)
        // do anything you want
      }
    });
  }, []);

Solution 6 - Javascript

I'm adding this here because I had this error and NOT A SINGLE StackOverflow post solved my issue. My problem was because my background.js was sending a bunch of messages to my content script for just 1 visit of a webpage. Once I added a sender.status check, it only sent the message when the tab change was "completed", and the error I had went away.

chrome.tabs.onUpdated.addListener(function(activeInfo, sender, sendResponse) {
    // conditional check prevents sending multiple messages per refresh
    if(sender.status ===  "complete") {
      // do stuff here
    }
});

Solution 7 - Javascript

I encountered this error while developing the Chrome extension. For me, the issue was a different extension ID. If you need to use your extension ID within your code (while sending messages to the background worker, for example), make sure that your extension ID matches the one in the browser.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionAlexanderView Question on Stackoverflow
Solution 1 - JavascriptDavid DehghanView Answer on Stackoverflow
Solution 2 - JavascriptMárton GyörgyView Answer on Stackoverflow
Solution 3 - JavascriptTrophyGeekView Answer on Stackoverflow
Solution 4 - JavascriptclhenrickView Answer on Stackoverflow
Solution 5 - JavascriptDaniel Tiago FischerView Answer on Stackoverflow
Solution 6 - JavascriptGeorgeView Answer on Stackoverflow
Solution 7 - JavascriptDanView Answer on Stackoverflow