chrome extension message passsing: sendResponse is not a function - javascript

In my Chrome extension, I'm trying to exchange data between an internal web page of the extension chrome-extension://myExtensionId/path/to/web/page.html and content scripts.
So, in order to make this data persistent among different content scripts, I'm trying to save it as global variables in the extension's background! I do so using message passing.
My problem is:
When I try to send a response back from the background I get this error:
Error in event handler for (unknown): TypeError: sendResponse is not a
function
I followed the documentation's examples and this is my attempt:
In the scriptOfTheInternalPage.js :
var message = {
'order': 'setData',
'varName': 'myArray',
'data': myArray
};
extPort.postMessage(message, function (response) {
console.log('response:\n', JSON.stringify(response));
});
In background.js :
var globals = {
'myArray': [],
...
};
chrome.runtime.onConnect.addListener(function (port) {
port.onMessage.addListener(
function (message, sender, sendResponse) {
console.log(
'the port received this message:\n', JSON.stringify(message), '\n',
(sender.tab) ? ' from tab #' + sender.tab.id : ' from the extension!'
);
if (message.order === 'setData') {
globals[message.varName] = message.data;
sendResponse({'response': 'data saved!'}); //<=====
}
return true; //<=== tried to return true here as well;
});
});
Does this error means I should create a brand new function outside of the onMessage event listener?
I'm confused! What am I missing?

Port's onMessage event listeners do not have the same signature as runtime.onMessage. You don't get sender and sendResponse parameters, only the message. Returning true has no effect either.
To reply to a message, you need to use the port itself. This is covered by examples:
port.onMessage.addListener(function(msg) {
if (msg.joke == "Knock knock")
port.postMessage({question: "Who's there?"});
}
So you do need an onMessage listener on both sides, and some way to track requests (unique ID?) if several can be made.

Related

Communication from chrome extension to web page

I know it's possible to postMessage from a web page to a chrome extension and get an ok response back to the web page, but is it possible the other way around ?
My question "I need to be able to sent a request into a webpage and wait for a response that I can use in the extension. Maybe not with postMessage ?"
I know the docs says "Only the web page can initiate a connection." (https://developer.chrome.com/extensions/messaging#external-webpage)
This is what I have tried so far.
background.js
chrome.extension.sendMessage("Hello from extension to webpage");
window.postMessage("Hello from extension to webpage");
chrome.tabs.sendMessage(TAB_ID, "Hello from extension to webpage");
webpage.js (not part of the extension)
window.onmessage = (event) => {
// Waiting for that message.
console.info("Event received ", event);
}
window.chrome.runtime.connect(CHROME_EXTENSION_APP_ID).onMessage.addListener(function (){
function(request, sender, sendResponse) {
console.info("Event received from the extension", event);
}
})
Any idea will be appreciated :)
You can use chrome.tabs.executeScript API to do this.
var dataToWebPage = {
text: 'test',
foo: 1,
bar: false
};
chrome.tabs.executeScript({
code: '(' + function(params) {
//This function will work in webpage
console.log(params); //logs in webpage console
return {
success: true,
response: "This is from webpage."
};
} + ')(' + JSON.stringify(dataToWebPage) + ');'
}, function(results) {
//This is the callback response from webpage
console.log(results[0]); //logs in extension console
});
Please note that it needs tabs permission.

Chrome extension: sendMessage from content to background getting response from popup

I am trying to send a message from a content script to my background script. When the background receives the message it sends data back to the content script in the callback.
My popup also has a listener for messages from the content script, but does not respond to a message meant for the background script.
Then content is receiving an undefined back from the callback, which I think is caused by the popup receiving the message but not responding.
The reference says:
Note: If multiple pages are listening for onMessage events, only the
first to call sendResponse() for a particular event will succeed in
sending the response. All other responses to that event will be
ignored.
So surely I should only get the response from my background script.
My content script does this:
function notifyReady() {
chrome.runtime.sendMessage({
type: 'ACTIVITY_HISTORY_READY'
},
function (response) {
console.log(">>>>Response: ", response);
if (response.type == 'HISTORY_DATA') {
processLog(response);
}
});
}
My background script listens like this:
chrome.runtime.onMessage.addListener(function (msg, sender, sendResponse) {
console.log("received " + msg.type);
if (msg.type = 'ACTIVITY_HISTORY_READY' && historyData) {
if (historyData) {
sendResponse({
type: "HISTORY_DATA",
position: historyData.position,
company: historyData.company
});
historyData = '';
} else {
sendResponse({
type: "NO_DATA"
});
}
}
});
And the listener in my popup is:
chrome.runtime.onMessage.addListener(function (msg, sender, sendResponse) {
if (msg.type == 'JOB_DETAILS') {
sendResponse("OK!");
document.getElementById('position').value = msg.position;
document.getElementById('company').value = msg.company;
document.getElementById('url').value = sender.tab.url;
}
});
if (msg.type = 'ACTIVITY_HISTORY_READY' && historyData) {
note that if historyData is falsey you are not sending any response. The else branch of the second if can never be taken.
You should remove historyData from the first if. The popup code has nothing to do with this.

Why does serviceworker cause every second jquery post to instantly stall out?

What I stated in the title only happens in chrome as in firefox "('serviceWorker' in navigator)" is always false and "console.warn('Service workers aren\'t supported in this browser.');" triggers instead.
If you clear the browser or run incognito then it works initially but when you log out for the first time and then log back in the problems start.
This is what it looks like in the network log
Here's the code in which I register the SW:
function activateSW(){
if('serviceWorker' in navigator){
if(window.location.pathname != '/'){
//register with API
if(!navigator.serviceWorker.controller) navigator.serviceWorker.register('/service-worker', { scope: '/' });
//once registration is complete
navigator.serviceWorker.ready.then(function(serviceWorkerRegistration){
//get subscription
serviceWorkerRegistration.pushManager.getSubscription().then(function(subscription){
//enable the user to alter the subscription
$('.js-enable-sub-test').removeAttr("disabled");
//set it to allready subscribed if it is so
if(subscription){
$('.js-enable-sub-test').prop("checked", true);
$('.js-enable-sub-test').parent().addClass('checked');
}
});
});
}
}else{
console.warn('Service workers aren\'t supported in this browser.');
}
}
'/service-worker' is a request that gets sent to index.php (via .htaccess). It eventually end up in this function:
function serviceworkerJS($params){
$hash = API::request('settings', 'getSWHash', '');
if($hash != false){
setcookie('SW_Hash', $hash, time() + (86400 * 365 * 10), "/");
header('Content-type: text/javascript');
echo "'use strict';
var hash = '".$hash."';";
include(ROOT_PATH.'public/js/service-worker.js');
}elseif(isset($_COOKIE['SW_Hash'])){
header('Content-type: text/javascript');
echo "'use strict';
var hash = '".$_COOKIE['SW_Hash']."';";
include(ROOT_PATH.'public/js/service-worker.js');
}else{
header('HTTP/1.1 404 Not Found');
}
}
Service-worker.js as seen in chrome://serviceworker-internals/ looks like this:
(Several references to the adress has been replaced by stars)
'use strict';
var hash = 'bd8e78963deebf350f851fbf8cdc5080';
var *****_API_ENDPOINT = 'https://*********.***/';
//For displaying notifications
function showNotification(title, body, icon, data, id) {
var notificationOptions = {
body: body,
icon: icon,
tag: id,
data: data
};
//possibly unnecessary
if(self.registration.showNotification){
return self.registration.showNotification(title, notificationOptions);
}else{
return new Notification(title, notificationOptions);
}
}
//asks the server for messages and sends them for displaying.
function getMessages(event){
//showNotification('debug', 'initial', '', '', 'debug1');
//build question
var FD = new FormData();
FD.append('hash', hash);
//ask start20 for the notifications
event.waitUntil(
fetch(*****_API_ENDPOINT + 'ajax-get-SW-notification/', {method: 'post', body: FD}).then(function(response){
//something went wrong
if (response.status !== 200){
console.log('Error communicating with ******, code: ' + response.status);
showNotification('debug', 'picnic', '', '', 'debug2');
throw new Error();
}
//decode the response
return response.json().then(function(data){
var len = data.notifications.length;
//showNotification('debug', len, '', '', 'propertyName');
//Display
for(var i = 0; i < len -1; i++){
showNotification(data.notifications[i].title,
data.notifications[i].body,
data.notifications[i].imageurl,
data.notifications[i].linkurl,
data.notifications[i].hash);
}
//the last one needs to be returned to complete the promise
return showNotification(data.notifications[len -1].title,
data.notifications[len -1].body,
data.notifications[len -1].imageurl,
data.notifications[len -1].linkurl,
data.notifications[len -1].hash);
});
})
);
}
//when the user installs a new SW
/*self.addEventListener('activate', function(event){
//getMessages(event);
//event.preventDefault();
event.waitUntil(return self.registration.showNotification('bicnic', { body: '*p' }));
});*/
//when the serviceworker gets a puch from the server
self.addEventListener('push', function(event){
getMessages(event);
event.preventDefault();
});
//get the link associated witht he message when a user clicks on it
self.addEventListener('notificationclick', function(event){
//ask if the notification has any link associated with it
var FD = new FormData();
FD.append('hash', event.notification.tag);
//get the link
event.waitUntil(
fetch(******_API_ENDPOINT + 'ajax-notification-has-link/', {method: 'post', body: FD}).then(function(response){
//something went wrong
if (response.status !== 200){
console.log('Error communicating with ********, code: ' + response.status);
return;
}
//decode the response
return response.json().then(function(data){
//if there's a link associated with the message hash
if(data.link){
console.log(******_API_ENDPOINT + 'notification-link/' + event.notification.tag);
return clients.openWindow(*****_API_ENDPOINT + 'notification-link/' + event.notification.tag);
}
});
})
);
});
//unnecessary?
/*self.addEventListener('install', function(event){
//event.preventDefault();
});
self.addEventListener("fetch", function(event) {
});//*/
Now if you comment away "if(!navigator.serviceWorker.controller) navigator.serviceWorker.register( '/service-worker', { scope: '/' });" then the issue disappears but ofc the serviceworker subscribing and unsubscribing stops working. (The if statement doesn't seem to do much and was only added in an attempt to solve this)
I've tried numerous versions of activateSW() with various conditions for the different expressions to run and haven't managed to make a version that works without breaking the serviceworker. I have also tried to catch errors on various points (registration, posts) but this has been unsuccessful as none of them throws any.
What I suspect might be the problem is that as you register the serviceworker an activate event is triggered. Now if you catch this and complete the event promise then you become unable to subscribe. However I suspect that the serviceworker remains active as you log out and this causes a problem.
If you have questions or want to see more code then just ask.
edit; here's the solution:
self.addEventListener("fetch", function(event) {
event.respondWith(
fetch(event.request)
);
});
What you're seeing is confusing noise in the Network panel of Chrome DevTools, but shouldn't have a practical effect on your application's behavior.
If a service worker controls a page but doesn't include a fetch event handler, current versions of Chrome (M44 stable, and M46 canary) end up logging all network requests in the Network panel with (canceled) status, like you're seeing. The actual request is still made without service worker intervention, which is the second, successful logged entry. Things should work as expected from the perspective of your page, but I completely understand the confusion.
I have heard that there are plans to make Chrome's service worker implementation behave much more like a "no-op" when a network request is made and there's no fetch event handler. I don't know how far along those plans are, but I would expect that the noise you're seeing logged to go away once that happens.

Cannot send message to content script properly

I'm so close to finishing my Chrome extension. I have one or two things to do. One of them is sending a message from the content script to the background script. I wrote the following, but it doesn't quite what I want.
content.js
var a=document.getElementsByTagName('a');
for (i=0,len=a.length;i<len;i++) {
a[i].addEventListener('contextmenu', function() {
var linkTitle = this.getAttribute('title').trim();
var linkUrl = this.getAttribute('href');
if ((linkTitle != null) && (linkTitle.length > 0)) {
chrome.extension.sendMessage({action:'bookmarkLink', 'title':linkTitle, 'url': linkUrl}, function(msg) {
alert('Messages sent: '+action+' and '+linkTitle+' also '+linkUrl);
});
}
});
};
background.js
chrome.contextMenus.create({'title': 'Add to mySU bookmarks', 'contexts': ['link'], 'onclick': mySUBookmarkLink});
function mySUBookmarkLink(info, tab) {
chrome.extension.onMessage.addListener(function(msg, sender, sendResponse) {
if (msg.action == 'bookmarkLink') {
chrome.storage.sync.set({'title': msg.linkTitle, 'url': msg.linkUrl}, function(msg) {
alert('Saved '+msg.linkTitle+' to bookmarks');
});
}
});
};
My problems are:
In the first code block, it alerts Saved undefined to bookmarks as soon as I right click on the link, while as I understand it should only send a message on right click and the second code block should alert Saved to bookmarks when I click on the context menu. What am I missing or doing wrong?
I may not have used parameters correctly (I am fairly new to extension development and Javascript in general). Do the above look okay?
Thank you in advance,
K.
It's chrome.runtime.sendMessage and chrome.runtime.onMessage rather than chrome.extension.
There used to be chrome.extension.sendRequest and chrome.extension.onRequest which have been deprecated in favor of the chrome.runtime API methods mentioned above.
See Chrome Extensions - Message Passing
it's JSON-serializable messaging, where first pair is for recognition, and then followed by pairs of
key: value.
You pull the value from received message by calling it's key.
is should be:
alert('Saved '+msg.title+' to bookmarks');
or even better:
function mySUBookmarkLink(info, tab) {
chrome.extension.onMessage.addListener(function(msg, sender, sendResponse) {
if (msg.action == 'bookmarkLink') {
var receivedValue = msg.title; //pull it out first, for better overview
chrome.storage.sync.set({'title': msg.title, 'url': msg.url}, function(msg) {
alert('Saved '+receivedValue+' to bookmarks');
});
}
});
};

emit to all after receiving a broadcast

I've got a little app that generates a code and stores it in mongodb(My chrome browser). Another user(My firefox browser) enters the given code and broadcasts it to let my chrome know that he's there.
Now i want my chrome browser to emit an agreement to itself and my firefox browser so they both get parsed by the same function the moment the agreement is emitted.
The point however is that i only get 1 console log in my terminal which leads me to think that only Chrome(or Firefox, which i doubt) is listening to the emit.
Can anyone take a look why not both browsers receive the 'agreement' emit?
My app.js: (The on connection part)
io.sockets.on('connection', function (socket) {
socket.on('code_game', function (data) {
if (codeToUse == data.code) {
//The receiving end received the proper code from the sending end
console.log(data.secondUser + ' is verbonden via de code: ' + data.code);
//Emit to all parties to let everyone know there's a connection
socket.emit('agreement', {
userOne: {
name: 'Timen',
code: codeToUse
},
userTwo: {
name: data.secondUser,
code: data.code
}
});
}
});
});
And the JS file being called in my view: (sendToFirstUser is Firefox in this case)
var receivingUsersCode = false;
var receivingUsersName = false;
var socket = io.connect('http://localhost');
socket.on('agreement', function (data) {
console.log("hooray");
});
function setReceivingData(code, username) {
receivingUsersCode = code;
receivingUsersName = username;
ding = 'drie';
$('#new_game').css('display', 'block');
$('.theCode').html(receivingUsersCode);
}
function sendToFirstUser(code, username) {
socket.emit('code_game', { code: code, secondUser: username});
}
I'm not sure I understand exactly what you're asking. But it seems to me like you're asking why both your Chrome and Firefox browser aren't emitting an 'agreement' event. If that's it, I think you've answered your own question:
"Another user(My firefox browser) enters the given code and broadcasts it to let my chrome know that he's there."
//Starts broadcasting to other clients
io.sockets.on('connection', function (socket) {
socket.broadcast.emit('code_game', { code: req.body.code, secondUser: req.body.secondUser});
});
Your firefox browser only emits to other clients (your chrome browser) through socket.broadcast.emit. So, only the chrome browser receives the 'code_game' event on the browser side. But in your browser side code, the client emits the 'agreement' event when it receives the 'code_game' event:
socket.on('code_game', function (data) {
if (receivingUsersCode == data.code) {
console.log(data.secondUser + ' is is connected via code: ' + data.code);
listenForMutualConnection();
socket.emit('agreement', {
userOne: {
name: receivingUsersName,
code: receivingUsersCode
},
userTwo: {
name: data.secondUser,
code: data.code
}
});
}
});
Since only the chrome browser is receiving the 'code_game' event, it's also the only one emitting the 'agreement' event.

Categories

Resources