I am in the process of building a Chrome extension, and for the whole thing to work the way I would like it to, I need an external JavaScript script to be able to detect if a user has my extension installed.
For example: A user installs my plugin, then goes to a website with my script on it. The website detects that my extension is installed and updates the page accordingly.
Is this possible?
Chrome now has the ability to send messages from the website to the extension.
So in the extension background.js (content.js will not work) add something like:
chrome.runtime.onMessageExternal.addListener(
function(request, sender, sendResponse) {
if (request) {
if (request.message) {
if (request.message == "version") {
sendResponse({version: 1.0});
}
}
}
return true;
});
This will then let you make a call from the website:
var hasExtension = false;
chrome.runtime.sendMessage(extensionId, { message: "version" },
function (reply) {
if (reply) {
if (reply.version) {
if (reply.version >= requiredVersion) {
hasExtension = true;
}
}
}
else {
hasExtension = false;
}
});
You can then check the hasExtension variable. The only drawback is the call is asynchronous, so you have to work around that somehow.
Edit:
As mentioned below, you'll need to add an entry to the manifest.json listing the domains that can message your addon. Eg:
"externally_connectable": {
"matches": ["*://localhost/*", "*://your.domain.com/*"]
},
2021 Update:
chrome.runtime.sendMessage will throw the following exception in console if the extension isn't installed or it's disabled.
Unchecked runtime.lastError: Could not establish connection. Receiving end does not exist
To fix this, add this validation inside the sendMessage callback
if (chrome.runtime.lastError) {
// handle error
}
I am sure there is a direct way (calling functions on your extension directly, or by using the JS classes for extensions), but an indirect method (until something better comes along):
Have your Chrome extension look for a specific DIV or other element on your page, with a very specific ID.
For example:
<div id="ExtensionCheck_JamesEggersAwesomeExtension"></div>
Do a getElementById and set the innerHTML to the version number of your extension or something. You can then read the contents of that client-side.
Again though, you should use a direct method if there is one available.
EDIT: Direct method found!!
Use the connection methods found here: https://developer.chrome.com/extensions/extension#global-events
Untested, but you should be able to do...
var myPort=chrome.extension.connect('yourextensionid_qwerqweroijwefoijwef', some_object_to_send_on_connect);
Another method is to expose a web-accessible resource, though this will allow any website to test if your extension is installed.
Suppose your extension's ID is aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa, and you add a file (say, a transparent pixel image) as test.png in your extension's files.
Then, you expose this file to the web pages with web_accessible_resources manifest key:
"web_accessible_resources": [
"test.png"
],
In your web page, you can try to load this file by its full URL (in an <img> tag, via XHR, or in any other way):
chrome-extension://aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/test.png
If the file loads, then the extension is installed. If there's an error while loading this file, then the extension is not installed.
// Code from https://groups.google.com/a/chromium.org/d/msg/chromium-extensions/8ArcsWMBaM4/2GKwVOZm1qMJ
function detectExtension(extensionId, callback) {
var img;
img = new Image();
img.src = "chrome-extension://" + extensionId + "/test.png";
img.onload = function() {
callback(true);
};
img.onerror = function() {
callback(false);
};
}
Of note: if there is an error while loading this file, said network stack error will appear in the console with no possibility to silence it. When Chromecast used this method, it caused quite a bit of controversy because of this; with the eventual very ugly solution of simply blacklisting very specific errors from Dev Tools altogether by the Chrome team.
Important note: this method will not work in Firefox WebExtensions. Web-accessible resources inherently expose the extension to fingerprinting, since the URL is predictable by knowing the ID. Firefox decided to close that hole by assigning an instance-specific random URL to web accessible resources:
The files will then be available using a URL like:
moz-extension://<random-UUID>/<path/to/resource>
This UUID is randomly generated for every browser instance and is not your extension's ID. This prevents websites from fingerprinting the extensions a user has installed.
However, while the extension can use runtime.getURL() to obtain this address, you can't hard-code it in your website.
I thought I would share my research on this.
I needed to be able to detect if a specific extension was installed for some file:/// links to work.
I came across this article here
This explained a method of getting the manifest.json of an extension.
I adjusted the code a bit and came up with:
function Ext_Detect_NotInstalled(ExtName, ExtID) {
console.log(ExtName + ' Not Installed');
if (divAnnounce.innerHTML != '')
divAnnounce.innerHTML = divAnnounce.innerHTML + "<BR>"
divAnnounce.innerHTML = divAnnounce.innerHTML + 'Page needs ' + ExtName + ' Extension -- to intall the LocalLinks extension click here';
}
function Ext_Detect_Installed(ExtName, ExtID) {
console.log(ExtName + ' Installed');
}
var Ext_Detect = function (ExtName, ExtID) {
var s = document.createElement('script');
s.onload = function () { Ext_Detect_Installed(ExtName, ExtID); };
s.onerror = function () { Ext_Detect_NotInstalled(ExtName, ExtID); };
s.src = 'chrome-extension://' + ExtID + '/manifest.json';
document.body.appendChild(s);
}
var is_chrome = navigator.userAgent.toLowerCase().indexOf('chrome') > -1;
if (is_chrome == true) {
window.onload = function () { Ext_Detect('LocalLinks', 'jllpkdkcdjndhggodimiphkghogcpida'); };
}
With this you should be able to use Ext_Detect(ExtensionName,ExtensionID) to detect the installation of any number of extensions.
Another possible solution if you own the website is to use inline installation.
if (chrome.app.isInstalled) {
// extension is installed.
}
I know this an old question but this way was introduced in Chrome 15 and so I thought Id list it for anyone only now looking for an answer.
Here is an other modern approach:
const checkExtension = (id, src, callback) => {
let e = new Image()
e.src = 'chrome-extension://'+ id +'/'+ src
e.onload = () => callback(1), e.onerror = () => callback(0)
}
// "src" must be included to "web_accessible_resources" in manifest.json
checkExtension('gighmmpiobklfepjocnamgkkbiglidom', 'icons/icon24.png', (ok) => {
console.log('AdBlock: %s', ok ? 'installed' : 'not installed')
})
checkExtension('bhlhnicpbhignbdhedgjhgdocnmhomnp', 'images/checkmark-icon.png', (ok) => {
console.log('ColorZilla: %s', ok ? 'installed' : 'not installed')
})
I used the cookie method:
In my manifest.js file I included a content script that only runs on my site:
"content_scripts": [
{
"matches": [
"*://*.mysite.co/*"
],
"js": ["js/mysite.js"],
"run_at": "document_idle"
}
],
in my js/mysite.js I have one line:
document.cookie = "extension_downloaded=True";
and in my index.html page I look for that cookie.
if (document.cookie.indexOf('extension_downloaded') != -1){
document.getElementById('install-btn').style.display = 'none';
}
You could have the extension set a cookie and have your websites JavaScript check if that cookie is present and update accordingly. This and probably most other methods mentioned here could of course be cirvumvented by the user, unless you try and have the extension create custom cookies depending on timestamps etc, and have your application analyze them server side to see if it really is a user with the extension or someone pretending to have it by modifying his cookies.
There's another method shown at this Google Groups post. In short, you could try detecting whether the extension icon loads successfully. This may be helpful if the extension you're checking for isn't your own.
Webpage interacts with extension through background script.
manifest.json:
"background": {
"scripts": ["background.js"],
"persistent": true
},
"externally_connectable": {
"matches": ["*://(domain.ext)/*"]
},
background.js:
chrome.runtime.onMessageExternal.addListener(function(msg, sender, sendResponse) {
if ((msg.action == "id") && (msg.value == id))
{
sendResponse({id : id});
}
});
page.html:
<script>
var id = "some_ext_id";
chrome.runtime.sendMessage(id, {action: "id", value : id}, function(response) {
if(response && (response.id == id)) //extension installed
{
console.log(response);
}
else //extension not installed
{
console.log("Please consider installig extension");
}
});
</script>
Your extension could interact with the website (e.g. changing variables) and your website could detect this.
But there should be a better way to do this. I wonder how Google is doing it on their extension gallery (already installed applications are marked).
Edit:
The gallery use the chrome.management.get function. Example:
chrome.management.get("mblbciejcodpealifnhfjbdlkedplodp", function(a){console.log(a);});
But you can only access the method from pages with the right permissions.
A lot of the answers here so far are Chrome only or incur an HTTP overhead penalty. The solution that we are using is a little different:
1. Add a new object to the manifest content_scripts list like so:
{
"matches": ["https://www.yoursite.com/*"],
"js": [
"install_notifier.js"
],
"run_at": "document_idle"
}
This will allow the code in install_notifier.js to run on that site (if you didn't already have permissions there).
2. Send a message to every site in the manifest key above.
Add something like this to install_notifier.js (note that this is using a closure to keep the variables from being global, but that's not strictly necessary):
// Dispatch a message to every URL that's in the manifest to say that the extension is
// installed. This allows webpages to take action based on the presence of the
// extension and its version. This is only allowed for a small whitelist of
// domains defined in the manifest.
(function () {
let currentVersion = chrome.runtime.getManifest().version;
window.postMessage({
sender: "my-extension",
message_name: "version",
message: currentVersion
}, "*");
})();
Your message could say anything, but it's useful to send the version so you know what you're dealing with. Then...
3. On your website, listen for that message.
Add this to your website somewhere:
window.addEventListener("message", function (event) {
if (event.source == window &&
event.data.sender &&
event.data.sender === "my-extension" &&
event.data.message_name &&
event.data.message_name === "version") {
console.log("Got the message");
}
});
This works in Firefox and Chrome, and doesn't incur HTTP overhead or manipulate the page.
You could also use a cross-browser method what I have used.
Uses the concept of adding a div.
in your content script (whenever the script loads, it should do this)
if ((window.location.href).includes('*myurl/urlregex*')) {
$('html').addClass('ifextension');
}
in your website you assert something like,
if (!($('html').hasClass('ifextension')){}
And throw appropriate message.
If you have control over the Chrome extension, you can try what I did:
// Inside Chrome extension
var div = document.createElement('div');
div.setAttribute('id', 'myapp-extension-installed-div');
document.getElementsByTagName('body')[0].appendChild(div);
And then:
// On web page that needs to detect extension
if ($('#myapp-extension-installed-div').length) {
}
It feels a little hacky, but I couldn't get the other methods to work, and I worry about Chrome changing its API here. It's doubtful this method will stop working any time soon.
If you're trying to detect any extension from any website,
This post helped: https://ide.hey.network/post/5c3b6c7aa7af38479accc0c7
Basically, the solution would be to simply try to get a specific file (manifest.json or an image) from the extension by specifying its path. Here's what I used. Definitely working:
const imgExists = function(_f, _cb) {
const __i = new Image();
__i.onload = function() {
if (typeof _cb === 'function') {
_cb(true);
}
}
__i.onerror = function() {
if (typeof _cb === 'function') {
_cb(false);
}
}
__i.src = _f;
__i = null;
});
try {
imgExists("chrome-extension://${CHROME_XT_ID}/xt_content/assets/logo.png", function(_test) {
console.log(_test ? 'chrome extension installed !' : 'chrome extension not installed..');
ifrm.xt_chrome = _test;
// use that information
});
} catch (e) {
console.log('ERROR', e)
}
Here is how you can detect a specific Extension installed and show a warning message.
First you need to open the manifest file of the extension by going to chrome-extension://extension_id_here_hkdppipefbchgpohn/manifest.json and look for any file name within "web_accessible_resources" section.
<div class="chromewarning" style="display:none">
<script type="text/javascript">
$.get("chrome-extension://extension_id_here_hkdppipefbchgpohn/filename_found_in_ web_accessible_resources.png").done(function () {
$(".chromewarning").show();
}).fail(function () {
// alert("failed.");
});
</script>
<p>We have detected a browser extension that conflicts with learning modules in this course.</p>
</div>
Chrome Extension Manifest v3:
const isFirefox = chrome.runtime.OnInstalledReason.CHROME_UPDATE != "chrome_update";
For FireFox, I believe chrome.runtime.OnInstalledReason.BROWSER_UPDATE will be "browser_update": https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/runtime/OnInstalledReason
The following is affecting my second html page model.html:
If my route address is with a '/ at the end (typed on browser addr field) like so: http://localhost:3002/home/model/ then the correct html page is loaded, but no css/js is loaded.
If my route address is without a '/ at the end like so: http://localhost:3002/home/model then the correct html page is loaded, and css/js is loaded.
Without '/' at the end, css/js loads fine using statics/css/style.css and js/dynamicData.js
The weird part is that when '/' is at the end, I can use ../statics/css/style.css to load the css and "../js/chartData.js" to load js - but that means the one without '/' now longer loads the css/js.
My folder structure:
js - has other js scripts
node_modules
statics
css - has style.css
image - has images
index.html
model.html
index.js - init express server
pc_server.js - express server
Express code (pc_server.js)
Middleware setup?:
process.chdir(__dirname);
// base = '/home'
app.use(base, express.static(__dirname));
Routes:
const INDEX_PAGE = '/';
const MODEL_PAGE = '/home/model';
function setupRoutes(app) {
const BASE = app.locals.base;
app.get(INDEX_PAGE, redirectHome(app));
// BASE = '/home'
app.get(BASE, toHomePage(app));
app.get(MODEL_PAGE, toModelPage(app));
Routes functions defined:
function redirectHome(app) {
return errorWrap(async function(req, res) {
try {
res.redirect(app.locals.base);
}
catch (err) {
console.error(err);
}
});
}
function toHomePage(app) {
return errorWrap(async function(req, res) {
try {
res.sendFile(path.join(__dirname+'/statics/index.html'));
}
catch (err) {
console.error(err);
}
});
}
function toModelPage(app) {
return errorWrap(async function(req, res) {
try {
res.sendFile(path.join(__dirname+'/statics/model.html'));
}
catch (err) {
console.error(err);
}
});
}
The goal is to load the same page with css/js with either http://localhost:3002/home/model/ or http://localhost:3002/home/model
Additional: Why is it that when I type http://localhost:3002/home I get http://localhost:3002/home/ automatically on my browser addr field?
the problem
it probably occurs due to relative links in your site.
when home/model is used - relative css/style.css link will lead to home/css/style.css
when home/model/ is used, the same link will lead to home/model/css/style.css
the solution:
the easiest way to solve it is changing your link tag to:
<link rel="stylesheet" type="text/css" href="../../home/statics/css/style.css">
this link goes to the root address, and then enters your path Independently from the user's path.
why it's working?
the ../../ prefix tell the browser to go two levels up.
the browser consider the "home/model/" as a visit inside a model folder inside home folder. two levels upward lead the browser to the root level, where it has a clean start.
when the user visits "home/model", it considered as a file inside the home folder. one level upward is the root level, and the second ../ does nothing.
after achieving the root level - the browser entering "home/statics/css/style.css" and find the right file in both cases :)
I am trying to remove the # in the following URL: (www.example.com/#section1). How could I do this using the htaccess file. I am sure this could be done using regular expression, but I am not sure how I would do this.
This is what I have written within the htaccess file RewriteRule ^[#].
Thanks for your help!
Hashes (#) are not send to the server, so you can't manipulate them on the server.
If you really need to remove them, you would have to use JavaScript on each page.
// Wait for the page to load, and call 'removeHash'.
document.addEventListener("DOMContentLoaded", removeHash);
document.addEventListener("Load", removeHash);
function removeHash() {
// If there is no hash, don't do anything.
if (!location.hash) return;
// http://<domain></pathname>?<search><#hash>
// Build an URL for the page, sans the domain and hash
var url = location.pathname;
if (location.search) {
// Include the query string, if any
url += '?' + location.search;
}
// Replace the loaded url with the built url, without reloading the page.
history.replaceState('', document.title, url);
}
I'd like to fetch some html from another domain with require.js. I know that CORS policies doesn't allow this easily. Note: I have configured the web server (with Access-Control-Allow-Origin "*" and other directives) and require.js so far that all JS and CSS files (css with require-css plugin) gets loaded from the other domain as expected - just fetching html makes problems. But in the browser network protocol I can see that the html content even gets loaded. However, this content does not get passed to the require function! The browser gets the content, but require.js doesn't provide it as an parameter...
My configuration:
requirejs.config({
baseUrl: "http://some.other.domain/",
paths: {
jquery: 'ext/jquery/jquery.min',
htmlTemplate: 'test.html?',
siteCss: '../css/site'
},
shim: {
htmlTemplate: [
'css!siteCss'
]
},
config: {
text: {
useXhr: function (url, protocol, hostname, port) {
return true;
}
}
},
map: {
'*': {
text: 'ext/require/text',
css: 'ext/require/css.min'
}
}
});
require(['text!htmlTemplate'], function (htmlTemplate) {
console.log(htmlTemplate); // prints 'undefined' into the console
});
Two notes: The useXhr configuration is taken from require.js text plugin adds “.js” to the file name but it makes no difference if it is there or not. I appended a ? to htmlTemplate path. With this the .js does not get appended to the URL and the browser loads the html content - as said before, unfortunately, without that require.js is passing it to parameter htmlTemplate.
What can I do? I've read that if I use the require.js optimizer the generated file wouldn't have this problem anymore (however that works...). But I need to develop my JS without optimization on every edit.
Update: Found one solution but I'd be happy if anyone can provide the 'right' solution.
I've found the actual problem! This part:
config: {
text: {
useXhr: function (url, protocol, hostname, port) {
return true;
}
}
},
should really do it. However, I found out that it wasn't called at all. Instead, the default implementation was called. And this returned false.
To make it work it is necessary to have the right keys in the config section since the mapping doesn't seem to be evaluated for it.
So this is the right configuration that fetches HTML from the other domain:
requirejs.config({
baseUrl: "http://some.other.domain/",
paths: {
jquery: 'ext/jquery/jquery.min',
htmlTemplate: 'test.html', // // ---> removed the '?'
siteCss: '../css/site'
},
shim: {
htmlTemplate: [
'css!siteCss'
]
},
config: {
'ext/require/text': { // ---> full path is required!!!
useXhr: function (url, protocol, hostname, port) {
return true;
}
}
},
map: {
'*': {
text: 'ext/require/text',
css: 'ext/require/css.min'
}
}
});
require(['text!htmlTemplate'], function (htmlTemplate) {
console.log(htmlTemplate); // now prints HTML into the console!!!
});
Hallelujah!
Found the right hint here. Another option might be to set the path for text. At least the configuration must be set somehow so that the function gets called...
I think I've found a solution. Doc of requirejs/text:
So if the text plugin determines that the request for the resource is on another domain, it will try to access a ".js" version of the resource by using a script tag. Script tag GET requests are allowed across domains. The .js version of the resource should just be a script with a define() call in it that returns a string for the module value.
Because of that I changed the configuration to this, so text is not used anymore:
requirejs.config({
baseUrl: "http://some.other.domain/",
paths: {
jquery: 'ext/jquery/jquery.min',
htmlTemplate: 'test.html', // removed the '?'
siteCss: '../css/site'
},
shim: {
htmlTemplate: [
'css!siteCss'
]
},
map: {
'*': {
// removed the text plugin
css: 'ext/require/css.min'
}
}
// removed the useXhr configuration for the text plugin
});
require(['htmlTemplate'], function (htmlTemplate) {
console.log(htmlTemplate); // prints '<div>Here I am!</div>' into the console
});
Now http://some.other.domain/test.html.js gets loaded. The content of test.html is:
define(function () {
return '<div>Here I am!</div>';
});
So I surrounded the HTML with a little bit of JS - no problem to me. And now htmlTemplate is set to the expected string. It's not pure HTML anymore, but since it is a fixed template (i.e. not generated) it may be acceptable.
I am getting No 'Access-Control-Allow-Origin' header is present on the requested resource. after adding the code
config: {
'ext/require/text': { // required!!!
useXhr: function (url, protocol, hostname, port) {
return true;
}
}
},
I'm trying to link to a local file. I've set href as follows:
Link Anchor
In Firefox, when I right click and "open link in new tab", nothing happens.
When I right click and "copy link location", then manually open a new tab and paste the copied link, it works fine. So it seems my file:// syntax is fine. I've also tried it with 3 slashes like file:/// but it's the same result.
What am I doing wrong?
By definition, file: URLs are system-dependent, and they have little use. A URL as in your example works when used locally, i.e. the linking page itself is in the user’s computer. But browsers generally refuse to follow file: links on a page that it has fetched with the HTTP protocol, so that the page's own URL is an http: URL. When you click on such a link, nothing happens. The purpose is presumably security: to prevent a remote page from accessing files in the visitor’s computer. (I think this feature was first implemented in Mozilla, then copied to other browsers.)
So if you work with HTML documents in your computer, the file: URLs should work, though there are system-dependent issues in their syntax (how you write path names and file names in such a URL).
If you really need to work with an HTML document on your computers and another HTML document on a web server, the way to make links work is to use the local file as primary and, if needed, use client-side scripting to fetch the document from the server,
Organize your files in hierarchical directories and then just use relative paths.
Demo:
HTML (index.html)
<a href='inner/file.html'>link</a>
Directory structure:
base/
base/index.html
base/inner/file.html
....
The href value inside the base tag will become your reference point for all your relative paths and thus override your current directory path value otherwise - the '~' is the root of your site
<head>
<base href="~/" />
</head>
This can happen when you are running IIS and you run the html page through it, then the Local file system will not be accessible.
To make your link work locally the run the calling html page directly from file browser not visual studio F5 or IIS simply click it to open from the file system, and make sure you are using the link like this:
Intro
../htmlfilename with .html
User can do this
This will solve your problem of redirection to anypage for local files.
Try swapping your colon : for a bar |. that should do it
Link Anchor
The right way of setting a href=“” when it's a local file.
It will not make any issue when code or file is online.
FAQ
Hope it will help you.
Here is the alternative way to download local file by client side and server side effort:
<a onclick='fileClick(this)' href="file://C:/path/to/file/file.html"/>
Js:
function fileClick(a) {
var linkTag = a.href;
var substring = "file:///";
if (linkTag.includes(substring)) {
var url = '/cnm/document/v/downloadLocalfile?path=' + encodeURIComponent(linkTag);
fileOpen(url);
}
else {
window.open(linkTag, '_blank');
}
}
function fileOpen(url) {
$.ajax({
url: url,
complete: function (jqxhr, txt_status) {
console.log("Complete: [ " + txt_status + " ] " + jqxhr);
if (txt_status == 'success') {
window.open(url, '_self');
}
else {
alert("File not found[404]!");
}
// }
}
});
}
Server side[java]:
#GetMapping("/v/downloadLocalfile")
public void downloadLocalfile(#RequestParam String path, HttpServletResponse
response) throws IOException, JRException {
try {
String nPath = path.replace("file:///", "").trim();
File file = new File(nPath);
String fileName = file.getName();
response.setHeader("Content-Disposition", "attachment;filename=" +
fileName);
if (file.exists()) {
FileInputStream in = new FileInputStream(file);
response.setStatus(200);
OutputStream out = response.getOutputStream();
byte[] buffer = new byte[1024];
int numBytesRead;
while ((numBytesRead = in.read(buffer)) > 0) {
out.write(buffer, 0, numBytesRead);
}
// out.flush();
in.close();
out.close();
}
else {
response.setStatus(404);
}
} catch (Exception ex) {
logger.error(ex.getLocalizedMessage());
}
return;
}