Chrome extension alternatives to local storage? - javascript

I'm looking for a storage mechanism which will persist across websites. I've got primitive data types numbers / strings which I need to store. localStorage doesn't work for me because it falls under the same origin policy. I need my data to be the same across all websites but much be tab specific and accessed via contentscripts.
Could someone suggest a suitable mechanism for achieveing this?
EDIT:
I'm currently implementing the first answers code and not having much look. I've got the following...
background.html
note: tabStorage is a class variable in this function
function store(){
chrome.extension.onRequest.addListener(function(request, sender, sendResponse) {
if(request.req == "save") {
tabStorage[sender.tab.id] = request.data;
}
if(request.req == "load") {
sendResponse(tabStorage[sender.tab.id]);
}
});
}
contentscript.js
response is returning undefined
chrome.extension.sendRequest({req: "load"}, function(response) {
console.log("getting stak");
console.log(response.data);
});
what am I doing wrong? the code is accessing both load and save so I know it's getting there and back but undefined is returning. why?

You need to use extension's own localStorage which you access from a background page. If you need this information in a content script, you need to send a request to a background page using messaging, read it there, and send a result back to a content script in a response.
UPDATE
If you don't need the storage to keep values between browser restarts then you don't need the localStorage. Just store everything in a background page and identify tabs by their ids. Background page will keep the values as long as the browser is opened. For example:
content script:
//save
chrome.extension.sendRequest({cmd: "save", data: {param1: "value1", param2: "value2"});
...
//load
chrome.extension.sendRequest({cmd: "load"}, function(response) {
console.log("tab data:", response)
});
background page:
var tabStorage = [];
chrome.extension.onRequest.addListener(function(request, sender, sendResponse) {
if(request.cmd == "save") {
tabStorage[sender.tab.id] = request.data;
}
if(request.cmd == "load") {
sendResponse(tabStorage[sender.tab.id]);
}
});

Related

Chrome extension - saving login details for popup

I'm working on a Chrome extension that will require the user to log into an account in order to see their content. What I want to do is prevent the user from having to log in every time by saving some kind of cookie or localStorage with relevant login/ session data once they do login.
So far after doing a bit of research it seems that using Google Chromes local storage API would be the best way of doing this (would be great if someone could confirm this).
My issue is with setting and getting local storage. I've setup a dummy local storage scenario here just to try and get Chrome to store ANY kind of local storage for the extension:
Popup.js:
function setCookie(){
chrome.extension.sendMessage({name: 'setLoginCookie'}, function(otherResponse) {
console.log(otherResponse)
})
}
function getCookie(){
chrome.extension.sendMessage({name: 'getloginCookie'}, function(response) {
alert(response)
})
}
event.js:
chrome.extension.onMessage.addListener(function(request, sender, sendResponse) {
if (request.name == 'setLoginCookie') {
var obj = {test:"test"}
chrome.storage.sync.set(obj, function() {
alert('Data saved');
});
}
if (request.name == 'getLoginCookie') {
chrome.storage.sync.get('test', function(data) {
sendResponse({ screenshotUrl: data.test });
})
}
return true;
});
When the setCookie function is executed I get the alert of 'data saved!' which would indicate to me it has worked but when I check the local storage on my extensions Chrome dev tools there is nothing there and if I call the getCookie function then it doesn't pick up anything.
Here are the permissions in my manifest.json:
"permissions": [
"storage",
"cookies",
"tabs",
"<all_urls>",
"pageCapture",
"downloads"
],
Have I used the wrong approach here? Is using chrome.storage.sync perhaps where i've gone wrong?
Thanks
chrome.storage.sync is not the same thing as local storage, you won't see the data saved using this method in the dev tools Local Storage.
You have a message mismatch. You're using {name: 'getloginCookie'} to send the message and the if statement is checking for getLoginCookie. The difference is the lower case l in the parameter you pass to the sendMessage function.
when I check the local storage on my extensions Chrome dev tools there is nothing there
Dev Tools do not expose chrome.storage inside them.
The "Local Storage" item exposes localStorage instead.
To inspect an extension's chrome.storage, you can use Storage Area Explorer extension or simply call chrome.storage.sync.get(null, (data) => { console.log(data) }) from the console.
Note: you don't have to delegate working with chrome.storage to your Event page. chrome.storage is directly accessible from all extension contexts - background, popup, options, content scripts. Don't layer on Messaging just for this.

Call content script on URL change

I'm new to Chrome extensions. I know there's lots of similar questions and info out there, but none of it seems to address this problem.
I need my content script to execute on every page that matches *://*.youtube.com/watch?v=*.
I've tried using the above value and *://*.youtube.com/* as the match property, but neither works supposedly due to the way YouTube handles requests. I've also tried using the onhashchange event, but of course YouTube doesn't use anchors in their URLs. I've read about webRequest, but I don't need the function to be called when somebody is scrolling through the comments and the page loads more comments.
All I need is a way to call my content script when the URL changes. How exactly can I accomplish this?
Additionally, I cannot load the content script at document_start because the extension scrapes the HTML and parses it.
I had the same issue and this is what I did. I added a background script to listen to all changes
chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
if (changeInfo.url) {
chrome.tabs.sendMessage(tabId, {
message: 'hello',
url: changeInfo.url
});
}
});
Then in my content script, I listen to it and reload the URL
chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
if (request.message === 'hello') {
const { url } = request;
chrome.location.replace(url);
}
});

How can I save data from background.js to local machine and fetch this data when required by a button on the popup.html page?

I just wanted to save my background.js data to my local machine and fetch the data to my extension whenever there happens to be a click on some popup.html button. Is there any way to do so and Is it different for different operating systems ?
All Chrome's extensions APIs are cross-platform. Regarding the storage, remember that content scripts run in the context of webpages, not extension pages. So when you access the localStorage from your contentscript, you are retrieving the storage from that particular page not the extension's storage.
Try this:
popup.js
document.getElementById("myButton").addEventListener("click", function(){
chrome.runtime.sendMessage({action: "getStorage", key: "name"}, function(response) {
alert(value.data);
});
});
background.js
chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
if (request.action === "getStorage") {
sendResponse({value: localStorage[request.key]});
}
});

chrome extension - passing data from background to custom html page

Creating browser extension where I have to open new tab from background.js and pass JSON data to this new tab. In new tab I am manipulating/rendering DOM using passed JSON data.
Below is part of my background.js where I create new tab with custom URL and send JSON data object
....
var analyticsUrl = chrome.extension.getURL("analytics.html");
chrome.tabs.create({ url: analyticsUrl, selected: true }, sendDataToAnalytics);
function sendDataToAnalytics(tab)
{
console.log(JSON.stringify(txnDataJSON));
chrome.tabs.sendMessage(tab.id, {"action" : "renderChartsTxns", "txns" : JSON.stringify(txnDataJSON)});
}
....
My custom analytics.html page has
<script src="analytics.js" type="text/javascript"></script>
And analytics.js looks like below
chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
if(request.action == "renderChartsTxns")
{
// JSON parsing and analytics.html DOM processing
});
Problem is, my analytics.js listener is never receiving any messages. I've confirmed that background.js is sending JSON message as expected (using background page debugging)
BTW, analytics.js/html are not registered as part of manifest.json file but these files are part of extension package.
I did this setup today morning and everything was working properly for few hours (was able to receive JSON data in analytics.js), not sure what changed later and I lost message receiving in analytics.js (for debugging I tried clearing browser cache, uninstall and reinstalled chrome and much more but no luck yet!)
The callback of chrome.tabs.create returns as soon as the tab is created, not after it fully loads.
As such, you have a race condition: your message is potentially sent before the listener is initialized. Since it's a race condition, it can sometimes work.
The correct logic here would be to send a message requesting data from the newly opened tab, and use sendResponse to pass that data from the background.
// analytics.js
chrome.runtime.sendMessage({"action" : "getTxns"}, function(txns) {
// Process the data
});
// background.js
// Register this before opening the tab
chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
if(request.action == "getTxns") {
sendResponse(txnDataJSON); // No need to serialize yourself!
}
});

How to check if a certain extension is installed in browser using webpage? [duplicate]

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

Categories