How should a chrome extension background page communicate with multiple content scripts? - google-chrome-extension

I'm having trouble communicating with multiple content scripts from my background page. My background page has code like:
chrome.tabs.sendRequest(tabId, { targetScript:"content1" }, function (resp) {
if (resp.fromCorrectScript) {
DoMoreStuff();
}
});
and I have content scripts like:
// content1.js
chrome.extension.onRequest.addListener(function (sender, request, sendResponse) {
if (request.targetScript === "content1") {
sendResponse({ fromCorrectScript:true });
} else {
sendResponse({});
}
});
and
// content2.js
chrome.extension.onRequest.addListener(function (sender, request, sendResponse) {
if (request.targetScript === "content2") {
sendResponse({ fromCorrectScript:true });
} else {
sendResponse({});
}
});
My understanding is that my callback in the background page should be called twice, once from each content script. It looks like it's only called twice sometimes, and pretty much only when I have a breakpoint at the if clause. Am I doing something wrong here?
Thanks,
-Greg

Well, it looks like it works correctly as long as I ensure that only one content script responds to the message. So my content script code should be more like:
// content1.js
chrome.extension.onRequest.addListener(function (sender, request, sendResponse) {
if (request.targetScript === "content1") {
sendResponse({ fromCorrectScript:true });
}
});
and
// content2.js
chrome.extension.onRequest.addListener(function (sender, request, sendResponse) {
if (request.targetScript === "content2") {
sendResponse({ fromCorrectScript:true });
}
});

I don't know what's the root of the problem, can only guess that whichever script runs callback first destroys it for the rest.
I can suggest workaround though. You can send requests in both directions, not only from background page to script. So your background page might look like:
chrome.tabs.sendRequest(tabId, { targetScript:"content1" });
chrome.extension.onRequest.addListener(function (request, sender, sendResponse) {
if (request.fromCorrectScript) {
DoMoreStuff();
}
});
And in scripts:
chrome.extension.onRequest.addListener(function (request, sender, sendResponse) {
if (request.targetScript === "content1") {
chrome.extension.sendRequest({fromCorrectScript:true});
} else {
chrome.extension.sendRequest({fromCorrectScript:false});
}
});
This shouldn't choke.

Related

Unchecked runtime.lastError: No tab with <id>, Chrome Extension

I don't understand why i recieve this error
Error
This code works
chrome.tabs.onUpdated.addListener(handleUrls);
function handleUrls(tabId, { changeInfo }, tab) {
const isOauthTokenPage = tab.url?.match(CONFIG.OAUTH_TOKEN_PAGE_PATTERN);
if (isOauthTokenPage) {
chrome.tabs.remove(tabId);
}
}
But why i get this error?
I tried chrome.tabs.onUpdated.removeListener before and after chrome.tabs.remove(tabId), tried chrome.tabs.query to get "actual" tabId
So it's trigger more than once
To avoid it
chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
// check url state
if (!changeInfo.url) return;
// your code...
if ((tab.url).match(CONFIG.OAUTH_TOKEN_PAGE_PATTERN)) {
// won't invoke onUpdated several times
chrome.tabs.remove(tabId);
}
});

Using a global variable in the Chrome extension

Consider that when accessing the example.com page the tabs.onUpdated event is executed three times.
background.js
function sendMsn(msn) {
chrome.tabs.query({currentWindow: true, active: true}, function (tabs) {
chrome.tabs.sendMessage(tabs[0].id, { "message": msn });
});
}
var cont = 0;
chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
if(changeInfo.status === 'complete') {
// This runs 3 times
if(cont === 0) {
sendMsn('ok');//This line is not running
cont++;
}
sendMsn(cont);
}
});
content.js
chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
console.log(request);
});
The expected output would be:
Ok
1
2
3
However, what is printed is:
1
1
1
I don't know the reason for the error, but I suspect it's the scope of the cont variable.

chrome.runtime.sendmessage not working in chrome extentions

hello I have a question
I send to backgroud.js from content.js;
but it is append error : Could not establish connection. Receiving end does not exist.
but it works fine that i think
content.js send chrome.runtime.sendMessage
background.js receive chrome.runtime.onMessage.addListener and menifest.json
chrome.runtime.sendMessage is it does not work
"background": { "service_worker": "background.bundle.js" },
"content_scripts": [
{
"matches": ["http://*/*", "https://*/*", "<all_urls>"],
"js": ["contentScript.bundle.js"],
"css": ["content.styles.css"]
}
],
background.js
chrome.runtime.onMessage.addListener(function (request, sender, sendResponse) {
switch (request.action) {
case "item-save": {
chrome.storage.local.get('item', function (itemData) {
let itemList = [];
if (Object.keys(itemData).length > 0) {
itemList = itemData.item;
}
itemList.push(request.source);
chrome.storage.local.set({
item: itemList
});
sendResponse("OK");
});
return true;
}
case "page-main": {
chrome.tabs.create({
url: chrome.runtime.getURL("options.html"),
});
return true;
}
default: return true;
}
});
content.js
button.addEventListener('click', () => {
chrome.runtime.sendMessage({
action : "item-save",
source : item
},function(response){
if(response ==="OK"){
let ok = confirm("check")
// if(ok){
// chrome.runtime.sendMessage({
// action : "page-main",
// });
// }
}
})
})
what's wrong?
It's a common chrome.runtime.onMessage bug that hopefully gets fixed one day.
I guess it gives an error when request.action is "page-main", since it's waiting for a sendResponse.
I think the solution would be to add an empty sendResponse() on "page-main".
You can also just use a return true; outside the switch and make it less redundant.:
chrome.runtime.onMessage.addListener(function (request, sender, sendResponse) {
switch (request.action) {
case "item-save": {
chrome.storage.local.get("item", function (itemData) {
// your code here ...
sendResponse("OK");
});
break;
}
case "page-main": {
chrome.tabs.create({
url: chrome.runtime.getURL("options.html"),
});
sendResponse(); // or try with sendResponse({})
break;
}
}
return true;
});
Also the chrome.storage functions are asynchronous.
So on "item-save" the sendResponse("OK"); will be executed before the chrome.storage.local.set.
If you want to run sendResponse after saving the itemList, it would be something like this:
chrome.storage.local.set({
item: itemList,
}, function () {
sendResponse("OK");
});
I hope it works! :)

Stop callback chain and send notification beforeSave method ApostropheCMS

I'm trying to prevent the user to save a piece if it doesn't achieve some requirements.
Currently I'm doing it like this:
self.beforeSave = function(req, piece, options, callback) {
let success = true;
let error = "";
if (Array.isArray(piece._subevents) && piece._subevents.length) {
success = self.checkDateAndTimeCompabilitiyWithChildren(piece);
}
if (!success) {
self.apos.notify(req, "Check the compatibility between parent event and subevents", { type: "error" });
error = "Subevents are not compatible with parent event";
}
callback(error);
};
This works but the problem is it shows 2 errors notifications (the default and my custom), 1 because of callback(error) and 1 because of apos.notify.
Any idea how to stop the item of being saved and only show my notification?
Thanks in advance.
UPDATE 1:
As Tom pointed out, my code looks like this now:
// lib/modules/events/public/js/editor-modal.js
apos.define('events-editor-modal', {
extend: 'apostrophe-pieces-editor-modal',
construct: function(self, options) {
self.getErrorMessage = function(err) {
if (err === 'incompatible') {
apos.notify('A message suitable for this case.', { type: 'error' });
} else {
apos.notify('A generic error message.', { type: 'error' });
}
};
}
});
// lib/modules/events/index.js
var superPushAssets = self.pushAssets;
self.pushAssets = function() {
superPushAssets();
self.pushAsset("script", "editor-modal", { when: "user" });
};
self.beforeSave = async function(req, piece, options, callback) {
return callback("incompatible")
};
For testing purposes I'm just returning the error in beforeSave. The problem is that an exception is being thrown in the browser console and the modal is not properly rendered again. Here's a screenshot about what I'm talking:
I'm trying to debug it and understand what's happening but no clue yet.
In your server-side code:
self.beforeSave = function(req, piece, options, callback) {
let success = true;
if (Array.isArray(piece._subevents) && piece._subevents.length) {
success = self.checkDateAndTimeCompabilitiyWithChildren(piece);
}
if (!success) {
return callback('incompatible');
}
return callback(null);
};
And on the browser side:
// in lib/modules/my-pieces-module/public/js/editor-modal.js
apos.define('my-pieces-module-editor-modal', {
extend: 'apostrophe-pieces-editor-modal',
construct: function(self, options) {
self.getErrorMessage = function(err) {
if (err === 'incompatible') {
return 'A message suitable for this case.';
} else {
return 'A generic error message.';
}
};
}
});
If the error reported by the callback is a string, it is passed to the browser. The browser can then recognize that case and handle it specially. 'my-pieces-module-editor-modal' should be substituted with the name of your pieces module followed by -editor-modal.

Catch "Failed to look up view" error and serve 404

I am currently routing every page to my pagesController that cannot be found in previous routes by including this line in routes.js:
this.match('/:page', { controller: 'pages', action: 'show' });
I had the idea to let my PagesController handle serving a 404 if not found:
PagesController.show = function() {
var page = this.param('page');
if(page != undefined){
page = page.replace(".html","");
try {
this.render("./"+page);
} catch(error){ //Failed to look up view -- not working at the moment =(
this.redirect({action : "404"});
};
}
return;
};
But my idea is failing. The error cannot be caught, so the fatal still gets served. Should I append a fn to the render call? With what arguments? How does it work? (/simple questions).
It might look something like this:
PagesController.show = function() {
var self = this;
var page = this.param('page');
if (page !== undefined) {
page = page.replace('.html', '');
this.render("./" + page, function(err, html) {
if (! err)
return self.res.send(html);
self.redirect({ action : '404' });
});
} else {
// this will probably never be called, because `page`
// won't be undefined, but still...
this.redirect({ action : '404' });
}
};

Resources