Unable to connect different files of Chrome Extension - javascript

I have been trying to make a chrome extension that gives the meaning of the selected text using urban dictionary API. I have tried different approaches but unable to connect all the files for proper execution.
manifest.json
{
"manifest_version": 2,
"name": "Urban Dictionary",
"version": "0.1",
"description": "Dictionary based on Urban Dict.",
"browser_action": {
"default_popup": "popup.html"
},
"icons": {
"16": "images/images.jpg",
"32": "images/images.jpg",
"48": "images/images.jpg",
"128":"images/images.jpg"
},
"permissions": [
"tabs",
"activeTab"
]
}
popup.html
<!doctype html>
<html>
<head>
<title>meaning</title>
</head>
<body>
<h1>meaning</h1>
<button id="test"></button>
<script src="popup.js"></script>
<script src="getword.js"></script>
</body>
</html>
popup.js
chrome.tabs.executeScript(null, {file: "getword.js"},(results)=>{ console.log(results); } );
getword.js
var something;
var term = window.getSelection().toString()
fetch("https://mashape-community-urban-dictionary.p.rapidapi.com/define?term="+term, {
"method": "GET",
"headers": {
"x-rapidapi-key": "My_API_KEY",
"x-rapidapi-host": "mashape-community-urban-dictionary.p.rapidapi.com"
}
})
.then(response => response.json())
.then(result => {
console.log(result)
something=result.list[0].definition
}
)
.catch(err => {
console.error(err);
});
console.log(something)
document.getElementById("test").innerHTML = something;
When trying to manipulate HTML using getword.js. The result comes out to be undefined.
I would be highly obliged if anyone can help me in any way to refactor this code.

In chrome extensions you always define your background scripts in manifest file otherwise it wont work.
like this :
"background": {
"scripts": [
"back.js"
],
"persistent": true
},
Secondly Popup.js needs to be included in your pop.html like we normally do <script src="popup.js"></script>
and lastly there is another type of script that is called content script which also needs to be included in manifest to work at all.
like this:
"content_scripts": [
{
"js": ["jquery-3.5.0.min.js","content.js"]
}
],
According to your need you should probably study content scripts i think.

There are several problems:
Injected code can't make cross-origin network requests.
getword.js's purpose is to be injected as a content script so it runs in the web page and thus shouldn't be listed in popup.html as the popup is a separate extension page in a separate window not related to the web page.
The solution is straightforward:
get the selected text from the web page,
transfer it to the popup script,
make the network request and show the result.
manifest.json (MV2) should list the API site in permissions:
"manifest_version": 2,
"permissions": [
"activeTab",
"https://mashape-community-urban-dictionary.p.rapidapi.com/"
]
popup.html: remove getword.js from html and delete getword.js file.
popup.js:
const API_HOST = 'mashape-community-urban-dictionary.p.rapidapi.com';
const API_OPTS = {
headers: {
'x-rapidapi-key': 'My_API_KEY',
'x-rapidapi-host': API_HOST,
},
};
chrome.tabs.executeScript({
code: 'getSelection().toString()',
}, async pageData => {
try {
const term = pageData[0].trim();
if (!term) throw new Error('No selection!');
const apiUrl = `https://${API_HOST}/define?term=${encodeURIComponent(term)}`;
const apiRes = await (await fetch(apiUrl, API_OPTS)).json();
const def = apiRes.list[0].definition;
document.getElementById('test').textContent = def;
} catch (e) {
document.getElementById('test').textContent =
chrome.runtime.lastError ? 'Cannot access this page' : e.message;
}
});

Related

Unchecked runtime.lastError: Could not establish connection. Receiving end does not exist. With my own Chrome extension

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 !

Cant fire `alert` from `service_worker` (previously background) from Chrome Extension (v3 manifest) [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();
}
}
});
}

browser.downloads.download - images disappearing after download

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

How to display an alert from background.js in Manifest v3

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

Chrome extension gmail API cookiePolicy?

I'm building a chrome extension that will read the user's emails and check them for typos. However, when trying to authenticate the user in my background.js I'm running into this error:
uO {message: "Invalid cookiePolicy", stack:
"gapi.auth2.ExternallyVisibleError: Invalid cookieP… at handleResponse
(extensions::sendRequest:67:7)"}
Here is how I'm trying to authenticate them:
background.js
var head = document.getElementsByTagName('head')[0];
var script = document.createElement('script');
script.type = 'text/javascript';
script.src = "https://apis.google.com/js/client.js?onload=callbackFunction";
head.appendChild(script);
chrome.identity.getAuthToken({interactive: true}, authorize);
function authorize(token) {
gapi.auth.authorize({
client_id: '800382879116-k3luktdc1lmb1e1fml8i8u.apps.googleusercontent.com',
immediate: true,
scope: 'https://www.googleapis.com/auth/gmail.readonly'
},
function(result){
console.log(result);
gapi.client.load('gmail', 'v1', callback);
});
}
background.html
<!DOCTYPE html>
<html>
<body>
<script src='scripts/background.js'></script>
</body>
</html>
manifest.json
{
"name": "Gmail Typo Analyzer",
"version": "0.1",
"description": "Gmail Typo Analyzer",
"permissions": [
"identity",
"storage"
],
"content_security_policy": "script-src 'self' https://apis.google.com; object-src 'self'",
"oauth2": {
"client_id": "82879116-k3luktdc1li8u.apps.googleusercontent.com",
"scopes": [
"https://www.googleapis.com/auth/gmail.readonly",
"https://www.googleapis.com/auth/userinfo.email"
]
},
"browser_action": {
"default_popup": "popup.html",
"default_icon": "images/Icon_16.png"
},
"background": {
"page": "background.html",
"persistent": false
},
"icons": {
"16": "images/Icon_16.png",
"32": "images/Icon_32.png",
"48": "images/Icon_48.png",
"128": "images/Icon_128.png"
},
"manifest_version": 2,
"key": "c0Kn5f+t92r4P8lmmoDlKtQ6X9Q42UfFtkkiSRBAVMPHnIHqOQvYC67VczJefSNTGpUYa8+wQDFoFj/clH9SfR+BvOGgI6BUVKBNGGoFS"
}
I'm super lost right now as their doesn't seem to be a definitive guide on achieving what I'm trying to do anywhere. Does anyone know what I might be doing wrong?
You didn't post your manifest.json file, where you would set the oauth2 credentials, so I would try something like:
manifest.json
...
"oauth2" : "client_id": "800382879116-k3luktdc1lmb1e1fml8i8u.apps.googleusercontent.com",
"scopes": [
"https://www.googleapis.com/auth/gmail.readonly"
]
...
background.js
chrome.identity.getAuthToken({interactive: true}, authorize);
function authorize(token) {
if (token) {
//user has given authorization, use token for requests.
//...
} else {
//no authorization received.
console.log('No authorization. Error: ' + chrome.runtime.lastError);
}
};
And you don't need to load Google API client, you can access Gmail's Restful API with XMLHttpRequests or Fetch API.
I used to get that cookie error too, but for Chrome extensions I only know how to load them from unpacked in the Extensions tab. So using gapi directly never worked for me.
As Ivan mentioned, Chrome extensions have this support "built-in" by setting the "oauth2" section in the manifest. Then you can call the APIs directly with Ajax like Ivan's example.
To build on Ivan's example, this is my working code to get a list of contacts. I haven't read from the XHR object yet, but Fiddler confirms that the data is coming back fine without any cookie or CORS errors. Of course make sure you enable these APIs in console.developers.google.com.
chrome.identity.getAuthToken({ interactive: true }, authorize);
function authorize(token) {
if (token) {
console.log(token);
var xhr = new XMLHttpRequest();
xhr.open('GET',
"https://people.googleapis.com/v1/people/me/connections?personFields=names");
xhr.setRequestHeader('Authorization',
'Bearer ' + token);
xhr.send();
//user has given authorization, use token for requests.
//...
} else {
console.log('No authorization. Error: ' + chrome.runtime.lastError);
}
};

Categories

Resources