Chrome extension run_at every post request it makes? - google-chrome

I have figured out how to run a script on every page load when it is done explicitly by user but I want to run my script each time it makes a post or get request in its back end on its own to the database or ad server implicitly. [For example on gmail if we keep our eyes on requests (maybe firebug - console - all) we will see that after certain time a POST request is getting fired from the browser on its own. ]
Is there any way I can do that?
Actually I am writing my first extension so it clearly states I don't know much about it.

You should use webRequest module in your extension. After specifing proper permissions in the manifest, for example:
"permissions": [
"webRequest",
"*://*/*"
],
"background": {
"scripts": ["background.js"]
},
you can register in your background page ("background.js" in the example) any required handlers, such as onBeforeRequest, onBeforeSendHeaders, onHeadersReceived, onCompleted, and others. I think the names are self-explaining, but you can consult with abovementioned documentation.
Depending from your requirements, you can define event handlers which prevent requests, modify headers, just read and somehow analyse http-headers.
Example for reading http headers and possibly changing them:
chrome.webRequest.onBeforeSendHeaders.addListener(
function(details)
{
console.log(details.url);
if(details.method == 'POST')
{
// do some stuff
for(var i = 0; i < details.requestHeaders.length; ++i)
{
// log or change some headers
// details.requestHeaders[i].name
// details.requestHeaders[i].value
}
}
return {requestHeaders: details.requestHeaders};
},
{urls: ["<all_urls>"]},
["blocking", "requestHeaders"]);

Related

how to access IndexedDB (of current opened domain/tab) from chrome extension

I currently have indexedDB on google.com domain. i want to be able to read it from google chrome extension. how can i accomplish this? do i need to add any specific permissions?
i currently have:
"permissions": [ "tabs", "bookmarks", "unlimitedStorage", "*://*/*", "identity", "https://*.google.com/*", "https://ssl.gstatic.com/", "https://www.googleapis.com/", "https://accounts.google.com/" ],
with what command i can do this? thank you!
Edit: i have readed i can access it from content script(aslong as the tab with domain is open - which is my case), but i dont know how to do that...
To access indexeddb of current tab add "activeTab" to "permissions" tag in manifest.json, Then create a content script, content script will be helpful in accessing the indexeddb as it runs in context of webpages, then add the content script created to the "content_scripts" tag in manifest.json file.
For Eg in manifest.json add the following:
"permissions": ["activeTab"],
"content_scripts": [
{
"matches": ["add the domains of the webpages where content script needs to run"],
"js": ["contentScript.js"]
}
]
For more info on matches check out here: https://developer.chrome.com/extensions/match_patterns
.
Inside content script add open the store and then perform transaction on the object store and perform queries on the object store.
For Eg in content script add following:
if (!('indexedDB' in window)) {
alert("This browser doesn't support IndexedDB");
} else {
let indexdb = window.indexedDB.open('firebaseLocalStorageDb', 1);
indexdb.onsuccess = function () {
let db = indexdb.result;
let transaction = db.transaction('firebaseLocalStorage', 'readwrite');
let storage = transaction.objectStore('firebaseLocalStorage');
console.log(storage.getAll());
};
}
Explanation of the above code:
It accesses the window object and opens the store "firebaseLocalStorageDb" with version "1", then after successfully accessing the object it looks for the result and performs transaction on the objectstore "firebaseLocalStorage" residing inside the store. Finally query the instance of objectstore "storage" to get all the key-value pairs.
For more info check: https://javascript.info/indexeddb
For anyone still interested, my solution to this problem -
this is placed in content script of extension -
chrome.extension.onConnect.addListener(function(port) {
if(port.name == "extension_request" ) {
port.onMessage.addListener(function(msg) {
if (msg.db) {
window.indexedDB.webkitGetDatabaseNames().onsuccess = function(sender,args)
{
var r = sender.target.result;
if(r.contains(msg.db)){
var openRequest = indexedDB.open(msg.db);
// your code
port.postMessage({foo: bar}); // your result which you want to send
}
}
}
}
}
and this is for background or popup script -
chrome.tabs.query({active: true, currentWindow: true}, function(tabs) {
var port = chrome.tabs.connect(tabs[0].id,{name: "extension_request"});
port.postMessage({db: "database_name_example"}); // send database name
port.onMessage.addListener(function(msg) {
if (msg.foo ) {
// do your stuff in extension
}
}
}

Chrome Packaged App with SQLite?

I was trying to integrate sql.js(JS based SQLite https://github.com/kripken/sql.js/) into my chrome app but as I launch my app, console shows the following errors:
Refused to evaluate a string as JavaScript because 'unsafe-eval' is not an allowed source of script in the following Content Security Policy directive: "default-src 'self' chrome-extension-resource:". Note that 'script-src' was not explicitly set, so 'default-src' is used as a fallback.
Uncaught EvalError: Refused to evaluate a string as JavaScript because 'unsafe-eval' is not an allowed source of script in the following Content Security Policy directive: "default-src 'self' chrome-extension-resource:".
My manifest file looks like this:
{
"manifest_version": 2,
"name": "Chrome App",
"description": "This is the test app!!!",
"version": "1",
"icons": {
"128": "icon_128.png"
},
"permissions": ["storage"],
"app": {
"background": {
"scripts": ["background.js"]
},
"content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self'"
},
"minimum_chrome_version": "28"
}
#MarcRochkind I would like to add some knowledge to your book for integrating SQL.js in Chrome Apps.
It is very well possible with very little effort (considered the obedience of policies and rules).
In order to integrate anything that uses eval, you need to sandbox that particular part of the script. In case of SQL.js, it's the entire library.
This can be done with an iframe which needs to be set in the main .html document that's called for creating a (or the main) window, e.g. chrome.app.window.create('index-app.html', { ..
The base of communication between the main document and the iframe will be by using postMessage for sending and receiving messages.
Let's say the source of this iframe is called /iframes/sqljs-sandboxed.html.
In the manifest.json you need to specify sqljs-sandboxed.html as a sandbox. A designated sandbox makes it possible to run eval and eval-like constructs like new Function.
{
"manifest_version": 1,
"name": "SQL.js Test",
..
"sandbox": {
"pages": [
"iframes/sqljs-sandboxed.html",
]
}
}
The sqljs-sandboxed.html uses an event listener to react on an event of type message. Here you can simply add logic (for simplicity sake I used a switch statement) to do anything structured with SQL.js.
The content of sqljs-sandboxed.html as an example:
<script src="/vendor/kripken/sql.js"></script>
<script>
(function(window, undefined) {
// a test database
var db = new SQL.Database();
// create a table with some test values
sqlstr = "CREATE TABLE hello (a int, b char);";
sqlstr += "INSERT INTO hello VALUES (0, 'hello');";
sqlstr += "INSERT INTO hello VALUES (1, 'world');";
// run the query without returning anything
db.run(sqlstr);
// our event listener for message
window.addEventListener('message', function(event) {
var params = event.data.params,
data = event.data.data,
context = {};
try {
switch(params.cmd) {
case '/do/hello':
// process anything with sql.js
var result = db.exec("SELECT * FROM hello");
// set the response context
context = {
message: '/do/hello',
hash: params.hash,
response: result
};
// send a response to the source (parent document)
event.source.postMessage(context, event.origin);
// for simplicity, resend a response to see if event in
// 'index-app.html' gets triggered a second time (which it
// shouldn't)
setTimeout(function() {
event.source.postMessage(context, event.origin);
}, '1000');
break;
}
} catch(err) {
console.log(err);
}
});
})(window);
</script>
A test database is created only once and the event listener mirrors an API using a simple switch. This means in order to use SQL.js you need to write against an API. This might be at, first glance, a little uncomfortable but in plain sense the idea is equivalent when implementing a REST service, which is, in my opinion, very comfortable in the long run.
In order to send requests, the index-app.html is the initiator. It's important to point out that multiple requests can be made to the iframe asynchronously. To prevent cross-fire, a state parameter is send with each request in the form of an unique-identifier (in my example unique-ish). At the same time a listener is attached on the message event which filters out the desired response and triggers its designated callback, and if triggered, removes it from the event stack.
For a fast demo, an object is created which automates attachment and detachment of the message event. Ultimately the listen function should eventually filter on a specific string value, e.g. sandbox === 'sql.js' (not implemented in this example) in order to speed up the filter selection for the many message events that can take place when using multiple iframes that are sandboxed (e.g. handlebars.js for templating).
var sqlRequest = function(request, data, callback) {
// generate unique message id
var hash = Math.random().toString(36).substr(2),
// you can id the iframe as wished
content_window = document.getElementById('sqljs-sandbox').contentWindow,
listen = function(event) {
// attach data to the callback to be used later
this.data = event.data;
// filter the correct response
if(hash === this.data.hash) {
// remove listener
window.removeEventListener('message', listen, false);
// execute callback
callback.call(this);
}
};
// add listener
window.addEventListener('message', listen, false);
// post a request to the sqljs iframe
content_window.postMessage({
params: {
cmd: request,
hash: hash
},
data: data
}, '*');
};
// wait for readiness to catch the iframes element
document.addEventListener('DOMContentLoaded', function() {
// faking sqljs-sandboxed.html to be ready with a timeout
setTimeout(function() {
new sqlRequest('/do/hello', {
allthedata: 'you need to pass'
}, function() {
console.log('response from sql.js');
console.log(this.data);
});
}, '1000');
});
For simplicity, I'm using a timeout to prevent that the request is being send before the iframe was loaded. From my experience, it's best practice to let the iframe post a message to it's parent document that the iframe is loaded, from here on you can start using SQL.js.
Finally, in index-app.html you specify the iframe
<iframe src="/iframes/sqljs-sandboxed.html" id="sqljs-sandbox"></iframe>
Where the content of index-app.html could be
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
</head>
<body>
<iframe src="/iframes/sqljs-sandboxed.html" id="sqljs-sandbox"></iframe>
<h1>Hello, let's code with SQL.js!</h1>
<script src="/assets/js/sqljs-request.js"></script>
</body>
</html>
"content_security_policy" is not a documented manifest property of Chrome Apps.
To my knowledge, sql.js is not compatible with Chrome Apps, as your error message indicates.
A variation of SQLite, Web SQL, is specifically documented as not working with Chrome Apps.
IndexedDB does work with Chrome Apps, but (a) it's not SQL-based, and (b) it's of limited utility because it's sandboxed and data is not visible to other apps, not even other Chrome Apps.
Your reference to "Chrome Packaged Apps" may mean that you're thinking of legacy "packaged apps," which operate under different rules than the newer Chrome Apps. However, packaged apps are no longer supported by Google and should not be developed. Perhaps you were looking at documentation or examples of package apps, not Chrome Apps.

Chrome Extension: How to change headers on every page request programmatically?

I'm currently developing a Chrome Extension and need to add/change a header value, but only on a specific page. Something like this:
chrome.onPageRequest(function(host) {
if(host == 'google.com') {
chrome.response.addHeader('X-Auth', 'abc123');
}
});
Any help would be greatly appreciated :)
You can use the chrome.webRequest API for that purpose. You'll need the following:
Declare the appropriate permissions in your manifest:
...
"permissions": [
...
"webRequest",
"*://*.google.com/*"
]
Register a listener for the chrome.webRequest.onHeadersReceived() event and modify the headers. In order to be able to modify the headers, you need to define the 'responseHeaders' extra info (see 3rd arguments of listener function):
chrome.webRequest.onHeadersReceived.addListener(function(details) {
console.log(details);
details.responseHeaders.push({
name: 'X-Auth',
value: 'abc123'
});
return { responseHeaders: details.responseHeaders };
}, {
urls: ['*://*.google.com/*']
}, [
"responseHeaders"
]);
Keep in mind that the webRequest permission only works if your background-page is persistent, so remove the corresponding line from your manifest (if it exists - which it should):
...
"background": {
"persistent": false, // <-- Remove this line or set it to `true`
"scripts": [...]
...
Also, keep in mind that pretty often Google redirects requests based on the user's country (e.g. redirecting www.google.com to www.google.gr), in which case the filter will not let them reach your onHeadersReceived listener.

Blocking request in Chrome

I'm trying to block some requests in a Chrome app.
I created a JavaScript listener that does this validation:
chrome.webRequest.onBeforeRequest.addListener(
{
urls: ["*://site.com/test/*"]
},
["blocking"]
);
But the requests are not blocking. Did I miss something in this code?
My manifest:
"background": {
"scripts": ["listener.js"],
"persistent": true
},
"permissions": ["tabs", "http://*/*"],
"manifest_version": 2,
It looks like you misunderstood the meaning of "blocking" here.
https://developer.chrome.com/extensions/webRequest.html#subscription
If the optional opt_extraInfoSpec array contains the string 'blocking'
(only allowed for specific events), the callback function is handled
synchronously. That means that the request is blocked until the
callback function returns. In this case, the callback can return a
BlockingResponse that determines the further life cycle of the
request.
To block a request (cancel it), return {cancel: true} in your event handler.
For example:
chrome.webRequest.onBeforeRequest.addListener(
function() {
return {cancel: true};
},
{
urls: ["*://site.com/test/*"]
},
["blocking"]
);
This will block all URLs matching *://site.com/test/*.
Also remember to declare both webRequest and webRequestBlocking permissions in your manifest.
From Chrome 59 you can block specific requests from Network tab of developer tools itself.
https://developers.google.com/web/updates/2017/04/devtools-release-notes#block-requests
Right-click on the request in the Network panel and select Block Request URL. A new Request blocking tab pops up in the Drawer, which lets you manage blocked requests.

Capture link a user clicks on a page

I am playing around with making an extension. If a user has the extension installed, I would like to capture the link they clicked on a web page. Not quite sure how to do this, but it seems simple. I might add, I would like this to happen as long as the plugin is installed and enabled, but DON'T want the user to have to do anything in the toolbar to 'activate' it.
Not sure how to start. And I figure I have one too many JS files, but just trying to get one of them to log to the console. Neither do. My end goal is I would like to redirect them to an intranet page if they go to certain places.
background.js
var redirectedSites = ["https://www.facebook.com/profile.php?id=<SOMEPROFILEID>"];
// when the browser tries to get to a page, check it against a list
chrome.webRequest.onBeforeRequest.addListener(
function(details) {
console.log('is this even getting hit?');
for(var i=0; i < redirectedSites.length; ++i) {
// if the attempt is to a listed site, redirect the request
if( details.url == redirectedSites[i] )
return {redirectUrl: "http://intranet/landing?from=" + details.url };
}
},
{urls: ["*://www.facebook.com/*"]},
["blocking"]
);
manifest.json
{
"name": "Capture Click",
"version": "0.1",
"description": "Simple tool that logs clicked links.",
"permissions": [
"tabs",
"webRequest",
"webRequestBlocking",
"https://*.facebook.com/*"
],
"background": {
"scripts": ["background.js"]
},
"manifest_version": 2
}
I've given some advice in the comments, but the best way to solve your actual larger problem is with a webRequest handler:
var redirectedSites = ["http://www.google.com/foobar", ...];
// when the browser tries to get to a page, check it against a list
chrome.webRequest.onBeforeRequest.addListener(
function(details) {
for(var i=0; i < redirectedSites.length; ++i) {
// if the attempt is to a listed site, redirect the request
if( details.url == redirectedSites[i] )
return {redirectUrl: "http://intranet/landing?from=" + details.url };
}
},
{urls: ["*://www.google.com/*"]},
["blocking"]);
This is a really simple example, but I hope you get the idea. Here, details.url is the page the user is trying to get to, and the returned object has a redirectUrl property that redirects the attempt to visit the page. My example checks details.url against a list of target sites; you could use a regex or something else that's more robust.
Note that this will affect not only clicked links and typed-in URLs, but also resources (scrips, images) and Ajax requests.