chrome.runtime.sendmessage not working in chrome extentions - google-chrome-extension

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! :)

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.

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.

Flux eventemitter

I am currently trying out the FLUX design pattern and I have stumpled upon an issue. In this project I am also using ReactJS to go with it. It is working fine to a point where it is almost done, but screws up. So I am starting the function(s) with a button that I render, when clicked it fires the chain of functions.
render: function(){
return (
<div><button onClick={this.waiting}>Hello</button>
{this.state.results}
</div>
)
}
you can now see that when this button is clicked, it fires a function called waiting() which contains the following
waiting: function() {
actions.trending();
return this.state.results;
},
So it fires the function and the following happens
var actions = {
trending: function(){
api.trending()
.then(function(result){
dispatcher.dispatch({
actionType: actionConstants.TRENDING_RESULT,
results: result
});
}, function(error){
console.log(error);
dispatcher.dispatch({
actionType: actionConstants.ERROR,
error: error
});
});
}
};
which is all working fine, I am getting my data and I am happy so far, problem is what happens next, when the dispatcher dispatches the actionType along with the data, this goes into the store I have. In my store-file I am then registering the payload(action).
dispatcher.register(function(action){
switch (action.actionType) {
case actionConstants.TRENDING_RESULT:
console.log(action.results); <- I can actually see my data
results = action.results;
resultErrors = null;
SearchStore.emit(actionConstants.TRENDING_RESULT); <- Error
break;
case actionConstants.ERROR:
results = null;
resultErrors = action.error;
console.log(resultErrors);
SearchStore.emit(actionsConstants.ERROR);
break;
}
});
So at this point I can see my data in the console.log but I am getting an error at the emit function that sounds as following
Uncaught (in promise) TypeError: this._events[evt].push
for my store functions I use the following
var SearchStore = {
getTrending: function() {
return JSON.stringify(results);
},
getError: function() {
return resultErrors;
},
emit: function(event) {
eventEmitter.on(event);
},
on: function(event, callback) {
eventEmitter.on(event, callback);
},
removeListener: function(event, callback) {
eventEmitter.removeListener(event, callback);
}
};
and finally to pick up on any emits I call my on function in ComponentDidMount that looks like this
componentDidMount: function(){
SearchStore.on(actionConstants.TRENDING_RESULT, this.loadResults());
SearchStore.on(actionConstants.ERROR, this.showError());
},
componentWillUnmount: function(){
SearchStore.removeListener(actionConstants.TRENDING_RESULT, this.loadResults());
SearchStore.removeListener(actionConstants.ERROR, this.showError());
},
For the Dispatcher I am using Facebooks FLUX dispatcher and for the emitter I am using eventemitter3. Everything is going smoothly up until I try to emit TRENDING_RESULT and and the payload with it. I am terribly sorry about the length of this question, but I wanted to be as thorough as I possibly could for your understanding.
The event emitter should call emit function not the on function.
So it should be something like:
var SearchStore = {
getTrending: function() {
return JSON.stringify(results);
},
getError: function() {
return resultErrors;
},
emit: function(event) {
eventEmitter.emit(event); // HERE!!
},
on: function(event, callback) {
eventEmitter.on(event, callback);
},
removeListener: function(event, callback) {
eventEmitter.removeListener(event, callback);
}
};
My solution was as following:
'use strict';
var dispatcher = require('../dispatcher/dispatcher');
var EventEmitter = require('events').EventEmitter;
var ObjectAssign = require('object-assign');
var actionConstants = require('../constants/actionConstants');
var _store = {
list: [],
error: [],
genres: [],
playlists: []
}
var resultErrors = null;
var CHANGE_EVENT = 'change';
var SearchStore = ObjectAssign( {}, EventEmitter.prototype, {
getTrending: function() {
return _store;
},
getError: function() {
return _store;
},
addChangeListener: function(callback){
this.on(CHANGE_EVENT, callback);
},
removeListener: function(callback) {
this.removeListener(CHANGE_EVENT, callback);
},
getGenres: function() {
return _store;
},
getPlaylists: function() {
return _store;
}
});
dispatcher.register(function(action){
switch (action.actionType) {
case actionConstants.TRENDING_RESULT:
action.results.map(function(item){
_store.list.push(item);
});
resultErrors = null;
SearchStore.emit(CHANGE_EVENT);
break;
case actionConstants.SEARCH_RESULT:
_store.list = [];
console.log(_store.list);
action.results.map(function(item){
_store.list.push(item);
});
resultErrors = null;
SearchStore.emit(CHANGE_EVENT);
break;
case actionConstants.ERROR:
results = null;
_store.error.push(action.error);
SearchStore.emit(CHANGE_EVENT);
break;
case actionConstants.ADD_GENRE:
_store.genres.push(action.index);
SearchStore.emit(CHANGE_EVENT);
break;
case actionConstants.REMOVE_GENRE:
_store.genres = _store.genres.filter(function(index){
return index !== action.index;
});
console.log(_store.genres);
SearchStore.emit(CHANGE_EVENT);
break;
case actionConstants.SAVE_PLAYLIST:
var playlists = {
"name": action.index,
"items": {}
}
;
_store.playlists.push(playlists);
SearchStore.emit(CHANGE_EVENT);
break;
default:
}
});
So I eliminated my my "emit" and "on" function all together and made a addChangeListener. so I emit straight from my switch cases and since "emit" is a already a function I don't need to create a function for a "emit" function. I just use my listener to pick up on CHANGE_EVENT and then it will run a callback function. Example:
componentDidMount: function(){
SearchStore.addChangeListener(this.loadPlaylists);
},
componentWillUnmount: function(){
SearchStore.removeListener(this.loadPlaylists);
},
As it is now it works perfectly, I understand that if I had just looked thoroughly through my code I could have been saved the trouble but the more you learn right? Anyways thanks for your time yet again.

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

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.

Resources