Chrome Extension - How do trigger the extension only in specific URL - javascript

I have created a google extension that calls my API to get if there is new cookies added to the API and that API would then transfer it into my chrome browser. E.g.
The problem is that whatever site I actually try to access, it does quite alot calls to my API which I am scared of where it will could eventually overload the database and my manifest.json looks like this:
{
"name": "Cookie Injector",
"version": "0.1",
"manifest_version": 3,
"background": {
"service_worker": "./js/background.js"
},
"permissions": ["cookies", "storage", "tabs"],
"action": {
"default_popup": "./html/popup.html"
},
"host_permissions": ["<all_urls>"],
"icons": {
"16": "/images/icon.png",
"32": "/images/icon.png",
"48": "/images/icon.png",
"128": "/images/icon.png"
}
}
and my background.js is:
function get_pending_cookies(discord_id) {
const id = new URLSearchParams({ 'user_id': discord_id });
return fetch(`http://cookie.pynewbie.com/pending-cookies?${id}`, {
method: 'GET',
}).then((response) => {
return response.json();
}).then((json) => {
return json;
});
}
function handle_cookies(discord_id) {
get_pending_cookies(discord_id).then((result) => {
...
// detect is focused on correct page
chrome.tabs.query({ currentWindow: true, active: true }, function (tabs) {
// getting current tab's URL
if (!(tabs === undefined || tabs.length === 0)) {
if (tabs[0].url === url) {
console.log("USER OPENED CORRECT PAGE, RELOADING");
chrome.tabs.reload(tabs[0].id);
}
}
});
})
});
}
chrome.runtime.onMessage.addListener(
function (request, sender, sendResponse) {
sendResponse(request);
set_discord_id(request.discord_id);
}
);
chrome.tabs.onUpdated.addListener(() => {
handle_cookies;
});
My question is how can I make the API (get_pending_cookies) to only be called when I enter specific URL e.g. pythonewbie.com (then it should do the API calls otherwise skip)

Related

Cannot get background.js to work with new manifest v3 notation [duplicate]

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();
}
}
});
}

Receive response of http request in chrome extension

So I am trying to get a way to display the content of a http response made by clicking a button on a webapplication and display it in a chrome extension.
The http request is to an API and responds with 1 line of data, so i'm trying to capture this, whenever a request is sent off to a specific url and store the response of the request as a variable of some kind .
So far I've followed this guide to get an understanding:
https://betterprogramming.pub/chrome-extension-intercepting-and-reading-the-body-of-http-requests-dd9ebdf2348b
This is nearly what I want to achieve but I Cant get it to work
Im hit with the error "Refused to execute inline script because it violates the following Content Security Policy directive: "script-src 'self' 'wasm-unsafe-eval'"."
Not really sure how I can just listen for the response of a certain request, any help?
Source code:
Manifest.json:
{
"name": "SafePaste",
"description": "Build an Extension!",
"version": "1.0",
"manifest_version": 3,
"devtools_page": "devtools.html",
"permissions": [
"clipboardRead",
"clipboardWrite",
"storage",
"activeTab"
],
"host_permissions": ["https://demogamesfree.pragmaticplay.net/gs2c/v3/gameService/"],
"content_scripts": [{
"matches": ["<all_urls>"],
"js": ["contentScript.js"],
"run_at": "document_start"
}]
}
contentScript.js
function interceptData() {
var xhrOverrideScript = document.createElement('script');
xhrOverrideScript.type = 'text/javascript';
xhrOverrideScript.innerHTML = '
(function() {
var XHR = XMLHttpRequest.prototype;
var send = XHR.send;
var open = XHR.open;
XHR.open = function(method, url) {
this.url = url; // the request url
return open.apply(this, arguments);
}
XHR.send = function() {
this.addEventListener('load', function() {
if (this.url.includes('<url-you-want-to-intercept>')) {
var dataDOMElement = document.createElement('div');
dataDOMElement.id = '__interceptedData';
dataDOMElement.innerText = this.response;
dataDOMElement.style.height = 0;
dataDOMElement.style.overflow = 'hidden';
document.body.appendChild(dataDOMElement);
}
});
return send.apply(this, arguments);
};
})();
`
document.head.prepend(xhrOverrideScript);
}
function checkForDOM() {
if (document.body && document.head) {
interceptData();
} else {
requestIdleCallback(checkForDOM);
}
}
requestIdleCallback(checkForDOM);
function scrapeData() {
var responseContainingEle = document.getElementById('__interceptedData');
if (responseContainingEle) {
var response = JSON.parse(responseContainingEle.innerHTML);
} else {
requestIdleCallback(scrapeData);
}
}
requestIdleCallback(scrapeData);
devtools.html
<script src="devtools.js"></script>
devtools.js
chrome.devtools.panels.create("MyPanel", null, 'panel.html'
panel.html
<html>
<body>
<script src="panel.js"></script>
</body>
</html>
panel.js
chrome.devtools.network.onRequestFinished.addListener(request => {
request.getContent((body) => {
if (request.request && request.request.url) {
if (request.request.url.includes('<url-to-intercept>')) {
chrome.runtime.sendMessage({
response: body
});
}
}
});
});
Put xhrOverrideScript.innerHTML inside a separate .js file, and instead use
xhrOverrideScript.src = chrome.runtime.getURL('./script.js');
Add this to the manifest to allow the file to be executed and commujnicate with background page.
"web_accessible_resources": [
{
"resources": ["your_script.js"],
"matches": ["https://*/*", "http://*/*"]
}
],
"externally_connectable": {
"matches": [
"https://www.crunchbase.com/*"
]
},
Then add a message listener in background page like this
chrome.runtime.onMessageExternal.addListener(async (request, sender,
sendResponse) => {
console.log("🔰 Message From Injected Script 🔰")
console.log(request)
if (request.message == "Intercepted Request Response") {
console.log(request.Intercepted_response)
sendResponse("done")
}
})
And finally in the script injected:
function sendResponsetoBackground(response) {
chrome.runtime.sendMessage(
CE_id,
{ message: 'Intercepted Request Response', intercepted_response: response },
(response) => {
console.log('Response sent to Background page', response);
}
);
}

Javascript Browser WebExtension API Manifest v3 - Service Worker and chrome.storage.local API issue

I'm implementing a small extension for Copy as cURL feature (as done by the Network tab of DevTools) and I would like to use Manifest v3. According to the documentation and to the contribution of the community, Service Worker at a certain time stops to live so some variables cannot retrieve the needed information from the active tab.
For managing this, I'm using chrome.storage.local.set and .get functions in order to keep the needed information also after the Service Worker stops to live. When I run the extension test, I don't receive any error, but, despite I retrieve the stored variables by the chrome.storage API, sometimes I continue to not retrieve the values anymore also when the Service Worker should be alive. For example:
when I connect to a website, I can retrieve and copy the correct data also in 1 min, then, if I continue to Copy (without refreshing the page), I don't get the parameters (i.e., GET headers).
sometimes, if I open a new tab, insert an address and quickly press Copy as cURL, of my extension, headers are not copied, and I need to refresh the page (not by clicking refresh button of browser but click on URL then ENTER) for getting them.
Maybe the issue is not related to the Time-to-live of the Service Worker because I can keep a page opened for a lot of minutes and it gives me the right parameters. I don't know where my approach is failing. The code of this small implementation is the following:
background.js
"use strict";
/*
Called when the item has been created, or when creation failed due to an error.
We'll just log success/failure here.
*/
function onCreated() {
if (chrome.runtime.lastError) {
console.log(`Error: ${chrome.runtime.lastError}`);
} else {
console.log("Item created successfully");
}
}
/*
Called when the item has been removed.
We'll just log success here.
*/
function onRemoved() {
console.log("Item removed successfully");
}
/*
Called when there was an error.
We'll just log the error here.
*/
function onError(error) {
console.log(`Error: ${error}`);
}
/*
Create all the context menu items.
*/
chrome.contextMenus.create({
id: "tools-copy",
//title: chrome.i18n.getMessage("menuItemToolsCopy"),
title: "Copy",
contexts: ["all"],
}, onCreated);
chrome.contextMenus.create({
id: "tools-copy-curl",
parentId: "tools-copy",
//title: chrome.i18n.getMessage("menuItemToolsCopyAsFFUF"),
title: "Copy as cURL",
contexts: ["all"],
}, onCreated);
const tabData = {};
const getProp = (obj, key) => (obj[key] || (obj[key] = {}));
const encodeBody = body => {
var data = '';
// Read key
for (var key in body.formData) { //body is a JSON object
data += `${key}=${body.formData[key]}&`;
}
data = data.replace(/.$/,"");
var body_data = `'${data}'`;
return body_data;
}
const FILTER = {
types: ['main_frame', 'sub_frame'],
urls: ['<all_urls>'],
};
const TOOLS = {
CURL: 'tools-copy-curl',
};
chrome.webRequest.onBeforeRequest.addListener(e => {
getProp(getProp(tabData, e.tabId), e.frameId).body = e.requestBody;
chrome.storage.local.set({tabData: tabData}, function() {
console.log('HTTP request saved');
});
}, FILTER, ['requestBody']);
chrome.webRequest.onBeforeSendHeaders.addListener(e => {
getProp(getProp(tabData, e.tabId), e.frameId).headers = e.requestHeaders;
chrome.storage.local.set({tabData: tabData}, function() {
console.log('HTTP request saved');
});
}, FILTER, ['requestHeaders']);
chrome.tabs.onRemoved.addListener(tabId => delete tabData[tabId]);
chrome.tabs.onReplaced.addListener((addId, delId) => delete tabData[delId]);
chrome.contextMenus.onClicked.addListener((info, tab) => {
chrome.storage.local.get(["tabData"], function(items) {
const data = items.tabData[tab.id]?.[info.frameId || 0] || {};
if (info.menuItemId === TOOLS.CURL) {
var txt_clip = `curl -u '${info.frameUrl || tab.url}'` +
(data.headers?.map(h => ` -H '${h.name}: ${h.value}'`).join('') || '') +
(data.body? ' --data_raw ' + encodeBody(data.body) : '');
}
chrome.tabs.sendMessage(tab.id,
{
message: "copyText",
textToCopy: txt_clip
}, function(response) {})
});
});
content.js
chrome.runtime.onMessage.addListener(
function(request, sender, sendResponse) {
if (request.message === "copyText") {
navigator.clipboard.writeText(request.textToCopy);
sendResponse({status: true});
}
}
);
manifest.json
{
"manifest_version": 3,
"name": "CopyAsCURL",
"description": "Copy as cURL test example.",
"version": "1.0",
"default_locale": "en",
"background": {
"service_worker": "background.js"
},
"permissions": [
"contextMenus",
"activeTab",
"cookies",
"webRequest",
"tabs",
"clipboardWrite",
"storage"
],
"host_permissions": [
"<all_urls>"
],
"content_scripts": [
{
"matches": [
"<all_urls>"
],
"js": ["content.js"]
}
],
"icons": {
"16": "icons/menu-16.png",
"32": "icons/menu-32.png",
"48": "icons/menu-48.png"
}
}
I want also to thank #wOxxOm for the support on similar topic.

getting `DOMException: Failed due to shutdown` when trying to record stream from screen+microphone

I am building an extension that records my screen and microphone audio as well.
Overview:
I have content.js which tries to get the access of navigator.mediaDevices.getUserMedia({... in the succession I send message to the background.js which again tries to access navigator.mediaDevices.getUserMedia({... over here I am recording the audio stream. In addition to this, I have popup.html which have a start button on clicking the button I am recording the screen. But in the full process I am getting the error DOMException: Failed due to shutdown.
Also, I am aware there have been questions on the above error (the famous one Chrome Extension - getUserMedia throws "NotAllowedError: Failed due to shutdown") but didn’t help me much.
content.js
navigator.mediaDevices.getUserMedia({
audio: { echoCancellation: true }
})
.then((e) => { chrome.runtime.sendMessage({ from: 'success' })})
.catch((e) => {console.log(e);});
Background.js
var recorder = null;
chrome.runtime.onMessage.addListener(gotMessage);
function gotMessage(message) {
if(message.from === 'success') {
navigator.mediaDevices.getUserMedia({
audio: { echoCancellation: true }
})
.then((e) => {
var chunks = [];
var options = {
mimeType: 'audio/webm',
};
recorder = new MediaRecorder(e, options);
recorder.ondataavailable = function (event) {
if (event.data.size > 0) {
chunks.push(event.data);
}
};
recorder.onstop = function () {
var superBuffer = new Blob(chunks, {
type: 'audio/webm',
});
var url = URL.createObjectURL(superBuffer);
};
recorder.start();
})
.catch((e) => {console.log(e);});
}
popup.js:
let btnStartTab = document.getElementById('start');
let btnStartTab1 = document.getElementById('stop');
btnStartTab.addEventListener('click', function (event) {
chrome.runtime.sendMessage({ from: 'start' });
});
btnStartTab1.addEventListener('click', function (event) {
chrome.runtime.sendMessage({ from: 'stop' });
});
manifest.json
{
"name": "My Recorer",
"version": "1.0",
"manifest_version": 2,
"background": {
"scripts": ["background.js"],
"persistent": true
},
"browser_action": {
"default_icon": "test.png"
},
"commands": {
"run-foo": {
"suggested_key": {
"default": "Ctrl + Shift + Y",
"mac": "Command+Shift+Y"
},
"description": "Run \"foo\" on the current page."
},
"_execute_action": {
"suggested_key": {
"default": "Ctrl + Shift + Y",
"mac": "Command+Shift+Y"
}
}
},
"permissions": [
"activeTab",
"clipboardWrite",
"declarativeContent",
"storage",
"tabs",
"tabCapture",
"desktopCapture",
"alarms",
"activeTab",
"downloads",
"<all_urls>"
]
}
After deep searching and help from SO foundation found a way to reach the solution thought of sharing.
So, we need to understand that background doesn’t have control over DOM that's why we need to take help from contentscript.js and below architecture is important :
In my case, I am injecting contentscript.js from manifest.json but one can use executescript() to inject the script.
contentscript.js will create an iframe with display none with iframe.src = chrome.extension.getURL('audiosources.html'); above is important!!
Also, audiosources.html is under web_accessible_resources in manifest.json
The body of above HTML will have <script src="audiosources.js"></script> only.
audiosources.js is the oe which will help to get rid of the error here I am getting access to navigator.mediaDevices.getUserMedia(...) and all the mic devices. here on success it will send message to background.js.
Now, background.js will do its magic of tabCapture with navigator.mediaDevices.getUserMedia and here we need to apply logic for merging.

Copy Data from webpage Firefox extension

I have a very strange situation. I have an extension which copies stuff from the webpage based on the user's selection. But, when ever there are multiple frames its fails. For example on Gmail. If I select anything from Gmail and try to find the selection it will end up with an error:
Error: window.getSelection(...) is null
Here is my code (This is a working example. I didn't include the icon.):
manifest.json
{
"description": "Adds a solid red border to all webpages matching mozilla.org. See https://developer.mozilla.org/en-US/Add-ons/WebExtensions/Examples#borderify",
"manifest_version": 2,
"name": "Borderify",
"version": "1.0",
"homepage_url": "https://github.com/mdn/webextensions-examples/tree/master/borderify",
"icons": {
"48": "icons/border-48.png"
},
"background": {
"scripts": ["myaddone.js"]
},
"browser_action": {
"default_icon": "icons/trash.svg",
"default_title": "Forget it!"
},
"content_scripts": [{
"matches": ["<all_urls>"],
"js": ["callHere.js"],
"all_frames": true
}]
}
callHere.js
function logger(msg) {
console.log("=============");
console.log(msg);
console.log("=============");
}
var getSelectedDataFromPage = function () {
logger("fooo");
selec = window.getSelection().toString().trim();
return selec;
}
browser.runtime.onMessage.addListener(request => {
var messageToCopy = request.greeting;
if (messageToCopy == "findCopy") {
var selectedText = getSelectedDataFromPage();
return Promise.resolve({
response: selectedText
});
}
logger(messageToCopy);
return Promise.resolve({
response: "Fail"
});
});
myaddone.js
function logger(msg) {
console.log(msg);
}
function onError(error) {
console.error(`Error: ${error}`);
}
function findSelectionTExt(tabs) {
for (let tab of tabs) {
browser.tabs.sendMessage(tab.id, {
greeting: "findCopy"
}).then(response => {
logger(response.response);
}).catch(onError);
}
}
browser.browserAction.onClicked.addListener(() => {
browser.tabs.query({
currentWindow: true,
active: true
}).then(findSelectionTExt).catch(onError);
});
It is using a message system to content script to copy stuff from selection. It works perfectly fine with Stack Overflow and other sites, but not sites which use more frames etc., like Gmail.
Loop Image, as you can see it able to grab the text first time and then its keep sending the message I think.
What I am really missing?
I did solved my issue using context menu item and it works very well with every where like iframes. I got a example from firefox repo. https://github.com/mdn/webextensions-examples/tree/master/context-menu-demo.

Categories