There have been quite a few similar questions on setInterval in background.js in a Chrome extension but none of the answers worked for me. I have a simple extension that checks connectivity to a server by calling an API and checking whether there is a 200 response and then updating the extension icon in the tray accordingly.
background.js
chrome.runtime.onInstalled.addListener(() => {
chrome.browserAction.setIcon({path: "/images/checking.png"});
console.log('VPN check extension started');
// main block - make API calls periodically and monitor the server response
async function ping() {
try {
const response = await axios.get('http://[IP]/api/v1/ping', {
timeout: 4000
});
access = response.status;
if (access == 200) {
chrome.browserAction.setIcon({path: "/images/OK_53017.png"});
} else {
chrome.browserAction.setIcon({path: "/images/not_OK.png"});
}
} catch (error) {
chrome.browserAction.setIcon({path: "/images/not_OK.png"});
}
}
window.setInterval(ping, 1000 * 10);
});
chrome.runtime.onStartup.addListener(() => {
chrome.browserAction.setIcon({path: "/images/checking.png"});
console.log('VPN check extension started');
// main block - make API calls periodically and monitor the server response
async function ping() {
try {
const response = await axios.get('http://[IP]/api/v1/ping', {
timeout: 4000
});
access = response.status;
if (access == 200) {
chrome.browserAction.setIcon({path: "/images/OK_53017.png"});
} else {
chrome.browserAction.setIcon({path: "/images/not_OK.png"});
}
} catch (error) {
chrome.browserAction.setIcon({path: "/images/not_OK.png"});
console.log('error');
}
}
window.setInterval(ping, 1000 * 10);
});
Neither onStartup nor onInstalled works well - when I restart Chrome or switch windows the extension becomes unresponsive.
Manifest
{
"name": "Access status",
"version": "0.0.3",
"description": "Chrome extension to check if access to the network is provided.",
"background": {
"scripts": ["axios.min.js", "background.js"],
"persistent": false
},
"browser_action": {
"default_popup": "popup.html",
"default_icon": {
"16": "images/checking.png"
},
"icons": {
"16": "images/checking.png"
}
},
"permissions": ["<all_urls>"],
"manifest_version": 2,
}
content.js is empty
Any suggestion how to make it work, regardless of which tab is in focus or which window is open and get it to set the interval when fresh Chrome opens? Thanks
A quick google brought up this post. Basically you should probably try using the alarms API instead since chrome will just kill your background script in order to optimize performance.
Your code would probably work at least until someone restarts the browser if you had the background as persistent: true. However that option seems to be deprecated now.
Related
I'm new to extension creation and have a problem, which I've already been able to find various ways to solve, but which are all different from mine and/or fixed with manifest V2 instead of V3 which I need.
Also, some fixes found work on their end, but not on mine, so I really don't understand the problem.
Here is my problem:
I want to make a chrome extension to take screenshots of my browser and apps
I found an online tutorial that seemed correct to me (by the way, the only tutorial that uses AND the screenshots AND the V3 manifest, so perfect!)
Following the tutorial, I got the following error: Unchecked runtime.lastError: Could not establish connection. Receiving end does not exist.
I looked for various ways, but nothing worked, I ended up downloading the git code of the tutorial, but it does not change anything, the error is still present
From what I understand, the error is in the following line:
chrome.action.onClicked.addListener(function (tab) {
chrome.desktopCapture.chooseDesktopMedia(
["screen", "window", "tab"],
tab,
(streamId) => {
if (streamId && streamId.length) {
setTimeout(() => {
chrome.tabs.sendMessage(
tab.id,
{ name: "stream", streamId },
(response) => console.log("received user data", response) // error is here, response is undefined
);
}, 200);
}
}
);
});
I get undefined instead of the response, and I think it's from there that it's a problem, because it never goes on and therefore never activates the onMessage function, nor the content_script
Here is the full background.js code :
chrome.action.onClicked.addListener(function (tab) {
chrome.desktopCapture.chooseDesktopMedia(
["screen", "window", "tab"],
tab,
(streamId) => {
if (streamId && streamId.length) {
setTimeout(() => {
chrome.tabs.sendMessage(
tab.id,
{ name: "stream", streamId },
(response) => console.log("received user data", response)
);
}, 200);
}
}
);
});
chrome.runtime.onMessage.addListener((message, sender, senderResponse) => {
if (message.name === "download" && message.url) {
chrome.downloads.download(
{
filename: "screenshot.png",
url: message.url,
},
(downloadId) => {
senderResponse({ success: true });
}
);
return true;
}
});
Content_script
chrome.runtime.onMessage.addListener((message, sender, senderResponse) => {
if (message.name === 'stream' && message.streamId) {
let track, canvas
navigator.mediaDevices.getUserMedia({
video: {
mandatory: {
chromeMediaSource: 'desktop',
chromeMediaSourceId: message.streamId
},
}
}).then((stream) => {
track = stream.getVideoTracks()[0]
const imageCapture = new ImageCapture(track)
return imageCapture.grabFrame()
}).then((bitmap) => {
track.stop()
canvas = document.createElement('canvas');
canvas.width = bitmap.width; //if not set, the width will default to 200px
canvas.height = bitmap.height;//if not set, the height will default to 200px
let context = canvas.getContext('2d');
context.drawImage(bitmap, 0, 0, bitmap.width, bitmap.height)
return canvas.toDataURL();
}).then((url) => {
chrome.runtime.sendMessage({name: 'download', url}, (response) => {
if (response.success) {
alert("Screenshot saved");
} else {
alert("Could not save screenshot")
}
canvas.remove()
senderResponse({success: true})
})
}).catch((err) => {
alert("Could not take screenshot")
senderResponse({success: false, message: err})
})
return true;
}
})
manifest v3
{
"name": "Screenshots",
"version": "0.0.1",
"description": "Take screenshots",
"manifest_version": 3,
"background": {
"service_worker": "background.js"
},
"permissions": ["desktopCapture", "downloads", "tabs", "nativeMessaging"],
"action": {
"default_title": "Take a Screenshot"
},
"icons": {
"16": "/assets/icon-16.png",
"32": "/assets/icon-32.png",
"48": "/assets/icon-48.png",
"128": "/assets/icon-128.png"
},
"content_scripts": [
{
"matches": ["<all_urls>"],
"js": ["content_script.js"]
}
]
}
I tried several things after various research like
Disable my extensions (which makes no sense, but you never know)
Add a timeout for the response, I tried up to 20 seconds delay, but without success
Added breakpoints everywhere to see if it crosses the line or not
Here is an implementation without service worker and content scripts.
manifest.json
{
"name": "desktopCapture",
"version": "1.0",
"manifest_version": 3,
"permissions": [
"desktopCapture",
"tabs",
"downloads"
],
"action": {
"default_popup": "popup.html"
}
}
popup.html
<html>
<body>
<script src="popup.js"></script>
</body>
</html>
popup.js
const createDate = {
url: "desktopCaptuer.html",
type: "popup",
width: 800,
height: 600
};
chrome.windows.create(createDate);
desktopCaptuer.html
<html>
<body>
<input type="button" id="captuer" value="Captuer">
<script src="desktopCaptuer.js"></script>
</body>
</html>
desktopCaptuer.js
chrome.windows.getCurrent({}, w => {
chrome.windows.update(w.id, { focused: true }, () => {
document.getElementById("captuer").onclick = () => {
const sources = ["screen", "window", "tab"];
chrome.tabs.getCurrent((tab) => {
chrome.desktopCapture.chooseDesktopMedia(sources, tab, (streamId) => {
let track, canvas;
navigator.mediaDevices.getUserMedia({
video: {
mandatory: {
chromeMediaSource: "desktop",
chromeMediaSourceId: streamId
},
}
}).then((stream) => {
track = stream.getVideoTracks()[0];
const imageCapture = new ImageCapture(track);
return imageCapture.grabFrame();
}).then((bitmap) => {
track.stop();
canvas = document.createElement("canvas");
canvas.width = bitmap.width;
canvas.height = bitmap.height;
let context = canvas.getContext("2d");
context.drawImage(bitmap, 0, 0, bitmap.width, bitmap.height);
return canvas.toDataURL();
}).then((url) => {
chrome.downloads.download({
filename: "screenshot.png",
url: url,
}, () => {
canvas.remove();
});
}).catch((err) => {
console.log(err);
alert("Could not take screenshot");
})
});
});
}
});
});
Works for me, using Chromium 107.0.5304.121 (Official. Build) Arch Linux (64-Bit).
Go to https://stackoverflow.com/
Click on the extension icon.
A new window opens, with the text "Select what you want to share. Screenshots wants to share the contents of your screen with stackoverflow.com"
Click on one of the tabs: Entire Screen, Window, Chromium Tab
Click on a screenshot preview or tab title
Click "Share"
The browser displays an alert with the text "Screenshot saved", and a file named "Screenshot.png" is created in the default downloads directory.
So, #Norio Yamamoto 's solution suits me perfectly, because I then need to make a popup to give a name and do other processing on my screen, so thanks to your help, I'm already moving on by starting to understand it HTML popups on extensions! Thanks !
For the problem itself, I was able to "fix" it in the end by reinstalling chrome, and it works as #Thomas Muller tells me... not sure why, maybe I had to break something with many tests, so the app was already working
But I noticed a problem on the version of the tutorial compared to the one with popup, the tutorial version does not work on: non-reload pages (thanks #wOxxOm for the tip by the way), nor on chrome home pages, nor on the extension page, so I really prefer the popup version, but I need to dig more to improve that
Thanks again !
I am attempting to display an alarm that pops up in my browser from background.js in Manifest v3. However, using the code implementation that is described in the Manifest v3 page does not produce an alarm.
Manifest.js:
{
"name": "Focus-Bot",
"description": "A bot meant to help you focus",
"version": "1.0",
"manifest_version": 3,
"background": {
"service_worker": "background.js"
},
"permissions": ["storage", "activeTab", "scripting"],
"action": {
"default_popup": "popup.html",
"default_icon": {
"16": "/images/get_started16.png",
"32": "/images/get_started32.png",
"48": "/images/get_started48.png",
"128": "/images/get_started128.png"
}
},
"icons": {
"16": "/images/get_started16.png",
"32": "/images/get_started32.png",
"48": "/images/get_started48.png",
"128": "/images/get_started128.png"
},
"options_page": "options.html"
}
Background.js:
chrome.runtime.onInstalled.addListener(() => {
chrome.scripting.executeScript({
function: showAlert
})
});
function showAlert(){
alert('object input for later');
}
This version of background.js returns the following error
TypeError: Error in invocation of scripting.executeScript(scripting.ScriptInjection injection, optional function callback): Error at parameter 'injection': Missing required property 'target'.
The example code of a working Chrome Extension (the green background button) uses chrome.tabs in a popup.js file to get a target and inject javascript, but when background.js runs the same code like this:
Background.js (tabs):
chrome.runtime.onInstalled.addListener(() => {
let [tab] = await chrome.tabs.query(queryOptions);
console.log(tab)
chrome.scripting.executeScript({
function: showAlert
})
});
function showAlert(){
alert('object input for later');
}
Background.js seems to crash with "Service worker registration failed", with no error logs.
How do I display an alarm for the current active page from background.js?
As the error message says you need to add target to executeScript's parameters. Always look up the exact usage of API methods in the documentation.
Your code uses await but the function isn't declared with async which is a syntax error that causes the service worker to fail the registration. Currently ManifestV3 is riddled with bugs so it doesn't even show the cause of the failure so you'll have to use try/catch manually.
try {
chrome.runtime.onInstalled.addListener(async () => {
const [tab] = await chrome.tabs.query(queryOptions);
chrome.scripting.executeScript({
target: {tabId: tab.id},
function: showAlert,
});
});
} catch (e) {
console.error(e);
}
An arguably better/cleaner approach would be to use two files: the main code in bg.js and the try-catch wrapper in bg-loader.js that imports bg.js, see this example.
Note that the active tab may be un-injectable e.g. a default start page or a chrome:// page (settings, bookmarks, etc.) or a chrome-extension:// page. Instead you can open a small new window:
alert({html: 'Foo <b>bar</b><ul><li>bla<li>bla</ul>'})
.then(() => console.log('alert closed'));
async function alert({
html,
title = chrome.runtime.getManifest().name,
width = 300,
height = 150,
left,
top,
}) {
const w = left == null && top == null && await chrome.windows.getCurrent();
const w2 = await chrome.windows.create({
url: `data:text/html,<title>${title}</title>${html}`.replace(/#/g, '%23'),
type: 'popup',
left: left ?? Math.floor(w.left + (w.width - width) / 2),
top: top ?? Math.floor(w.top + (w.height - height) / 2),
height,
width,
});
return new Promise(resolve => {
chrome.windows.onRemoved.addListener(onRemoved, {windowTypes: ['popup']});
function onRemoved(id) {
if (id === w2.id) {
chrome.windows.onRemoved.removeListener(onRemoved);
resolve();
}
}
});
}
So I was tinkering with a firefox extension and came across something I can't explain. This extension downloads images from a certain site when a browser action (button) is clicked. Can confirm that the rest of the extension works perfectly and the code below has proper access to the response object.
const downloading = browser.downloads.download({
filename:response.fileName + '.jpg',
url:response.src,
headers:[{name:"Content-Type", value:"image/jpeg"}],
saveAs:true,
conflictAction:'uniquify'
});
const onStart = (id) => {console.log('started: '+id)};
const onError = (error) => {console.log(error)};
downloading.then(onStart, onError);
So the saveAs dialog pops up (filename with file extension populated), I click save, and then it downloads. As soon as the file finishes downloading it disappears from the folder it was saved in. I have no idea how this is happening.
Is this something wrong with my code, Firefox, or maybe a OS security action? Any help would be greatly appreciated.
Extra Information:
Firefox - 95.0.2 (64-bit)
macOS - 11.4 (20F71)
I had the same issue. You have to put download in background, background.js.
Attached sample of Thunderbird addon creates new menu entry in the message list and save raw message to the file on click.
If you look to the manifest.json, "background.js" script is defined in the "background" section. The background.js script is automatically loaded when the add-on is enabled during Thunderbird start or after the add-on has been manually enabled or installed.
See: onClicked event from the browserAction (John Bieling)
manifest.json:
{
"description": "buttons",
"manifest_version": 2,
"name": "button",
"version": "1.0",
"background": {
"scripts": ["background.js"]
},
"permissions": [
"menus","messagesRead","downloads"
],
"browser_action": {
"default_icon": {
"16": "icons/page-16.png",
"32": "icons/page-32.png"
}
}
}
background.js:
async function main() {
// create a new context menu entry in the message list
// the function defined in onclick will get passed a OnClickData obj
// https://thunderbird-webextensions.readthedocs.io/en/latest/menus.html#menus-onclickdata
await messenger.menus.create({
contexts: ["all"],
id: "edit_email_subject_entry",
onclick: (onClickData) => {
saveMsg(onClickData.selectedMessages?.messages);
},
title: "iktatEml"
});
messenger.browserAction.onClicked.addListener(async (tab) => {
let msgs = await messenger.messageDisplay.getDisplayedMessages(tab.id);
saveMsg(msgs);
})
}
async function saveMsg(MessageHeaders) {
if (MessageHeaders && MessageHeaders.length > 0) {
// get MessageHeader of first selected messages
// https://thunderbird-webextensions.readthedocs.io/en/latest/messages.html#messageheader
let MessageHeader = MessageHeaders[0];
let raw = await messenger.messages.getRaw(MessageHeader.id);
let blob = new Blob([raw], { type: "text;charset=utf-8" })
// https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/downloads
await browser.downloads.download({
'url': URL.createObjectURL(blob),
'filename': "xiktatx.eml",
'conflictAction': "overwrite",
'saveAs': false
});
} else {
console.log("No message selected");
}
}
main();
I am attempting to display an alarm that pops up in my browser from background.js in Manifest v3. However, using the code implementation that is described in the Manifest v3 page does not produce an alarm.
Manifest.js:
{
"name": "Focus-Bot",
"description": "A bot meant to help you focus",
"version": "1.0",
"manifest_version": 3,
"background": {
"service_worker": "background.js"
},
"permissions": ["storage", "activeTab", "scripting"],
"action": {
"default_popup": "popup.html",
"default_icon": {
"16": "/images/get_started16.png",
"32": "/images/get_started32.png",
"48": "/images/get_started48.png",
"128": "/images/get_started128.png"
}
},
"icons": {
"16": "/images/get_started16.png",
"32": "/images/get_started32.png",
"48": "/images/get_started48.png",
"128": "/images/get_started128.png"
},
"options_page": "options.html"
}
Background.js:
chrome.runtime.onInstalled.addListener(() => {
chrome.scripting.executeScript({
function: showAlert
})
});
function showAlert(){
alert('object input for later');
}
This version of background.js returns the following error
TypeError: Error in invocation of scripting.executeScript(scripting.ScriptInjection injection, optional function callback): Error at parameter 'injection': Missing required property 'target'.
The example code of a working Chrome Extension (the green background button) uses chrome.tabs in a popup.js file to get a target and inject javascript, but when background.js runs the same code like this:
Background.js (tabs):
chrome.runtime.onInstalled.addListener(() => {
let [tab] = await chrome.tabs.query(queryOptions);
console.log(tab)
chrome.scripting.executeScript({
function: showAlert
})
});
function showAlert(){
alert('object input for later');
}
Background.js seems to crash with "Service worker registration failed", with no error logs.
How do I display an alarm for the current active page from background.js?
As the error message says you need to add target to executeScript's parameters. Always look up the exact usage of API methods in the documentation.
Your code uses await but the function isn't declared with async which is a syntax error that causes the service worker to fail the registration. Currently ManifestV3 is riddled with bugs so it doesn't even show the cause of the failure so you'll have to use try/catch manually.
try {
chrome.runtime.onInstalled.addListener(async () => {
const [tab] = await chrome.tabs.query(queryOptions);
chrome.scripting.executeScript({
target: {tabId: tab.id},
function: showAlert,
});
});
} catch (e) {
console.error(e);
}
An arguably better/cleaner approach would be to use two files: the main code in bg.js and the try-catch wrapper in bg-loader.js that imports bg.js, see this example.
Note that the active tab may be un-injectable e.g. a default start page or a chrome:// page (settings, bookmarks, etc.) or a chrome-extension:// page. Instead you can open a small new window:
alert({html: 'Foo <b>bar</b><ul><li>bla<li>bla</ul>'})
.then(() => console.log('alert closed'));
async function alert({
html,
title = chrome.runtime.getManifest().name,
width = 300,
height = 150,
left,
top,
}) {
const w = left == null && top == null && await chrome.windows.getCurrent();
const w2 = await chrome.windows.create({
url: `data:text/html,<title>${title}</title>${html}`.replace(/#/g, '%23'),
type: 'popup',
left: left ?? Math.floor(w.left + (w.width - width) / 2),
top: top ?? Math.floor(w.top + (w.height - height) / 2),
height,
width,
});
return new Promise(resolve => {
chrome.windows.onRemoved.addListener(onRemoved, {windowTypes: ['popup']});
function onRemoved(id) {
if (id === w2.id) {
chrome.windows.onRemoved.removeListener(onRemoved);
resolve();
}
}
});
}
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');
});