Multiple messages in a single function call to chrome.runtime.sendMessage(); - javascript

chrome.runtime.sendMessage();
I want to pass multiple (to be specific 2) messages from my contentscript.js to popup.js.
I don't need other arguments for this function, I only need the message argument.
In my contentscript.js I have this:
chrome.runtime.sendMessage(message1);
chrome.runtime.sendMessage(message2);
Here is my popup.js:
chrome.runtime.onMessage.addListener(function(messsage1){
//code for handling the message
});
chrome.runtime.onMessage.addListener(function(messsage2){
//code for handling the message
});
I want to combine these two functions into a single function and handle the messages like:
chrome.runtime.onMessage.addListener(function(){
// how to write the parameter for this function, I can't use ',' right?
// code for handling the message1, message2
});
How can I do this?

I would send the messages as JSON objects, with an additional property to specify which type of message it is. For example:
chrome.runtime.sendMessage({content: "Message1", type: "m1"});
chrome.runtime.sendMessage({content: "Message2", type: "m2"});
And then you can combine the message listener into one function:
chrome.runtime.onMessage.addListener(function(message) {
if(message.type == "m1") {
console.log("First message: ", message.content);
}
if(message.type == "m2") {
console.log("Second message: ", message.content);
}
}
Of course this is just a rough example - you should tailor the structure of the JSON objects to your extension's requirements, but this is the pattern that I would use.

contentscript.js
chrome.runtime.sendMessage({
greeting: "message1"
})
chrome.runtime.sendMessage({
greeting: "message2"
})
popup.js
chrome.runtime.onMessage.addListener(
function (request, sender, sendResponse) {
if (request.greeting == "message1") {
//action 1
} else if (request.greeting == "message2") {
//action 2
}
});
API: http://developer.chrome.com/extensions/messaging

runtime.sendMessage can be used only for one-time requests as per documentation. Consider using tabs.connect instead.
This is the code for the extension page to connect to your tab:
var port = chrome.tabs.connect(tab.id);
port.postMessage({type: "first", content: "Hi!"});
port.postMessage({type: "second", content: "How are you?"});
And this code should be specified in the content script:
chrome.runtime.onConnect.addListener(function(port) {
port.onMessage.addListener(function(msg) {
if (msg.type == 'first') {
...
}
if (msg.type == 'second') {
...
}
});
};

Related

How to pass Data between files Chrome Extension?

Currently, I mainly work with two files, background.js and popup.js.
In background.js
I have a bunch of functions that let me store data in an IndexedDB. And in popup.js I call the functions like this:
chrome.runtime.sendMessage({
message: "insert",
payload: [
{
url: form_data.get("message"),
text: form_data.get("message"),
},
],
});
Depending on the message, a certain function is called. When the function has successfully executed I do this from the background.js file:
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
if (request.message === "insert") {
let insert_request = insert_records(request.payload);
insert_request.then((res) => {
chrome.runtime.sendMessage({
message: "insert_success",
payload: res,
});
});
});
This is my problem:
How do I send data from background.js to popup.js. What I want is to get the URL of the current page, and then send it to popup.js and store it in the Database.
I have already looked at already existing posts, but none of them really helped.
Can someone please help me out.
Update
Currently I use this is in background.js to get the current URL. It works just fine. But how can I pass the tab.url to my popup.js file?:
let activeTabId, lastUrl, lastTitle;
function getTabInfo(tabId) {
chrome.tabs.get(tabId, function (tab) {
if (lastUrl != tab.url || lastTitle != tab.title)
console.log((lastUrl = tab.url), (lastTitle = tab.title));
});
}
chrome.tabs.onActivated.addListener(function (activeInfo) {
getTabInfo((activeTabId = activeInfo.tabId));
});
chrome.tabs.onUpdated.addListener(function (tabId, changeInfo, tab) {
if (activeTabId == tabId) {
getTabInfo(tabId);
}
});

Creating and accessing global variable in google chrome extension

All of the information I can find on this is pretty old. Like the title says I am trying to make a global variable in one script and access it from another. The purpose of the extension is to search for a class named "page-title" and then return the innerHTML of that HTML element. Once I get the code working I will specify the URL I want the extension to run on so it's not constantly running.
After a couple iterations trying to accomplish this in different ways I followed the method explained in this answer but my needs have different requirements and I am receiving the error "Unchecked runtime.lastError: Could not establish connection. Receiving end does not exist." tied to the popup.html.
I tried the Unchecked runtime error solution found here but it's been awhile (~ 7 years) since I've dived into any coding and I'm not sure I implemented it correctly.
I've also tried to pass the value between JS documents is the HTML injection method, but without overriding security defaults in the manifest that doesn't really work. It also seemed super bootstrappy and I wanted to pass the information in a more conventional way. I tried creating a global variable by simply declaring the variable outside of a function/class/if statement and loading that .js file first, but that was unsuccessful as well.
Manifest
"name": "P.P. to Sharepoint",
"version": "1.0.0",
"description": "Open P.P. client folder in sharepoint",
"manifest_version": 3,
"author": "Zach Morris",
"action":{
"default_popup": "popup.html",
"default_title": "Open Sharepoint Folder"
},
"background": {
"service_worker": "background.js"
},
"permissions": [
"activeTab",
"tabs",
"scripting",
"notifications"
],
"content_scripts": [{
"js": ["contentScript.js"],
"matches": ["<all_urls>"]
}]
}
popup.html
My popup.html is super simple and really just has a button to press. I included all the .js files in the order I thought necessary
<script src="globalVariable.js"></script>
<script src="contentScript.js"></script>
<script src="popup.js"></script>
<script src="script.js"></script>
<script src="background.js"></script>
globalVariable.js
This one is straight forward. I need to pull the client's name out of the HTML of the page then use it in an API call when I click the button in popup.js This initializes the variable and uses it as place holder.
var clientInfo = {
name: 'test name'
};
ContentScript.js
I only want to run this if importScripts is not undefined. So I threw it in the if statement. Then I make sure I pulled a client name from the page. If not I throw an error message saying no client was found.
if( 'function' === typeof importScripts) {
importScripts('globalVariable.js');
addEventListener('message', onMessage);
function onMessage(e) {
if(b[0]) {
clientInfo.name = b[0].innerHTML;
alert(clientInfo.name + ' was assigned!');
} else {
alert('There is no client on this screen ' + 'b[0] is ' + b[0] + " clientInfo = " + clientInfo.name);
};
};
} else {
console.log("Your stupid code didn't work. ");
}
popup.js
This one pulls up the globalVariable.js to use the clientInfo. and makes a call to the button in background.js
if( 'function' === typeof importScripts) {
importScripts('globalVariable.js');
addEventListener('message', onMessage);
function onMessage(e) {
const text = clientInfo.name;
const notify = document.getElementById( 'myButton' );
notify.addEventListener( 'click', () => {
chrome.runtime.sendMessage( '', {
type: 'notification',
message: text });
} );
}
}
background.js
Same thing here. I import the globalVariable script to use the global variable. The notification will eventually be replaced with the API call when the rest of the code is working properly. I probably don't need to import the script here to access the variable because I can mass it with the event listener in popup.js, but I put it in here out of desperation.
if( 'function' === typeof importScripts) {
importScripts('globalVariable.js');
addEventListener('message', onMessage);
function onMessage(e) {
// do some work here
chrome.runtime.onMessage.addListener( data => {
if ( data.type === 'notification' ) {
chrome.notifications.create(
'',
{
type: 'basic',
title: 'Notify!',
message: data.message || 'Notify!',
iconUrl: 'notify.png',
}
);
console.log("sent notification");
};
});
}
}
You can have the popup.js listen for a button click and content.js handle all the logic of finding the correct element.
popup.js
document.querySelector('#btn').addEventListener('click', () => {
chrome.tabs.query({ active: true, currentWindow: true }, (tabs) =>
chrome.tabs.sendMessage(tabs[0].id, { command: 'getClientName' })
);
});
content.js
chrome.runtime.onMessage.addListener((msg, sender, response) => {
if (msg.command === 'getClientName')
findClientName(document.querySelectorAll('h3.page-title'));
});
Example of findClientName function:
const findClientName = async (element) => {
let clientName;
if (element.length > 0) {
element.length === 1
? (clientName = setClientName(element[0]))
: handleMultipleElements(element);
} else {
handleNoClientNameFound();
}
clientName ? await makeAPIRequest(clientName) : null;
};
Try this method instead maybe?
{
var x = 2;
}
so:
{
var clientInfo = {
name: 'test name'
};
}
Not very good at this language, so I thought maybe you're missing the brackets?

Multiple function call to chrome.runtime.sendMessage() with synchronous behaviour

I connect content.js with background.js to do 2 different tasks: inject local HTML and fetch data from another webpage.
Currently, the createContainer() starts after fetchweb() is done and I am not sure why (I need createContainer() to run first). I tried to transform both functions into Promise but still the same result
Content.js
function createContainer1() {
// call html file container
chrome.runtime.sendMessage({ cmd: "read_cont1" }, function (html) {
$("#container1").html(html);
});
// more code
}
function fetchWeb() {
chrome.runtime.sendMessage(
{ cmd: "send_url", url: window.location.href},
function (response) {
console.log(JSON.stringify(response));
}
);
}
createContainer1()
fetchWeb()
background.js
chrome.runtime.onMessage.addListener(function (request, sender, sendResponse) {
if (request.cmd == "read_cont1") {
$.ajax({
url: chrome.extension.getURL("container1.html"),
dataType: "html",
success: sendResponse,
});
return true;
} else if (request.cmd == "send_url") {
sendResponse({ review_url: fetchData(request.url) });
return true;
}
});
Your two sendMessages are both asynchronous functions and--unless specifically dealing with asynchronous coding through callbacks, promises, or async/await--I don't think there's any other way to guarantee which resolves first.
If fetchWeb should run every time after createContainer sends its message, you could add fetchWeb to sendMessage's callback (and then remove it from your main body):
...chrome.runtime.sendMessage({ cmd: "read_cont1" }, function (html) {
$("#container1").html(html);
fetchWeb();
});...
If fetchWeb should only sometimes run, you could pass data into the createContainer function answering that question:
function createContainer1(executeFetchWeb) {
// call html file container
chrome.runtime.sendMessage({ cmd: "read_cont1" }, function (html) {
$("#container1").html(html);
if (executeFetchWeb) {fetchWeb()}
});
// more code
}
If there's something else happening in "//more code" that needs to happen before fetchWeb runs, it would be helpful to see that. But unless that code is asynchronous as well, I imagine that code is already executing first. This could all be done with promises as well, but sendMessage is already setup to work well with callbacks. From the documentation:
chrome.runtime.sendMessage(
extensionId?: string,
message: any,
options?: object,
responseCallback?: function,
)

Sinon stub callFake function not replacing original function

So this is the (snipped) code for a chatbot. I want to override the sendMessage() function to just echo the message argument. In this case, the original function runs and gives an error at the 2nd line of the function. Obviously, modules aren't loaded and I don't need them to. This is a test for the eventHandler to echo the right messages. Ideas?
var modules = require('./modules');
console.log('[tose] Loading modules: ', Object.keys(modules));
function eventHandler(channel, type, data, react=()=>{}) {
switch (type) {
case 'new_message':
console.log('[tose][new_message]', channel, 'from:', data.cid, 'message:', data.message);
if (regexTemplates.testSearch.test(data.message.toLowerCase())) {
...
} else {
sendMessage(channel, data.cid, data.message); // Basic echo message
}
break;
}
}
// The function to be stubbed
function sendMessage(channel, cid, message) {
console.log('[tose][send_message]', channel, 'to:', cid, 'message:', message);
coms[channel].sendMessage(cid, message); // Getting error here thus not really stubbed
}
exports.eventHandler = eventHandler;
exports.sendMessage = sendMessage
And the test:
describe('Tose core', function() {
describe('Process messages', function() {
before(function() {
var stub = sinon.stub(tose, 'sendMessage').callsFake(function(channel, cid, message) {
assert.equal(message, 'Test message');
return message
});
});
after(function() {
tose.sendMessage.restore();
});
it('should echo messages', function() {
var data = {message: 'Test message'}
tose.eventHandler('test', 'new_message', data)
assert(tose.sendMessage.calledOnce);
});
});
});
The problem here is that when you use Sinon to stub an object's function, you're stubbing that (and only that) object's function.
Your code (the first code block) is using the local definition of the sendMessage function.
When you stub the tose object (in the second code block), you are changing the sendMessage function thats on the tose object and not the local definition of the function.
There are many different ways you could approach this, one of which is:
var modules = require('./modules');
var functions = {
eventHandler: eventHandler,
sendMessage: sendMessage,
};
console.log('[tose] Loading modules: ', Object.keys(modules));
function eventHandler(channel, type, data, react=()=>{}) {
switch (type) {
case 'new_message':
console.log('[tose][new_message]', channel, 'from:', data.cid, 'message:', data.message);
if (regexTemplates.testSearch.test(data.message.toLowerCase())) {
...
} else {
functions.sendMessage(channel, data.cid, data.message); // Basic echo message
}
break;
}
}
// The function to be stubbed
function sendMessage(channel, cid, message) {
console.log('[tose][send_message]', channel, 'to:', cid, 'message:', message);
coms[channel].sendMessage(cid, message); // Getting error here thus not really stubbed
}
module.exports = functions;
Note: functions is not a descriptive name - feel free to change it to something that is more meaningful.

May i use sendResponce/onMessage within event page? (bug?)

Seems like i can send message, but can't receive response. Everything works fine when i use sendMessage from another context (popup, content script, etc).
Is it bug?
event-page.js
(function (chrome, undefined) {
'use strict';
chrome.runtime.onMessage.addListener(function (message, sender, sendResponse) {
console.info('onMessage: ' + message.method);
switch (message.method) {
case 'withResponseAsync':
setTimeout(function () {
sendResponse({some: 'response'});
}, 1000);
return true;
break;
case 'withResponse':
sendResponse({some: 'response'});
break;
}
});
var showResponse = function (response) {
if (chrome.runtime.lastError) {
console.error(chrome.runtime.lastError.message);
}
console.info(response);
};
// ok. onMessage: noResponse
chrome.runtime.sendMessage({method: 'noResponse'});
// fail. Could not establish connection. Receiving end does not exist.
chrome.runtime.sendMessage({method: 'withResponse'}, showResponse);
// fail. Could not establish connection. Receiving end does not exist.
chrome.runtime.sendMessage({method: 'withResponseAsync'}, showResponse);
})(chrome);

Categories

Resources