Passing URL of the active tab to my site handler - google-chrome-extension

I suspect this is a total newbie question, but I seem to be missing the basics here. I am NOT new to coding and have a lifetime of experience (27 years) with various languages, but the plugin process is eluding me.
I have developed custom bookmarking system in php & js, it works great and I've been using it for months as I develop it.
I simply want to get the url of the page in the active tab and pass it to my php handler. I want my web site script return the html form into the popup. I can think of a thousand ways that "should" work.
ALL the code examples I am finding seem to over-complicate what should be a simple task.
In short I just want:
<script type="text/javascript">
<!--
var loadurl = "http://my.site.com?theUrl=" + window.location;
location.href = loadurl;
//-->
</script>
And have that page show in the popup. So far I'm at a loss. Even tried ajax calls etc.
Can somebody clue me in on how to achieve this simple task? Maybe I can get started writing extensions with the info.
For the record, most of the examples I have found are deprecated under manifest 2.0

Manifest 2.0 introduces a new feature contentSecurityPolicy. All external resources are blocked by default. For the best practice, you should include all needed asset files in the extension. The communication between your extension and your service (php side) is only data using XHR2.
So, In order to make bookmark extension work, I guess you need to something like this:
Add your service's domain to permissions array
{
...
permissions: ['*://my.site.com/*', 'tabs']
}
Move all javascript from popup.html to popup.js. In popup.js, You create a ajax request to your bookmarking service. More document here
function addBookmark(url) {
var xhr = new XMLHttpRequest();
xhr.open("GET", "http://my.site.com/new_bookmark.php?url=" + encodeURIComponent(url), true);
xhr.onreadystatechange = function() {
if (xhr.readyState == 4) {
var resp = xhr.responseText;
// handle service result here
}
}
xhr.send();
}
chrome.tabs.getSelected(null,function(tab) {
addBookmark(tab.url);
});

Related

Manifest v3 fetch data from external API

I'm developing a Browser (Chrome, Firefox) extension with react with Manifest v3.
In my app, I have a search bar where I show suggested words based on the value typed by the user (like search engine do).
In manifest v2, I used to load this script like so:
"content_security_policy": "script-src 'self' https://suggest.finditnowonline.com; object-src 'self'"
In v3 this is not supprted anymore but I cannot find the way how I could still make my code work.
he most relevant resource I found online are this answer: Content Security Policy in Manifest V3 for Facebook Page Plugin
and this documentation: https://www.chromium.org/Home/chromium-security/extension-content-script-fetches
But I cannot understand how I can implement my script from the background.js page since It needs to fetch the API dynamically, every time the user type something in the input field.
This is the react code: where I fetch the api
useEffect(() => {
const fetchSuggestedWords = async () => {
try {
const res = await fetchJsonp(`${process.env.SUGGESTED_WORDS_URL}${searchValue}`)
const suggestedWordsArray = await res.json()
setSuggestedWords(suggestedWordsArray[1].slice(0, 10))
return
} catch {
console.log('error fetching suggested results')
}
}
if (searchSuggestedWords) {
fetchSuggestedWords()
}
}, [searchValue])
Where searchValue is a state update whenever the onChange event is trigger on the input field.
Any tips on how to approach this new format?
Would people recommend not switching to Manifest v3 just yet?
From what I gathered, you're trying to talk to an api and not load an external script.
If you're trying to load external, that will not work.
Fetching data on the other hand has multitudes of ways it can be done, not all are proper though.
Also I've noticed that you're misunderstanding the core keys of the async messaging system and service worker cycle.
Add a service worker
*Background.js or svc-worker.js
and give it a on message listener, try to at least handle messaging between your extension, if you're not sure, you can always get an example on github.
After that, it's a matter of setting the CSP and optimizing where you'll be fetching the data.
After a little while, I'm sure you'll get the hang of it.
the code in content should be like
content.js
inputElement.oninput = (e) => {let input = e.target.value;
chrome.runtime.sendMessage({Origin: 'Content', Message: input})};
Handle the message in svc worker
svc-worker.js formatted
chrome.runtime.onMessage(request => {
const {Origin, Message} = request
// parse message
// fetch chain here
})
Please note, this is not the only way to handle this.
There's a billion different ways, if you want to add some of this data in a use Effect hook as a dependency, that's going to be tricky, as the data given obtained is always counted as different when obtained via message. < At least in my case scenario.

Chrome extension - This extension may have been corrupted

I created an extension for Chrome. I need to allow the user to change a javascript file within the extension for various reasons. When the whatever.js file is changed at C:\Users\\AppData\Local\Google\Chrome\User Data\Default\Extensions\cidaekdakljdsijofjahinafcafmanb\6.0.5_0\whatever.js Chrome comes back with "This extension may have been corrupted." and I have to reinstall the extension.
How do I stop this? How do I allow the changing of javascript file? I'm just trying to set a variable. Can I include a text file and change that instead? Under what circumstances can I get Chrome to stop doing this?
Can I add a user script? whatever.user.js? I read about that somewhere.
Thanks.
You can fix it by following proper development practices. If you are making the user edit the code to charge a variable, thats a huge coding issue.
Instead it should be changeable with a user interface, for example in the extension options page. If you want full control of the option, download it from a server or read it from a web accesible extension resource json file. here is an example that reads from such file since you asked it in comments:
var url = chrome.extension.getURL("options.json"); //in manifest under web_accessible_resources
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function (e) {
if (xhr.readyState == 4 && xhr.status == 200) {
var options=null;
try {
options = JSON.parse(xhr.responseText);
}
catch (e) {
console.log("error: cant parse options.");
}
if (options) {
//got it!
}
}
};
xhr.open("GET", url);
xhr.send();
this also simplifies a lot the user experience and possible user errors. just the fact that each OS stores and caches the code differently would be a nightmare to support.
and no, you cant bypass that chrome security measure meant to protect users from i.e. viruses modifying the extension source code.

chrome extension - alternative to externally_connectable?

It seems like the externally_connectable feature that allows a website to communicate with an extension is still in the dev channel and not yet stable. Are there any other ways to allow a specific website to communicate with my extension, while I wait for this feature to become stable? How have chrome extension developers traditionally done it?
Thanks Rob W for pointing me in the direction of HTML5 messaging. For the benefit of other chrome extension developers, I'm writing about the general problem I was trying to solve and the solution that worked in the end.
I am making a chrome extension that can control music playback on a tab via a popup player. When a user clicks on play/pause/etc on the popup player, the extension should be able to convey that message to the webpage and get back a response stating whether the action was accomplished.
My first approach was to inject a content script into the music player page. The problem is, though, that content scripts operate in a "sandbox" and cannot access native javascript on the page. Therefore, the content script was pretty useless (on its own), because while it could receive commands from the extension, it could not effect any change on the webpage itself.
One thing that worked in my favor was that the website where the music was playing belongs to me, so I could put whatever javascript I wanted there and have it be served from the server. That's exactly what I used to my advantage: I created another javascript file that would reside on the website and communicate with the content script mentioned above, via the window object of the page (i.e. HTML5 messaging). This only works because the content script and the javascript file both exist in the same webpage and can share the window object of the page. Thanks Rob W for pointing me to this capability. Here is an example of how the javascript file on the page can initiate a connection with the content script via the window object:
content_script.js (injected by extension into xyz.com):
window.addEventListener("message", function(event) {
if(event.data.secret_key &&
(event.data.secret_key === "my_secret_key") &&
event.data.source === "page"){
if(event.data.type){
switch(event.data.type) {
case 'init':
console.log("received connection request from page");
window.postMessage({source: "content_script", type: 'init',
secret_key: "my_secret_key"}, "*");
break;
}
}
}
}, false);
onpage.js (resides on server and served along with xyz.com):
window.postMessage({source: "page", type: 'init',
secret_key: "my_secret_key"}, "*");
window.addEventListener("message", function(event) {
if(event.data.secret_key &&
(event.data.secret_key === "my_secret_key") &&
event.data.source === "content_script"){
if(event.data.type){
switch(event.data.type) {
case 'init':
console.log("connection established");
break;
}
}
}
}, false);
I check the secret key just to make sure that the message originates from where I expect it to.
That's it! If anything is unclear, or if you have any questions, feel free to follow up!
You could have an extension inject a content script alongside a web page, and use that to pass messages back and forth between the website and the background page of the extension.
It's tedious, though, and externally connectable is a lot nicer.

How do you integrate Universal Analytics in to Chrome Extensions?

The chrome extension guide has a tutorial for the old analytics install: https://developer.chrome.com/extensions/tut_analytics.html
The instructions just say to link to the https version and update the manifest to allow loading scripts from that URL. So those should still apply to the new version. And in fact I can see the script loading from the server.
Once the script loads analytics does not properly initialize it self and never processes it's internal queue (ga.f) to send those events to the server. There is no error in the console. It's just quietly does nothing.
My guess is that the new Universal Analytics is just not set up to run in the the extension environment but the universal docs make no mention of that: https://developers.google.com/analytics/devguides/collection/analyticsjs/
does anyone know if it's even possible to add Universal Analytics to an extension yet and when that might be added?
There's an issue for that on Google code: The solution is to pass analytics your own protocol check function or simply null for no checking, in an official way.
This has to come after ga('create', ...) :
ga('set', 'checkProtocolTask', null); // Disable file protocol checking.
So you don't need to modify the original analytics.js script. Just include the standard tracking code snippet (dont' forget to add the "https:" prefix) and add "https://www.google-analytics.com" to your Content Security Policy.
A note to ayal gelles' solution:
It is not necessary to add chrome-extension://... to the Content Security Policy since it's already included in the 'self' statement. Also, instead of loading the script via hardcoded URL you should use chrome.runtime.getURL("path/to/analytics.js"). This way you don't need to know your extension's ID, Chrome will fill it in for you.
I wrote up a blog post on this - How to add Google’s Universal Analytics tracking to a Chrome extension
Here's the guts of it:
// Standard Google Universal Analytics code
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','https://www.google-analytics.com/analytics.js','ga'); // Note: https protocol here
ga('create', 'UA-XXXXX-YY', 'auto');
ga('set', 'checkProtocolTask', function(){});
ga('send', 'pageview', '/options.html');
There are 3 points I’d particularly like to highlight:
Specify “https” at the start of the script address to match with the listing in the manifest.json file
Override checkProtocolTask with an empty function
Send a virtual pageview by specifying the path – /options.html – otherwise Google Analytics will reject a URL in the format chrome-extension://gdocgfhmbfbbbmhnhmmejncjdcbjkhfc/options.html
I just encountered this and seem to have hacked my way through. This might break at some point or not be fully functional, but here goes:
Download the GA uglified+minified source code from here: https://www.google-analytics.com/analytics.js, put in your chrome extension folder, where it could be later loaded by the background page.
In it, find a function that looks something like this:
function Oa(){var a=M[B][E];if("http:"!=a&&"https:"!=a)throw"abort";}.
This is the "point of failure" since our "protocol" is "chrome-extension:" and not either of the two.
So.. change this function to be something like:
function Oa(){var a=M[B][E];if("chrome-extension:"!=a&&"http:"!=a&&"https:"!=a)throw"abort";}
add a "Content Security Policy" of this sort to your manifest file, make sure it points to YOUR LOCAL version of analytics.js you have just modified:
"content_security_policy": "script-src 'self' chrome-extension://EXTENSIONID/path/to/analytics.js; object-src 'self'",
Change the GA snippet to ALSO point to that same file, something like this:
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','chrome-extension://EXTENSIONID/path/to/analytics.js','ga');
hope this helps.
I managed to get Google Analytics up and running using Chrome Platform Analytics (CPA). The only confusing part is how to set up a property in the administration console of GA. I had to create a Mobile Application property, which is not too intuitive.
Also, I created an options page that lets users disable analytics if desired, to comply with the opt-out requirements.
I hope that helps!
Regarding new analytics.js (as opposite to old ga.js) this example works for me:
function setupGoogleAnalytics() {
if (!window.ga) {
(function(){
window.ga = function() {
(window.ga.q = window.ga.q || []).push(arguments);
}, window.ga.l = 1 * new Date();
var tag = 'script';
var a = document.createElement(tag);
var m = document.getElementsByTagName(tag)[0];
a.async = 1;
a.src = 'https://www.google-analytics.com/analytics.js';
m.parentNode.insertBefore(a, m);
})();
ga('create', 'UA-XXXXXXX-Y', 'auto');
ga('set', 'checkProtocolTask', null);
}
}
Please note that you need to add following content_security_policy snippet to the manifest.json:
{
...
"content_security_policy": "script-src 'self' https://www.google-analytics.com; object-src 'self'"
...
}
There's a way to use the Measurement Protocol to communicate with Google Analytics.
I have developed a script for that :
https://github.com/melalj/universal-ga-extension

Opening and writing to a new window from a Google Chrome extension sandbox page

(Cross posted here)
Hi,
I have a sandboxed page (specified in my manifest) which is loaded into an iframe in my extension's background page. From within my sandboxed page, I'd like to open a new window and write to it, i.e.:
var win = window.open(); win.document.write('<p&gtHello!</p>');
This works from my extension's background page and from regular web pages, but when invoked from either content scripts or my sandboxed page, the window opens, but I cannot access the win object (it's defined, but empty---console.log outputs "Window {}").
I assume this is due to same-origin policies (with every window being given a uinque-origin within the sandboxed environment). However, since the window opens an about:blank page, I'm confused why this would matter.
Is this a feature? Is there a parameter I can add to my manifest to avoid this? And does anyone know of work-arounds that don't involve using postMessage back to my background page? My ideal solution is to have my sandboxed script open a new window and interact with it directly, not with message passing.
I can provide a full example if necessary, but I'm hoping someone might just know off the top of their head. I'm running Chrome 24.0.1312.57 on Mac and 24.0.1312.68 on Ubuntu if that helps.
Thanks,
Hank
Content scripts, by definition, are part of external regular web pages you load so I'm not really sure how your script could work on a "regular web page" but not the content script. Do you mean the code works when you embed it in your own pages, but not in other pages via the content script?
Regardless, if the script is working properly from your background page, you could always try messaging. (more here: http://developer.chrome.com/extensions/messaging.html)
Script for your sandbox/contentscript:
//send message to background page
chrome.extension.sendMessage({todo: "newWindow"});
In background page:
//create a listener
chrome.extension.onMessage.addListener(
function(request, sender) {
if (request.todo === "newWindow") {
//do your stuff here
var win = window.open(); win.document.write('<p&gtHello!</p>');
}
});
Per the cross-post here, the issue is indeed that the opened window is given a unique origin. This was intentional as the members of the standards working group (SWG) felt that it would be more secure to not make an exception for about:blank pages where they inherit the sandbox's origin.
However, to get around this issue, at least for my purposes, I can use the following method. First, forget sandboxing. This workaround uses an iframe embedded in a background page with the src url set to data:text/html,.... This gives a unique origin to the iframe so it's no longer in extension space. That means eval can be used and chrome apis cannot be accessed. Unlike in a sandbox, windows opened from the iframe share that same origin as the iframe, allowing created windows to be accessed. For example:
In a background html page:
<html>
<head>
...
<script src="background.js"></script>
...
</head>
<body>
...
<iframe id="myframe"></iframe>
...
</body>
</html>
In background.js:
...
document.getElementById('myframe').setAttribute('src', 'data:text/html,'+
encodeURI('<html><head>'+
'<script src='+chrome.extension.getURL('jquery.js')+'></script>'+
'<script src='+chrome.extension.getURL('myscript.js')+'></script>'+
...
'</head><body></body></html>'
));
...
In myscript.js
jQuery(document).ready(function(){
...
// To receive messages from background.js.
window.addEventListener('message', function(e){ ... } );
// To send messages to background.js.
parent.postMessage({...}, '*');
// To open AND ACCESS a window.
var win = window.open();
win.document.write('Hello'); // Fails in sandbox, works here.
// Eval code, if you want. Can't do this from an extension
// page loaded normally unless you allow eval in your manifest.
// Here, it's okay!
eval( 'var x = window.open(); x.document.write("Hi!")' );
// No chrome apis.
chrome.log( chrome.extension ); // -> undefined
chrome.log( chrome.windows ); // -> undefined
// No direct access to background page (e.g., parent).
chrome.log( parent ); // -> Window with no properties.
...
});

Resources