Creating and accessing global variable in google chrome extension - javascript

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?

Related

What's the best way to call a content scripts' function from the background script in a Firefox extension?

I want to call a function that is implemented in the content script of an extension, that gets the selected text from webpages, from a function in the background script that will be later called in a listener connected to a menu item.
Is that possible and what would be the shortest way to do it?
Here are the relevant code snippets:
manifest.json
"background": {
"scripts": ["background.js"]
},
"content_scripts": [
{
"matches": ["<all_urls>"],
"js": ["content.js"]
}
]
content.js
var text = "";
function highlightedText() {
text = content.getSelection();
}
background.js
function listenerFunction() {
highlightedText();
/* Doing various stuff that have to use the text variable */
}
browser.menus.onClicked.addListener((info, tab) => {
highlightedText();
});
Obviously, the above code is not working as the "highlighted" function is now visible from the background script.
So, what's the quickest / shortest way to make the code work?
OK. I'm having to crib this from one of my own private extensions but the gist is this:
In the background script set up the menu, and assign a function to the onclick prop:
browser.menus.create({
id: 'images',
title: 'imageDownload',
contexts: ['all'],
onclick: downloadImages
}, onCreated);
Still in the same script get the current tab information, and send a message to the content script.
function getCurrentTab() {
return browser.tabs.query({ currentWindow: true, active: true });
}
async function downloadImages() {
const tabInfo = await getCurrentTab();
const [{ id: tabId }] = tabInfo;
browser.tabs.sendMessage(tabId, { trigger: 'downloadImages' });
}
The content script listens for the message:
browser.runtime.onMessage.addListener(data => {
const { trigger } = data;
if (trigger === 'downloadImages') doSomething();
});
And once the processing is done pass a new message back to the background script.
function doSomething() {
const data = [1, 2, 3];
browser.runtime.sendMessage({ trigger: 'downloadImages', data });
}
And in a separate background script I have the something like the following:
browser.runtime.onMessage.addListener(data => {
const { trigger } = data;
if (trigger === 'downloadImages') ...
});

How To Call Chrome Extension Function After Page Redirect?

I am working on building a Javascript (in-browser) Instagram bot. However, I ran into a problem.
If you run this script, the first function will be called and the page will be redirected to "https://www.instagram.com/explore/tags/samplehashtag/" and the second function will be called immediately after (on the previous URL before the page changes to the new URL). Is there a way to make the second function be called after this second URL has been loaded completely?
I have tried setting it to a Window setInterval() Method for an extended time period, window.onload and a couple of other methods. However, I can't seem to get anything to work. Any chance someone has a solution?
This is my first chrome extension and my first real project, so I may be missing something simple..
manifest.json
{
"name": "Inject Me",
"version": "1.0",
"manifest_version": 2,
"description": "Injecting stuff",
"homepage_url": "http://danharper.me",
"background": {
"scripts": [
"background.js"
],
"persistent": true
},
"browser_action": {
"default_title": "Inject!"
},
"permissions": [
"https://*/*",
"http://*/*",
"tabs"
]
}
inject.js
(function() {
let findUrl = () => {
let hashtag = "explore/tags/samplehashtag/";
location.replace("https://www.instagram.com/" + hashtag);
}
findUrl();
})();
background.js
// this is the background code...
// listen for our browerAction to be clicked
chrome.browserAction.onClicked.addListener(function(tab) {
// for the current tab, inject the "inject.js" file & execute it
chrome.tabs.executeScript(tab.ib, {
file: 'inject.js'
});
});
chrome.tabs.onUpdated.addListener(function(tabId, changeInfo, tab) {
chrome.tabs.executeScript(tab.ib, {
file: 'inject2.js'
});
});
inject2.js
(function() {
if (window.location.href.indexOf("https://www.instagram.com/explore/tags/samplehashtag/") != -1){
let likeAndRepeat = () => {
let counter = 0;
let grabPhoto = document.querySelector('._9AhH0');
grabPhoto.click();
let likeAndSkip = function() {
let heart = document.querySelector('.glyphsSpriteHeart__outline__24__grey_9.u-__7');
let arrow = document.querySelector('a.coreSpriteRightPaginationArrow');
if (heart) {
heart.click();
counter++;
console.log(`You have liked ${counter} photographs`)
}
arrow.click();
}
setInterval(likeAndSkip, 3000);
//alert('likeAndRepeat Inserted');
};
likeAndRepeat();
}
})();
It is not clear from the question and the example, when you want to run your function. But in chrome extension there is something called Message Passing
https://developer.chrome.com/extensions/messaging
With message passing you can pass messages from one file to another, and similarly listen for messages.
So as it looks from your use case, you can listen for a particular message and then fire your method.
For example
background.js
chrome.runtime.sendMessage({message: "FIRE_SOME_METHOD"})
popup.js
chrome.runtime.onMessage.addListener(
function(request) {
if (request.message == "FIRE_SOME_METHOD")
someMethod();
});
EDIT
Also if you want to listen for the URL changes, you can simply put a listener provided as in the documentation.
chrome.tabs.onUpdated.addListener(function(tabId, changeInfo, tab) {
console.log('updated tab');
});

Problem stopping CORB from blocking requests

I have a Chrome extension that scrapes some values from a webpage based on some query selectors that are provided via an API call.
Relevant portion of manifest.json:
"background": {
"scripts": ["js/background.js"],
"persistent": false
},
"content_scripts": [
{
"matches": ["<all_urls>"],
"js": ["js/jquery.min.js"]
}
],
"permissions": [
"<all_urls>",
"storage",
"activeTab"
]
}
js/background.js:
The idea here is that if a user has entered an atsmap value on their options page, we should perform an API call.
chrome.storage.sync.get(['atsmap'], function(result) {
if (result.atsmap) {
var url = "https://myurl.com/AtsMapping.aspx?AtsCode=" +
encodeURIComponent(result.atsmap)
fetch(url).then(r => r.text()).then(text => {
console.log(text);
response = JSON.stringify(text);
chrome.storage.sync.set({"fieldmapping": response}, function() {
console.log('Fieldmapping is set to ' + response);
});
})
}
return true;
});
This portion appears to be working properly, here is the console from the background page:
In popup.js (which is included at the bottom of popup.html), I call an inject.js script after the DOM is loaded:
// DOM Ready
$(() => {
'use strict';
chrome.tabs.executeScript({file: 'js/inject.js'}, () => {
// We don't need to inject code everwhere
// for example on chrome:// URIs so we just
// catch the error and log it as a warning.
if (chrome.runtime.lastError) {
console.warn(chrome.runtime.lastError.message);
}
});
// injected code will send an event with the parsed data
chrome.runtime.onMessage.addListener(handleInjectResults);
});
And finally, in js/inject.js, I get the value of fieldmapping from storage and attempt to use it:
(function () {
'use strict';
let fieldmap;
let message;
console.log("test");
chrome.storage.sync.get(['atsmap'], function(result) {
if (result.atsmap) {
chrome.storage.sync.get(['fieldmapping'], function(result) {
console.log('Value currently is ' + result.fieldmapping);
fieldmap = JSON.parse(result.fieldmapping);
console.log(fieldmap);
// <key> : { // ID of input on popup.js
// selector: <selector> // DOM selector of value in page
// value: <value> // value to use in popup.js
// }
if(fieldmap.AtsMapping[4].atsMapNotes == 'John Smith (2)') {
message = {
txtLName: {
selector: fieldmap.AtsMapping[6].lastName,
value: null
},
When I go to a demo page that I've setup for the scraping, then click my extension icon, rather than scraping the page for the form values, I get the following in the console:
I don't understand how, on inject.js line 32, I can console.log(fieldmap); and get what appears to be the proper response, and yet on inject.js line 39, the same fieldmap is undefined.
Any suggestions would be helpful as I'm completely lost here.

"Receiving end does not exist" when passing message to injected content script

In an add-on for Firefox, I'm trying to inject code from a background script into a tab and then pass a message to it. Unfortunately, the content script seems to add the listener only after the message has already been sent, resulting in in error. What am I missing? Here is my sample code:
manifest.json:
{
"description": "Test background to content message passing",
"manifest_version": 2,
"name": "Background content message passing",
"version": "0.1.0",
"default_locale": "en",
"applications": {
"gecko": {
"id": "bcm#example.com",
"strict_min_version": "51.0"
}
},
"permissions": [
"contextMenus",
"<all_urls>"
],
"background": {
"scripts": ["background.js"]
}
}
background.js:
"use strict";
const {contextMenus, i18n, runtime, tabs} = browser;
contextMenus.onClicked.addListener(function(info, tab) {
if (info.menuItemId == "bgd-cnt-msg") {
tabs.executeScript(tab.id, {
file: "/content.js",
})
.then(runtime.sendMessage({"result": 42}))
.then(console.log("Debug: runtime message sent"))
.catch(console.error.bind(console));
}
});
contextMenus.create({
id: "bgd-cnt-msg",
title: "Test message passing",
contexts: ["all"],
documentUrlPatterns: ["<all_urls>"]
});
content.js
"use strict";
console.log("Debug: executing content script");
browser.runtime.onMessage.addListener(function (message) {
console.log("Debug: received message %O", message);
});
console.log("Debug: added listener");
The result of selecting the the context menu entry is
Debug: runtime message sent background.js:11:15
Debug: executing content script content.js:3
Debug: added listener content.js:9
Error: Could not establish connection. Receiving end does not exist. undefined
I.e., the context scripts executes after the message to the tab is sent. How can I add the listener before sending the message?
As suggested by #Thắng, I changed my code to use tabs.sendMessage instead of runtime.sendMessage:
contextMenus.onClicked.addListener(function(info, tab) {
if (info.menuItemId == "bgd-cnt-msg") {
tabs.executeScript(tab.id, {
file: "/content.js",
})
.then(tabs.sendMessage(tab.id, {"result": 42}))
.then(console.log("Debug: runtime message sent"))
.catch(console.error.bind(console));
}
});
Now the error is reported a little earlier:
Debug: runtime message sent background.js:11:15
Error: Could not establish connection. Receiving end does not exist. undefined
Debug: executing content script content.js:3
Debug: added listener content.js:9
Thanks to #Thắng, who provided a working solution, I fixed my code to not only use tabs.sendMessage but also pass functions for the callbacks:
contextMenus.onClicked.addListener(function(info, tab) {
if (info.menuItemId == "bgd-cnt-msg") {
tabs.executeScript(tab.id, {
file: "/content.js",
})
.then(function () { tabs.sendMessage(tab.id, {"result": 42}) })
.then(function () { console.log("Debug: runtime message sent") })
.catch(console.error.bind(console));
}
});
With an additional fix in content.js
browser.runtime.onMessage.addListener(function (message) {
console.log("Debug: result is " + message.result);
});
I now get
Debug: executing content script content.js:3
Debug: added listener content.js:9
Debug: runtime message sent background.js:11:15
Debug: result is 42 content.js:6
In background script, you need to let it know it should send the message to which tab, so don't use runtime.sendMessage for this.
var sending = chrome.tabs.sendMessage(
tabId, // integer
message, // any
options // optional object
)
See more here (for webExtensions but also compatible with Chrome): https://developer.mozilla.org/en-US/Add-ons/WebExtensions/API/tabs/sendMessage
Your fully working extension here (you may need to change all the browser.* to chrome.*):
https://drive.google.com/file/d/1KGf8tCM1grhhiC9XcHOjsrbBsIZGff3e/view?usp=sharing

Chrome Extension - Channels Not Working

I am trying to create a channel to my Google App Engine (Python) server, and there seems to be a problem but I am unsure why. When the user toggles the extension, it authenticates the user. If successful, the server replies with a channel token which I use to create the channel. When I authenticate the user, alert("a") appears, but alert("b") does not which makes me believe there is a problem with the line var channel = new goog.appengine.Channel(msg.token);, but the console does not report an error.
I have also copied the javascript code from here and placed it in my manifest as oppose to putting <script type="text/javascript" src="/_ah/channel/jsapi"></script> in background.html.
//script.js
function authenticate(callback) {
var url = "https://r-notes.appspot.com/init/api/authenticate.json?username=" + username + "&password=" + password;
$.post(url, function(data) {
if (data.status == "200") {
channelToken = data.channeltoken;
if (callback) {
callback();
}
var port = chrome.extension.connect({name: "myChannel"});
port.postMessage({token: channelToken});
port.onMessage.addListener(function(msg) {
console.log(msg.question);
});
}
});
}
//background.html
chrome.extension.onConnect.addListener(function(port) {
port.onMessage.addListener(function(msg) {
alert("a"); //pops up
var channel = new goog.appengine.Channel(msg.token);
alert("b"); //does not pop up
console.log(channel); //display error ' Error in event handler for 'undefined': ReferenceError: goog is not defined '
var socket = channel.open()
socket.onopen = function() {
// Do stuff right after opening a channel
console.log('socket opened');
}
socket.onmessage = function(evt) {
// Do more cool stuff when a channel message comes in
console.log('message recieved');
console.log(evt);
}
});
});
//manifest.json
{
"name": "moot",
"description": "Clicking on the moot button will display a sidebar!",
"version": "0.2.69",
"background_page": "html/background.html",
"browser_action": {
"default_icon": "img/icon_64.png",
"default_title": "moot"
},
"content_scripts": [
{
"matches": ["<all_urls>"],
"js": ["js/channelApi.js",
"js/script.js", "js/mootsOnSidebar.js", "js/mootsOnPage.js", "js/authenticate.js", "js/otherFunctions.js",
"js/jquery/jquery-1.7.1.js", "js/jquery/jquery.mCustomScrollbar.js", "js/jquery/jquery-ui.min.js",
"js/jquery/jquery.autosize.js", "js/jquery/jquery.mousewheel.min.js", "js/jquery/jquery.easing.1.3.js",
"js/channel.js"],
"css": ["css/cssReset.css", "css/sidebar.css", "css/onPageCreate.css", "css/onPageExists.css", "css/scrollbar.css", "css/authenticate.css"]
}
],
"permissions": [
"tabs", "contextMenus", "http://*/*", "https://*/"
],
"icons": {
"16": "img/icon_16.png",
"64": "img/icon_64.png"
}
}
EDIT - After doing console.log(channel), I discovered the error ' Error in event handler for 'undefined': ReferenceError: goog is not defined '. I am unsure why I receive this error as I did include the required javascript file as I followed this post.
So the solution is that you need to include the file <script type="text/javascript" src="https://talkgadget.google.com/talkgadget/channel.js"></script> in a HTML page. I placed this on the first row of background.html.
My mistake was saving a local copy of channel.js, and refer to it in manifest.json.
I'm now going to place a copy of channel.js on my server, and refer to my server's copy. I don't think there will be any issues with that.
Make a console log for the value of msg direct between alert("a") and var channel = ...
and inspect the value.

Categories

Resources