var open = XMLHttpRequest.prototype.open;
XMLHttpRequest.prototype.open = function(method, uri, async, user, pass) {
this.addEventListener("readystatechange", function(event) {
if(this.readyState == 4){
var self = this;
var response = {
method: method,
uri: uri,
responseText: self.responseText
};
console.log(response);
} else {
console.log(this.readyState);
}
}, false);
open.call(this, method, uri, async, user, pass);
};
I am trying to listen for XHR before they are being sent. Something similar to jQuery's beforeSend in the $.ajax method.
My goal is to listen for all XHR's before they are being sent. I suppose the closest thing would be to check above if this.readyState === 1?
Would the code above cause any ajax libraries like jQuery to malfunction because I use prototype on XMLHttpRequest?
I am trying to listen for XHR before they are being sent.
Then try to spoof the send() method, not the open() one.
Would the code above cause any ajax libraries like jQuery to malfunction because I use prototype on XMLHttpRequest?
No, not really. Only,
it won't work where those libs choose not to use XMLHttpRequest (particularly IE)
…and even fail if the browser does not support the XMLHttpRequest object (or does not support accessing or modifying its prototype)
libs might be able to work around your spoof by dereferencing the functions before you can (though I don't know any common lib that does)
Your code calls the native method with a fixed number of arguments, not sure if that affects anything, and it does not re-return the result (even if we know it's undefined). To be 100% sure, use return open.apply(this, arguments);.
You need to insert this script as a DOM element, but the following code is what works for me for 90% of all network requests:
response_interceptor.js
(function () {
console.log('🔫 REQUEST INTERCEPTOR INJECTED');
let CE_id = 'Your Chrome Extension ID';
let WithNetworkPayload;
let gotResponse = false;
let job_network_payload = [];
let job_network_urls = [];
var XHR = XMLHttpRequest.prototype;
var send = XHR.send;
var open = XHR.open;
XHR.open = function (method, url) {
this.url = url; // the request url
request_list.push({
request: this,
args: arguments,
});
job_network_urls.push(url);
return open.apply(this, arguments);
};
//Get response from chosen request
XHR.send = function () {
this.addEventListener('load', function () {
console.log('Got Response 🤯');
console.log(this);
try {
this.response
.text()
.then((res) => {
job_network_payload.push({ endpoint: this.url, payload: res });
console.log(res);
})
.catch((err) => {
job_network_payload.push({ endpoint: this.url, payload: err });
console.log('❌ Error on getting response.Text()');
console.log(err);
});
} catch (err) {
console.log('❌ Error on getting response.Text()');
console.log(err);
job_network_payload.push({
endpoint: this.url,
payload: this.response,
});
}
//sendResponsetoBackground(this.response);
});
return;
};
})();
Use a common contentScript.js to add response_interceptor.js as a DOM element:
contentScript.js
function inject_response_interceptor() {
function interceptData() {
var xhrOverrideScript = document.createElement('script');
xhrOverrideScript.type = 'text/javascript';
xhrOverrideScript.src = chrome.runtime.getURL(
'./injected_scripts/response_interceptor.js'
);
document.head.prepend(xhrOverrideScript);
}
let interval = setInterval(() => {
if (document.head) {
clearInterval(interval);
interceptData();
}
}, 100);
}
inject_response_interceptor();
In the manifest dont forget to add the contentScript.js to be injected at documentStart:
manifest.js
...
"content_scripts": [{
"matches": ["http://*/*", "*://*/*"],
"run_at": "document_start",
"js": ["contentScript.js"]
}]
Related
I am writing some code to intercept and log ever XHR request sent over JavaScript, but I am unable to find where to access the request payload from. Is this possible to do? My code is below, this logs the url, method, and response, but no payload.
let oldXHROpen = window.XMLHttpRequest.prototype.open;
window.XMLHttpRequest.prototype.open = function (
method,
url,
async,
user,
password
) {
console.log("---hooked request---");
console.log({ url });
console.log({ method });
console.log(this);
this.addEventListener("load", function () {
console.log("load: " + this.responseText);
});
return oldXHROpen.apply(this, arguments);
};
I am basicly using 3 diffrent JS methods to get the data from the api but they return the error 405: Method Not Allowed but server has the get method in it as allowed. It is using path for the variable so I am wondering if it is related to that.
Here are the Codes for Methods that i call the API.
Fetch Method;
function getCompleted(queryParam) {
$('#loader').removeClass('hidden');
$('#loaded').addClass('hidden');
fetch("****/fullprofile/" + queryParam, {
method: "GET", headers: {
"User": "*****",
"Content-Type": "application/json"
}
})
.then((data) => {
const contentType = response.headers.get('content-type');
console.log(contentType);
return data.json()
})
.then(function (result) {
ResponseBody = result;
$('#loader').addClass('hidden');
$('#loaded').removeClass('hidden');
}).catch(function () {
$('#loader').addClass('hidden');
$('#loaded').removeClass('hidden');
});
}
HTTP Request Method;
function httprequest(queryParam2) {
$('#loader').removeClass('hidden');
$('#loaded').addClass('hidden');
var xmlhttp = new XMLHttpRequest();
xmlhttp.withCredentials=true;
var url = "*****/fullprofile/";
xmlhttp.onreadystatechange = function (data) {
console.log(this.responseText);
console.log(data);
}
xmlhttp.open("GET", url + queryParam2);
xmlhttp.setRequestHeader("User", "*****");
xmlhttp.send();
}
Ajax Method;
function ajax(queryParam3) {
$.ajax({
url: "****/fullprofile/" + queryParam3,
"method":"GET",
"headers":{
"User":"EBT\\****"
},
success: function (data) {
ResponseBody = data;
console.log(data);
}
});
}
Thank you all for the advices and help.
the reason was sending with headers; it returns options that needs to be responded again and it wasn't worth doing in JS so i decided to make a gateway api to use the api i have with header.
Thank you.
It is possible that the resource you are trying to consult does not use the verb GET.
you have to confirm with which verb the resource is obtained
Look here:
405 Method Not Allowed MDN Docs
Confirm in the documentation of the api with which verb that resource is requested. I could be POST, PUT etc
Here you can look explanation
I am trying to add a custom header on all network requests going from application and I am trying to do this via service worker fetch.
The content of header needs to come from app(client), so I need to wait for a response from client before responding to any event.
Below is my attempt to achieve this
Here is my fetch listener code
function send_message_to_client(client, msg){
return new Promise(function(resolve, reject){
var msg_chan = new MessageChannel();
msg_chan.port1.onmessage = function(event){
if(event.data.error){
reject(event.data.error);
}else{
resolve(event.data);
}
};
client.postMessage("SW Says: '"+msg+"'", [msg_chan.port2]);
});
}
self.addEventListener('fetch', function (event) {
event.waitUntil(async function () {
const client = await clients.get(event.clientId);
send_message_to_client(client, "Pass Transaction Details")
.then(function (m) {
var req = new Request(event.request.url, {
method: event.request.method,
headers: event.request.headers,
mode: 'same-origin',
credentials: event.request.credentials,
redirect: 'manual'
});
var res_obj = JSON.parse(m);
req.headers.append('MY_CUSTOM_HEADER', res_obj.hdr_val);
return event.respondWith(fetch(req));
})
.catch(function (error) {
console.log("Error after event.respondWith call");
console.log(error);
});
}());
});
and here is how I registered this worker and its message listener
navigator.serviceWorker.register('/my-sw.js', {scope: '/'})
.then(function(reg) {
navigator.serviceWorker.onmessage = function (e) {
var msg_reply = {
"message" : "Replying to SW request",
};
msg_reply.hdr_val = sessionStorage.getItem('__data_val');
console.log("Replying with "+ JSON.stringify(msg_reply));
e.ports[0].postMessage(JSON.stringify(msg_reply));
};
}).catch(function(error) {
// registration failed
console.log('Registration failed with ' + error);
});
but apparently its shooting 2 requests, 1 original request and 1 with modified headers.
Any idea what am I doing wrong ? I am a newbie in javascript so pardon me if there is some stupid mistake.
From service worker debug console, I found that its going in catch block right after event.respondWith() call, so something wrong there probably ?
You must call FetchEvent.respondWith() synchronously in your fetch handler. In your code you are calling waitUntil() synchronously, but calling respondWith() later. By not calling respondWith() before returning from the fetch handler you are telling the browser to continue on with its normal networking path without interception.
I think you want something like this:
self.addEventListener('fetch', function (event) {
// call respondWith() here
event.respondWith(async function () {
const client = await clients.get(event.clientId);
send_message_to_client(client, "Pass Transaction Details")
.then(function (m) {
var req = new Request(event.request.url, {
method: event.request.method,
headers: event.request.headers,
mode: 'same-origin',
credentials: event.request.credentials,
redirect: 'manual'
});
var res_obj = JSON.parse(m);
req.headers.append('MY_CUSTOM_HEADER', res_obj.hdr_val);
// just return the response back to the respondWith() here
return fetch(req);
})
// You probably want to add a .catch() to return some reasonable fallback
// response.
Following scenario. I have a simple login-form for username and password.
Wher the user clicks the login-button the form is posted to the server which is checking the credentials. When they are okay, a redirect is made to the default landing-page.
For sending the form-data I have written the following:
static async postData<TResult>(options: { method: string, url: string, data?: any }): Promise<TResult> {
return new Promise<TResult>((resolve, reject) => {
try {
const xhr = new XMLHttpRequest();
xhr.onload = (): void => {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
resolve(xhr.response);
} else {
reject(xhr.status);
}
}
};
xhr.withCredentials = true;
xhr.open(options.method, options.url, true);
xhr.send(options.data);
} catch (e) {
console.error(e);
}
});
}
which is called like:
const response = await postData({ method: theForm.method, url: theForm.action, data: formData })
.then(response => {
console.log(response);
})
.catch(reason => {
console.error(reason);
});
In Chrome and Firefox the response has an responseURL-property which I could use for setting window.location.url = xhr.responseURL; to redirect the page. But this wont work in IE as the response-type is something completely other.
On the other hand I have the complete HTML (which is the content of the redirect aka the landing-page) in xhr.responseText but I have no idea how I can use this?
I tried to replace the page-content as described here But this is throwing a bunch of errors in Chrome and IE it's not working at all showing
SCRIPT70: Permission denied
Some additional notes. I am using TypeScript to write the client-code which is targeted to es6 and afterwards transpiled using babel-loader to es5 via webpack.
I think you need try next code:
location.href = 'http://www.example.com' or
location.assign("http://www.mozilla.org"); // or
location = "http://www.mozilla.org";
I'm working on some chrome-extension. The Google Developer Console is configured and using gapi eventually works, but I've got problem with, let's say, UX.
So here is scenario I'm trying to acheive:
Try to authenticate with Google Chrome logged in user.
If fail, try to authenticate via gapi.auth.authorize with immediate: true.
If fail, try to authenticate via gapi.auth.authorize with immediate: false.
And this kind of works. I get the popup which asks for permission, I click Accept, but then popus goes blank, title is set to "Connecting..." (doesn't close) and callback function is never fired.
I know access is granted because when I click accept and reload the page, it can authorize using immediate:true and my extension works perfectly.
I check few issues, topics and questions asking different queries in google searching for the answer and I found this sollutions:
setTimeout(checkAuth, 1) - tried, no success.
I deduced that immediate:false cannot be called right after immediate:true, so I give it a try and tried to authenticate with immediate:false as first. Same results.
I tried adding gapi.auth.init, and checkingAuth in it's callback (also using setTimeout).
So here is bit of code (background.js). Sorry it looks like spaghetti, I'm beginner in JS.
function respond(interactive, sendResponse) {
xhrWithAuth('GET',
'https://www.googleapis.com/gmail/v1/users/me/profile',
interactive, // false
onUserMailFetched, sendResponse);
function xhrWithAuth(method, url, interactive, callback, sendResponse) {
var access_token;
var retry = true;
getToken();
// 1. trying to use Chrome user
function getToken() {
chrome.identity.getAuthToken({
interactive: interactive
}, function (token) {
if (chrome.runtime.lastError) {
// 2. here lastError is User is not signed in. Calling onUserMailFetched
callback(chrome.runtime.lastError, null, null, sendResponse);
}
access_token = token;
requestStart();
});
}
// I guess not important in topic
function requestStart() {
var xhr = new XMLHttpRequest();
xhr.open(method, url);
xhr.setRequestHeader('Authorization', 'Bearer ' + access_token);
xhr.onload = requestComplete;
xhr.send();
}
// I guess not important in topic
function requestComplete() {
if (this.status == 401 && retry) {
retry = false;
chrome.identity.removeCachedAuthToken({
token: access_token
},
checkAuth_neverCalled);
} else {
callback(null, this.status, this.response, sendResponse);
}
}
// I guess not important in topic
function checkAuth_neverCalled() {
console.log("checking auth when getAuthToken fails");
gapi.auth.authorize({
client_id: OAUTH2_CLIENT_ID,
scope: OAUTH2_SCOPES,
immediate: false
}, handleAuthResult);
// Handle the result of a gapi.auth.authorize() call.
function handleAuthResult(authResult) {
console.log("authenticated: ", authResult);
if (authResult) {
// do something with data
} else {
consoel.log("failed");
}
}
}
}
// This is important part.
function onUserMailFetched(error, status, response, sendResponse) {
if (!error && status == 200) {
// do something with data
} else {
// 3. as we have error at first call, we checkAuth with immediate = true
setTimeout(function () {
checkAuthWhenNotLogged(sendResponse, true);
}, 10);
}
}
// This is important part.
function checkAuthWhenNotLogged(sendResponse, immediateVal) {
gapi.auth.authorize({
client_id: OAUTH2_CLIENT_ID,
scope: OAUTH2_SCOPES,
immediate: immediateVal
}, handleAuthResult);
// Handle the result of a gapi.auth.authorize() call.
// 5. But this function is never called again (when called with false).
function handleAuthResult(authResult) {
if (authResult) {
// 4. and this is called when checkAuth with true fail. We call checkAuth (itself) with false.
if (authResult.error == "immediate_failed") {
gapi.auth.init(function () {
setTimeout(function () {
checkAuthWhenNotLogged(sendResponse, false);
}, 10);
});
} else {
// yay, we are authneticated and can call gmail service
gapi.client.load('gmail', 'v1', function () {
var request = gapi.client.gmail.users.getProfile({
'userId': 'me'
});
request.execute(function (profile) {
// do something with data
});
});
}
} else {
console.log("failed");
}
}
}
}
Any hint, link or solution will be apreciated.
Ok, here is what I did to make OAuth2 work.
Scenario looks like:
Try to authenticate with Google Chrome logged in user.
If fail, try to authenticate via gapi.auth.authorize with immediate: true.
If fail, try to use chrome.identity.launchWebAuthFlow
First of all I need to explain why launchWebAuthFlow wasn't working earlier. As I mention, I configured the Google Developers Console and created key and client id as for Chrome Application. This was wrong for launchWebAuthFlow. It should be Web Application with configured redirect URL.
In chrome extension here is how you can get redirect url:
var redirectURL = chrome.identity.getRedirectURL("suffix");
It will create something like this:
https://{appId}.chromiumapp.org/
You need to set this as redirect link in your Client ID configuration. In my case I had to change used Client ID in js code.
Here is code that works for me:
(...)
var redirectURL = chrome.identity.getRedirectURL();
var options = {
'interactive': true,
url: 'https://accounts.google.com/o/oauth2/auth?' +
'scope=profile email' +
'&response_type=token' +
'&client_id=' + OAUTH2_CLIENT_ID_WEB +
'&redirect_uri=' + redirectURL
}
chrome.identity.launchWebAuthFlow(options, function (redirectUri1) {
if (chrome.runtime.lastError) {
console.log(chrome.runtime.lastError);
} else {
// redirectUri is a link with access_token param inside, we need to extract it
var paramName = "access_token"
var results = new RegExp(paramName + '=([^&#]*)').exec(redirectUri1);
if (results == null) {
console.log("not found");
} else {
console.log(results[1] || 0);
access_token = results[1]; // here we set the token
requestStart(); // here I launch google api request
}
};
});
(...)
function requestStart() {
// url = 'https://www.googleapis.com/plus/v1/people/me'
// method = 'GET'
var xhr = new XMLHttpRequest();
xhr.open(method, url);
xhr.setRequestHeader('Authorization', 'Bearer ' + access_token);
xhr.onload = requestComplete;
xhr.send();
}
function requestComplete() {
if (this.status == 401 && retry) {
retry = false;
chrome.identity.removeCachedAuthToken({
token: access_token
},
checkAuth);
} else {
callback(this.status, this.response);
}
}
Hope someone will take advantage of this. I know I spent way too much time on this.