I have simple website created with vanilla JavaScript and I had create a simple chrome extension.
I want to know is there any way to figure out if my visitors' chrome have my extensions.
This is my manifest.json
{
"name": "Covid-19 Stats UK",
"version": "1.0.0",
"description": "latest covid data of UK",
"manifest_version": 3,
"author": "Sampurna Chapagain",
"action":{
"default_popup": "index.html",
"default_title": "Latest Covid Report"
}
}
If you control both the extension and the website, two approaches leap to mind: web accessible resources or site-extension messaging.
In this answer I'll be using abcdefghijklmnopqrstuvwxyzabcdef as a placeholder for your extension's ID.
Web accessible resources
Perhaps the simplest way to check if the user has your extension installed is to use XMLHttpRequest or fetch() to try to load a file from the extension. By default, extensions do not expose their resources to websites, but extension authors can selectively expose resources using the "web_accessible_resources" key in the manifest. The following example is for a Manifest V3 extension.
manifest.json
{
"name": "",
"version": "",
"manifest_version": 3,
"web_accessible_resources": [
{
"matches": ["https://example.com/*"],
"resources": [
"1x1.png", // Expose a single file
"web-accessible-resources/*" // Or a directory tree
]
}
]
}
Then, add the following snippet to your site to check if the extension is installed.
Website JavaScript
const extensionId = "abcdefghijklmnopqrstuvwxyzabcdef";
async function checkExtensionInstalled() {
try {
await fetch(`chrome-extension://${extensionId}/1x1.png`);
return true;
} catch (e) {
if (e instanceof TypeError && e.message == 'Failed to fetch') {
return false;
} else {
throw e;
}
}
}
// Then in your site's buisness logic, do something like this
checkExtensionInstalled.then(installed => {
if (installed) {
console.log("The extension IS installed.");
} else {
console.log("The extension is NOT installed.")
}
});
Messaging
The extension platform allows a website and an extension to communicate directly with each other, but both sides must know about each other in order for this to work.
The following example is adapted from Chrome's official docs.
First, add an "externally_connectable" declaration to your extension's manifest.
manifest.json
"externally_connectable": {
"matches": ["https://*.example.com/*"]
}
Next, update your site's JS to send the extension a message.
Website JavaScript
// The ID of the extension we want to talk to.
const extensionId = "abcdefghijklmnopqrstuvwxyzabcdef";
const messageBody = "Ping";
if (chrome?.runtime?.sendMessage) {
chrome.runtime.sendMessage(extensionId, messageBody, function(response) {
console.log("The extension IS installed.", response);
});
} else {
console.log("The extension is NOT installed.")
}
Finally, in the extension's background context, add a listener for this message.
background.js
function handleExternalMessage(message, _sender, sendResponse) {
if (message === "Ping") {
sendResponse("Pong");
};
}
chrome.runtime.onMessageExternal.addListener(handleExternalMessage);
Related
This is the first time I read about writing Firefox extensions.
What I need is obviously only viable via WebExtensions and both a background and a contentscript. I actually only want to write all open tabs as links in a new tab and then File->Save it. Another alternative Idea was to put it into a JSON Object and save that through a dialog, then I probably could even spare the contentscript but I haven't found anything in the API to download a JSON Object via asking the user to download it via Download Dialog.
Whatever. I think I need to communicate with the content-script then.
I tried to run the following example, but it is not working. When I load the manifest file and open the debugger for extensions, it doesn't log anything and nothing has happened except that the variables myPort and portFromCS seem to be declared without any value.
https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Content_scripts#connection-based_messaging
// manifest.json
{
"manifest_version": 2,
"name": "Save Open Tabs",
"version": "1.0",
"description": "Save my tabs",
"background": {
"scripts": ["background.js"]
},
"content_scripts": [
{
"matches": ["*://*/*"],
"js": ["content.js"]
}
],
"permissions": [
"activeTab",
"tabs"
]
}
// content.js
let myPort=browser.runtime.connect({name:"port-from-cs"});
myPort.postMessage({greeting: "hello from content script"});
myPort.onMessage.addListener((m) => {
console.log("In content script, received message from background script: ");
console.log(m.greeting);
});
// background.js
let portFromCS;
function connected(p) {
portFromCS = p;
portFromCS.postMessage({greeting: "hi there content script!"});
portFromCS.onMessage.addListener((m) => {
portFromCS.postMessage({greeting: "In background script, received message from content script:" + m.greeting});
});
}
browser.runtime.onConnect.addListener(connected);
Why doesn't the example work? Maybe wrong URL matching in the manifest file?
i am working on firefox addon. i have 2 scripts filler.js and aws.js
filler.js
document.getElementById("orderNow").addEventListener("click", function() {
var domain = 'https://www.amazon.com/';
var openWin = window.open(domain);
//message sender
var message = "WS Iron Man";
openWin.postMessage(message,domain); //sending the message
});
aws.js
window.onload = function () {
//alert('page loaded successfully'); //alert function working here
window.addEventListener("message", receiveMessage, false);
function receiveMessage(event) {
if (event.origin !== "http://localhost/waveapp/includes/pages/order_details.html")
return;
// alert to check function working or not
var msg = event.data;
alert(msg);
}
};
manifest.json
{
"manifest_version": 2,
"name": "Borderify",
"version": "1.0",
"description": "Copy details to amazon.com.",
"icons": {
"48": "icons/border-48.png"
},
"content_scripts": [
{
"matches": ["<all_urls>"],
"js": ["filler.js"]
},
{
"matches": ["*://*.amazon.com/*"],
"js": ["aws.js"]
}
],
"permissions": [
"clipboardRead",
"unlimitedStorage",
"storage"
]
}
filler.js running in my localhost and aws.js run on amazon.com. this is a firefox extension.
when i run, i am getting error msg in console
"Failed to execute ‘postMessage’ on ‘DOMWindow’: The target origin provided (‘https://www.amazon.com’) does not match the recipient window’s origin (‘null’)"
also getting this msg
"Storage access automatically granted for tracker “https://www.amazon.com” on “http://localhost”"
Please help me fix it, i am sorry for my bad english
I suggest you to read into Window.postMessage().
It says and I quote:
Normally, scripts on different pages are allowed to access each other
if and only if the pages they originate from share the same protocol,
port number, and host (also known as the "same-origin policy").
window.postMessage() provides a controlled mechanism to securely
circumvent this restriction (if used properly).
So it is not a CORS error, because those don't apply in this situation.
There are multiple reasons why this might happen:
You are actually using a different URL than the one provided
You closed your open window already (window will set everything to null, when it is closed)
You are doing something in between window.open and window.postMessage, which might change what is stored inside your reference
How can I listen/track when chrome extension is installed from the web store?
I previously had an inline installation of the extension but by inline installations coming to an end soon, I want the user action to open the web store to install the extension and listen for when they add the extension for UI changes and act based on that.
I tried the messaging approach found in here but it seems not working.
manifest.json looks like:
"background": {
"scripts":["index.js"],
"persistent": false
},
"permissions": ["desktopCapture"],
"externally_connectable": {
"matches": [
"*://localhost:*/*",
"https://*.stageten.tv/*"
]
}
and index.js :
chrome.runtime.onMessageExternal.addListener(
function(request, sender, sendResponse) {
if (request === screenShareExtensionId) {
if (request.message) {
if (request.message == "version") {
sendResponse({version: 1.0})
alert('Hiiii')
}
}
}
return true;
})
and inside my app:
chrome.runtime.sendMessage(screenShareExtensionId, { message: "version" },
function (reply) {
if (reply) {
if (reply.version) {
return true;
}
}
else {
return false;
}
})
and based on the value in my redux logic, the UI either changes or not/waits for the extension to get installed.
You can do it at the start of your background page.
You need to save a flag (for example, it can be a version of the extension) to the localStorage.
After that, on each start of the background page, you need to check if this flag is in your storage. If there is no flag - then you need to track install, otherwise, it's just usual reload of the background page.
The same way can be used to track updates of the extension from the store, just need to compare versions.
Solved this this in this self-answered question, which I can't mark as a duplicate of this because of no accepted/up votes.
Here's how I solved it from the background script (w/o using a content script):
background.js
Listen for onInstalled event.
Query all opened tabs that match the URL's you want to notify.
Execute a small script in each tab that will postMessage notifying
that installation was succesful.
chrome.runtime.onInstalled.addListener(function listener(details) {
if (details.reason === chrome.runtime.OnInstalledReason.INSTALL) {
chrome.tabs.query({
url: [
'https://localhost:3000/*',
'https://staging.foo.com/*',
'https://production.foo.com/*'
]
}, tabs => {
Array.from(tabs).forEach(tab => {
chrome.tabs.executeScript(tab.id, {
code: `window.postMessage('screenshare-ext-installed', window.origin);`
});
});
});
chrome.runtime.onInstalled.removeListener(listener);
}
});
manifest.json
Just make sure both externally_connectable and permissions declare
the URL patterns of the sites you want to notify.
"externally_connectable": {
"matches": [
"https://localhost:3000/*",
"https://staging.foo.com/*",
"https://production.foo.com/*"
]
},
"permissions": [
"desktopCapture",
"https://localhost:3000/*",
"https://staging.foo.com/*",
"https://production.foo.com/*"
],
Web page
Just listen somewhere for the postMessage message fired by
the extension on succesful installation.
window.onmessage = e => {
if (e.data === 'screenshare-ext-installed') {
// extension successfully installed
startScreenShare()
}
}
Credits
#wOxxOm's comment
we have a JSP page in a servlet and we have developed a google chrome extension in client side. we want to communicate with the extension through the JSP or html. as we have searched, there should be a background.js and a content script. we put the background.js inside the extension folder and we register it in chrome. then as we put the content script alongside the JSP or external web page, these two pages can not interact.
this is the code of background.js:
alert("hey back");
chrome.runtime.onConnectExternal.addListener(function (port) {
port.postMessageExternal({ greeting: "hey" });
port.onMessageExternal.addListener(function (message, sender) {
if (message.greeting == "salam") {
alert("message from content: " + message.greeting);
port.postMessageExternal({ greeting: "H R U?" });
}
else if (message.greeting == "khobam") {
alert("message from content: " + message.greeting);
}
else {
alert("background did not receive salam");
}
});
});
this is the manifest.json:
{
"manifest_version": 2,
"name": "msg-test",
"description": "message test",
"version": "1.0",
"browser_action": {
"default_icon": "icon.png"
},
"background": {
"scripts": ["background.js"],
"persistent": true
},
"permissions": [
"tabs",
"http://*/*"
],
"offline_enabled": true,
"externally_connectable": {
"ids":["*"],
"matches": ["file:///C:/Users/h.aghajani/Desktop/b.html"],
"accept_tls_channel_id":true
}
}
this is the content script:
alert("hey content");
var port = chrome.runtime.connect({ name: "mycontentscript" });
port.onMessage.addListener(function (message, sender) {
if (message.greeting == "hey") {
alert("message from background: " + message.greeting);
port.postMessage({ greeting: "salam" });
}
else if (message.greeting == "H R U?") {
alert("message from background: " + message.greeting);
port.postMessage({ greeting: "khobam" });
}
else {
alert("content did not receive hello");
port.postMessage({ greeting: "no salam" });
}
});
there are some problems:
it doesn't work at all. where is the problem? in fact how can we call a
background script from an external web page?
Besides, if we put the content script in the extension folder, whenever the browser works offline (without internet connection), the background and content script can not talk properly.
Thanks.
First off, never use alert for debugging purposes. Never. Simply forget it.
Use console.log or the actual debugger. In order to debug the background page of an extension enable the [x] developer mode checkbox on the top of chrome://extensions page.
we call the content.js in html, as : <script src="content.js"/>
This is invalid HTML, the script tag isn't a void (self closing) element, so the correct syntax is:
<script src="content.js"></script>
As you can see on chrome://extensions page the extension has an error that file:// scheme is not permitted in "externally_connectable". Setup a local web server and make it serve the test files. Also you don't need "ids":["*"] as it's only used for communicating from other extensions/apps, not web pages.
"externally_connectable": {
"matches": ["http://localhost/test.html"],
"accept_tls_channel_id":true
},
Now at last the page script, misleadingly named as content.js, is loaded and displays an error in the console that chrome.runtime.connect should specify the extension ID as the first parameter as per the documentation so that Chrome would know where to send the message.
var port = chrome.runtime.connect("qwertyuiopasdfghjkl", { name: "mycontentscript" });
replace qwertyuiopasdfghjkl with your extension's ID as seen on chrome://extensions page when the [x] developer mode checkbox is enabled on the top.
Now let's look at the background script:
There's no such thing in Chrome API as postMessageExternal, just use postMessage.
I'm trying to write a chrome extension that works with YouTube and need to access some of YouTube's cookie information. I cant seem to get my extension to see any cookies. (Even though I can see them under resources in the "Inspect Element" developer portion of Chrome).
I'm pretty sure I've set up permissions correctly in the manifest 2 file because when I take out the "cookies" permission just to test it I get an error saying "Cannot call method 'getAll'". My current problem is just that no cookies are returned by the callback function.
{
"manifest_version": 2,
"name": "YouTube Viewer",
"description": "This extension is for YouTube videos.",
"version": "1.7",
"icons": {
"128": "ytblack.png"
},
"permissions": [
"cookies",
"https://www.youtube.com/",
"http://www.youtube.com/",
"tabs",
"storage"
],
"background": {
"scripts": ["bootstrap.js"],
"persistent": false
},
"page_action": {
"default_title": "YT View",
"default_icon": "ytblack.png",
"default_popup": "popup.html"
}
}
My manifest calls the bootstrap.js. Inside bootstrap.js there is a call to another file ytview.js but I'm not concerned with that. The code in that is working fine. But inside bootstrap.js my cookies.length is returning as 0 when I look at my "background page" console. The log for "Callback for cookies came in fine." fires correctly. But then it says "cookies.length=0". Like I said, I know the cookies exist because I can see them in the resources.
chrome.tabs.onUpdated.addListener(function(id, info, tab){
// decide if we're ready to inject content script
if (tab.status !== "complete"){
console.log("not yet");
return;
}
if (tab.url.toLowerCase().indexOf("youtube.com/watch") === -1){
console.log("you are not on a YouTube video");
return;
}
chrome.cookies.getAll({domain: "www.youtube.com"}, function(cookies) {
console.log('Callback for cookies came in fine.');
console.log('cookies.length=' + cookies.length);
for(var i=0; i<cookies.length;i++) {
console.log('cookie=' + cookies[i].name);
}
});
chrome.tabs.executeScript(null, {"file": "ytview.js"});
});
Any ideas why no cookies are being returned? Maybe something with "domain" in the .getAll statement? I've tried lots of combinations like www.youtube.com, youtube.com, https://www.youtube.com with no luck.
for future users:
youtube.com use ".youtube.com" as cookie domain to allow the site to share cookies across all youtube subdomains so in your example you should use domain name without 'www' subdomain for example:
chrome.cookies.getAll({domain: "youtube.com"}, function(cookies) {
//...
});
you can clearly see cookies domain using default chrome developer tools
I figured it out. In my manifest I was asking for permission on www.youtube.com but the cookies I was trying to read were on simply youtube.com without the www. Adding the plain youtube.com to the permissions in manifest fixed it.