chrome.webRequest doesn't catch all requests - google-chrome-extension

I'm writing chrome extension and I need to catch all requests from the beginning of downloading page.
I'm using chrome.webRequest.onBeforeRequest in background.js, and send them to content.js for logging.
But I don't see all requests. It looks like background.js start working with delay (and I missing important requests). How can I avoid it?
Here is my background.js:
chrome.webRequest.onBeforeRequest.addListener(logURL, { urls: ["<all_urls>"] });
function logURL(requestDetails) {
chrome.tabs.query({ active: true, currentWindow: true }, function(tabs) {
chrome.tabs.sendMessage(
tabs[0].id,
{ message: requestDetails.url },
function(response) {}
);
});
}
and content.js
chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
if (request.message == "reload") {
location.reload();
console.log("reload");
} else {
console.log(request.message);
}
});
How can I catch all request or run my background.js previously? Maybe I have made mistakes and can't find them?
Thanks!

If somebody will have the same mistake:
chrome.webRequest catch all requests, but content.js file inject later (after few requests).
So you should collect requests in background.js and send them after content.js injection (for example you can send a message to background.js).

Related

Chrome Extension - Monitoring network traffic with body data in background [duplicate]

It seems to be difficult problem (or impossible??).
I want to get and read HTTP Response, caused by HTTP Request in browser, under watching Chrome Extension background script.
We can get HTTP Request Body in this way
chrome.webRequest.onBeforeRequest.addListener(function(data){
// data contains request_body
},{'urls':[]},['requestBody']);
I also checked these stackoverflows
Chrome extensions - Other ways to read response bodies than chrome.devtools.network?
Chrome extension to read HTTP response
Is there any clever way to get HTTP Response Body in Chrome Extension?
I can't find better way then this anwser.
Chrome extension to read HTTP response
The answer told how to get response headers and display in another page.But there is no body info in the response obj(see event-responseReceived). If you want to get response body without another page, try this.
var currentTab;
var version = "1.0";
chrome.tabs.query( //get current Tab
{
currentWindow: true,
active: true
},
function(tabArray) {
currentTab = tabArray[0];
chrome.debugger.attach({ //debug at current tab
tabId: currentTab.id
}, version, onAttach.bind(null, currentTab.id));
}
)
function onAttach(tabId) {
chrome.debugger.sendCommand({ //first enable the Network
tabId: tabId
}, "Network.enable");
chrome.debugger.onEvent.addListener(allEventHandler);
}
function allEventHandler(debuggeeId, message, params) {
if (currentTab.id != debuggeeId.tabId) {
return;
}
if (message == "Network.responseReceived") { //response return
chrome.debugger.sendCommand({
tabId: debuggeeId.tabId
}, "Network.getResponseBody", {
"requestId": params.requestId
}, function(response) {
// you get the response body here!
// you can close the debugger tips by:
chrome.debugger.detach(debuggeeId);
});
}
}
I think it's useful enough for me and you can use chrome.debugger.detach(debuggeeId)to close the ugly tip.
sorry, mabye not helpful... ^ ^
There is now a way in a Chrome Developer Tools extension, and sample code can be seen here: blog post.
In short, here is an adaptation of his sample code:
chrome.devtools.network.onRequestFinished.addListener(request => {
request.getContent((body) => {
if (request.request && request.request.url) {
if (request.request.url.includes('facebook.com')) {
//continue with custom code
var bodyObj = JSON.parse(body);//etc.
}
}
});
});
This is definitely something that is not provided out of the box by the Chrome Extension ecosystem. But, I could find a couple of ways to get around this but both come with their own set of drawbacks.
The first way is:
Use a content script to inject our own custom script.
Use the custom script to extend XHR's native methods to read the response.
Add the response to the web page's DOM inside a hidden (not display: none) element.
Use the content script to read the hidden response.
The second way is to create a DevTools extension which is the only extension that provides an API to read each request.
I have penned down both the methods in a detailed manner in a blog post here.
Let me know if you face any issues! :)
To get a XHR response body you can follow the instructions in this answer.
To get a FETCH response body you can check Solution 3 in this article and also this answer. Both get the response body without using chrome.debugger.
In a nutshell, you need to inject the following function into the page from the content script using the same method used for the XHR requests.
const constantMock = window.fetch;
window.fetch = function() {
return new Promise((resolve, reject) => {
constantMock.apply(this, arguments)
.then((response) => {
if (response) {
response.clone().json() //the response body is a readablestream, which can only be read once. That's why we make a clone here and work with the clone
.then( (json) => {
console.log(json);
//Do whatever you want with the json
resolve(response);
})
.catch((error) => {
console.log(error);
reject(response);
})
}
else {
console.log(arguments);
console.log('Undefined Response!');
reject(response);
}
})
.catch((error) => {
console.log(error);
reject(response);
})
})
}
If response.clone().json() does not work, you can try response.clone().text()
I show my completed code if it can be some help. I added the underscore to get the request url, thanks
//background.js
import _, { map } from 'underscore';
var currentTab;
var version = "1.0";
chrome.tabs.onActivated.addListener(activeTab => {
currentTab&&chrome.debugger.detach({tabId:currentTab.tabId});
currentTab = activeTab;
chrome.debugger.attach({ //debug at current tab
tabId: currentTab.tabId
}, version, onAttach.bind(null, currentTab.tabId));
});
function onAttach(tabId) {
chrome.debugger.sendCommand({ //first enable the Network
tabId: tabId
}, "Network.enable");
chrome.debugger.onEvent.addListener(allEventHandler);
}
function allEventHandler(debuggeeId, message, params) {
if (currentTab.tabId !== debuggeeId.tabId) {
return;
}
if (message === "Network.responseReceived") { //response return
chrome.debugger.sendCommand({
tabId: debuggeeId.tabId
}, "Network.getResponseBody", {
"requestId": params.requestId
//use underscore to add callback a more argument, passing params down to callback
}, _.partial(function(response,params) {
// you get the response body here!
console.log(response.body,params.response.url);
// you can close the debugger tips by:
// chrome.debugger.detach(debuggeeId);
},_,params));
}
}
I also find there is a bug in chrome.debugger.sendCommand. If I have two requests with same URI but different arguments. such as:
requests 1:https://www.example.com/orders-api/search?limit=15&offer=0
requests 2:https://www.example.com/orders-api/search?limit=85&offer=15
The second one will not get the corrected responseBody, it will show:
Chrome Extension: "Unchecked runtime.lastError: {"code":-32000,"message":"No resource with given identifier found"}
But I debugger directly in background devtools, it get the second one right body.
chrome.debugger.sendCommand({tabId:2},"Network.getResponseBody",{requestId:"6932.574"},function(response){console.log(response.body)})
So there is no problem with tabId and requestId.
Then I wrap the chrome.debugger.sendCommand with setTimeout, it will get the first and second responseBody correctly.
if (message === "Network.responseReceived") { //response return
console.log(params.response.url,debuggeeId.tabId,params.requestId)
setTimeout(()=>{
chrome.debugger.sendCommand({
tabId: debuggeeId.tabId
}, "Network.getResponseBody", {
"requestId": params.requestId
//use underscore to add callback a more argument, passing params down to callback
}, _.partial(function(response,params,debuggeeId) {
// you get the response body here!
console.log(response.body,params.response.url);
// you can close the debugger tips by:
// chrome.debugger.detach(debuggeeId);
},_,params,debuggeeId));
},800)
}
I think the setTimeout is not the perfect solution, can some one give help?
thanks.

Chrome Extension: Check if iframe and current page has loaded

I've built an extension that loads a React-application via an iframe.
I'm passing a message from the application and listening for that message in content script to ensure application has loaded.
Here's a simplified version of what I have so far:
App.js
componentDidMount() {
window.top.postMessage({isAppLoaded: true}, '*'};
}
Content.js
chrome.runtime.onMessage.addListener(function (msg, sender) {
if (msg.isPageLoaded) {
console.log('Current page has loaded!');
}
}
window.addEventListener('message', function(event) {
if (event.data.isAppLoaded) {
console.log('React app successfully loaded!');
}
}
Background.js
chrome.tabs.onUpdated.addListener(function (tabId, info) {
if (info.status === 'complete') {
chrome.tabs.query({active: true, currentWindow: true}, function(tabs) {
chrome.tabs.sendMessage(tabs[0].id, {isPageLoaded: true});
})
}
})
On some websites the page loads first then the React-app and vice-versa on some other websites. This becomes more complicated with websites that have lots of embedded iframes such as LinkedIn.
What is the correct/better way to guarantee the React-app AND the page has finished loading?
Please ignore the wildcard message passing. I'm aware of its security vulnerabilities. Above code snippets are simplified version of what I have.

Link to history in a Chrome Extension

I would like to link to chrome://history page in a button of an extension I'm working, but with href="chrome://history" the console says Not allowed to load local resource: chrome://history/
What can I do?
Thanks and greetings
Edited:
I'm trying with this:
The button I want to trigger with the event has .btn-history class.
In content.js:
function messaging(){
chrome.runtime.sendMessage({command: "openHistory"});
}
document.addEventListener('DOMContentLoaded', function () {
document.getElementsByClassName('btn-history')[0].addEventListener('click', messaging);
});
In background.js:
chrome.runtime.onMessage.addListener(function(message, sender, sendResponse) {
if(command == "openHistory") {
chrome.tabs.create({'url': 'chrome://history'});
}
});
But doesn't work.
If you are using jquery, you can have a button in your html, like so:
<button id="historyBtn">History</button>
And in your javascript you can use define an event handler for the button using jquery:
$(function(){
$("#historyBtn").click(function(){
chrome.tabs.create({url: "chrome://history"});
});
});
If you would like to change the currently active tab to open history, you can replace the chrome.tabs.create line in the above snippet with something like:
chrome.tabs.query({ active: true, currentWindow: true }, function (tabs) {
chrome.tabs.update(tabs[0].id, { url: "chrome://history" });
});
Hope this helps :)

How to send responses from onMessageExternal Listener?

I am sending a message from a content script to my extension using chrome.runtime.sendMessage.
Content Script:
chrome.runtime.sendMessage(extensionId, {
"some" : "request"
}
},
function(response) {
alert("got response");
}
);
The receiving part in the background script looks like that:
Background Script A
chrome.runtime.onMessageExternal.addListener(function(request, sender, sendResponse) {
console.log("send response");
sendResponse({
"some" : "response"
});
});
This works well: send response is logged to the console of the background page and I receive the alert got response from the content script.
I then introduced some asynchronous JavaScript to the listener:
Background Script B
chrome.runtime.onMessageExternal.addListener(function(request, sender, sendResponse) {
window.crypto.subtle.generateKey({
name: "RSASSA-PKCS1-v1_5",
modulusLength: 2048,
publicExponent: new Uint8Array([1, 0, 1]),
hash: {
name: "SHA-256"
}
}, false, ["sign"]).then(function(keyPair) {
console.log("send response");
sendResponse({
"some" : "response"
});
});
});
here the callback is called regularly (meaning, send response is logged to the console of the background page after completion. The content script however never receives the response (i.e. there is no got response alert).
What am I doing wrong here? Why is the responseCallback called in A but not in B? That's exactly what the callback chain is made for or isn't it?
This seems to be a bug/feature in chrome. Apparently you need to return true, otherwise the message channel will be closed:
https://developer.chrome.com/extensions/runtime.html#event-onMessageExternal
This function becomes invalid when the event listener returns, unless you return true from the event listener to indicate you wish to send a response asynchronously (this will keep the message channel open to the other end until sendResponse is called).
Adding return true; to the background script like this solves the issue:
chrome.runtime.onMessageExternal.addListener(function(request, sender, sendResponse) {
window.crypto.subtle.generateKey({
name: "RSASSA-PKCS1-v1_5",
modulusLength: 2048,
publicExponent: new Uint8Array([1, 0, 1]),
hash: {
name: "SHA-256"
}
}, false, ["sign"]).then(function(keyPair) {
console.log("send response");
sendResponse({
"some" : "response"
});
});
return true;
});

chrome extension - getting the last url instead of current

I'm getting the last visited url instead of the current one. (when i go to site.com and after that to site2.com, the url I get is 'site.com' and after I refresh site2.com I'm getting the right one.
Based on the answers here:
Google Chrome Extension get page information
Display current URL in a chrome extension
I've come up with this code:
manifest.json
{
"manifest_version": 2,
"browser_action": {
"default_popup": "action.html"
},
"content_scripts": [
{
"matches": ["http://*/*", "https://*/*"],
"js": ["content.js"],
"run_at": "document_end"
}
],
"background": {
"scripts": ["background.js"]
},
"permissions": [
"tabs",
"unlimitedStorage",
]
}
content.js
chrome.extension.sendRequest({method: "getUrl"}, function(response) {
console.log(response.data);
});
background.js
chrome.extension.onRequest.addListener(function(request, sender, sendResponse) {
if(request.method == "getUrl") {
chrome.tabs.query({ currentWindow: true, active: true }, function (tabs) {
currentUrl = tabs[0].url;
});
sendResponse({data: currentUrl});
}
else
sendResponse({}); // snub them.
});
I've also tried to put this code below directly in content.js and I'm getting an error and in background.js and the url is set to chrome://extensions/.
chrome.tabs.query({ currentWindow: true, active: true }, function (tabs) {
currentUrl = tabs[0].url;
});
what is the right way to do this ?
First of all:
chrome.extension.sendRequest/onRequest are deprecated. Please, use chrome.runtime.sendMessage/onMessage instead.
Also, whenever possible, prefer event pages over background pages. Most of the time it takes next to nothing to convert a background page to an event page, but can save consideably on resources.
The problem:
chrome.tabs.query is asynchronous. It means that, when called, it initializes the request and then completes its execution so the next part of your script gets executed. Once the request has completed, the registered callback function is executed. So, this is what happens in your case:
You visit http://www.site1.com.
chrome.tabs.query({...}, callback) gets executed (notice the callback has not been executed yet).
sendResponse({data: currentUrl}) gets executed (note that at this point currentUrl is not defined).
chrome.tabs.query's request completes and the callback function is executed, setting currentUrl = "http://www.site1.com" (but only after it is too late).
You visit http://www.site2.com.
chrome.tabs.query({...}, callback) gets executed (notice the callback has not been executed yet).
sendResponse({data: currentUrl}) gets executed (note that at this point currentUrl still equals http://www.site1.com from step 4).
chrome.tabs.query's request completes and the callback function is executed, setting currentUrl = "http://www.site2.com" (again only after it is too late).
The solutions:
(A)
Move the logic inside the callback.
(Note: Whenever the callback (i.e. sendResponse) is going be called asynchronously, it is necessary for the onMessage listener to return true.)
chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
if(request.method && (request.method == "getURL")) {
if (sendResponse) {
chrome.tabs.query({
currentWindow: true,
active: true
}, function(tabs) {
if (tabs.length > 0) {
sendResponse({ data: tabs[0].url });
}
});
/* Since 'sendResponse' is to going be called
* asynchronously, it is necessary to return 'true' */
return true;
}
} else {
// Do not talk to strangers !!!
}
});
(B)
Even simpler in your case, the sender parameter of the onMessage listener, contains the field tab which contains the URL info. E.g.:
chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
if(request.method && (request.method == "getURL") && sendResponse) {
sendResponse({ data: sender.tab.url });
}
});
(C)
If you only need the URL in the context of the content script, why don't you use location.href from within the content script ?

Resources