Execute javascript code after AJAX request - javascript

I have created a cookie banner related plugin for my site and now I would like to run the tracking code scripts once the user accepts the cookie banner.
I was able to inject the code with insertAdjacentHTML and now I would like to figure out how to execute this code so that the related tracking cookies are triggered.
I have seen eval(), but I have also seen that it is not a recommended function and it opens a security hole.
This is my code:
http.onreadystatechange = function() { //Call a function when the state changes.
if(http.readyState == 4 && http.status == 200) {
var con_cod = JSON.parse(this.responseText);
var consents = con_cod["consents"];
var head = document.getElementsByTagName("head")[0];
var code_before_end_head = con_cod["code_before_end_head"];
head.lastElementChild.insertAdjacentHTML("afterend", code_before_end_head);
var now = new Date();
var time = now.getTime();
var expireTime = time + 24 * 60 * 60 * 1000;
now.setTime(expireTime);
document.cookie = cookie_name+'='+JSON.stringify(consents)+'; expires='+now.toUTCString()+'; SameSite=None; Secure; path=/';
}
}
http.send(params);
How can I solve this situation? Of course I could also make the page reload, but this is not a good user experience for my visitors.
UPDATE:
I am using the code given here as recommended in the comments:
Jquery cookie monitor
I am now able to see when the cookie is created and modified and give a response accordingly.
I am currently using alerts to make sure of this, but now I would need to run external JavaScript code such as Hotjar or Google Analytics code that if it is just injected (which I am doing) will not run.
This for example is the Hotjar JavaScript code that I am trying to run unsuccessfully:
<!-- Hotjar Tracking Code -->
<script id="gcbi-statistics">
(function(h,o,t,j,a,r){
h.hj=h.hj||function(){(h.hj.q=h.hj.q||[]).push(arguments)};
h._hjSettings={hjid:7349271,hjsv:6};
a=o.getElementsByTagName('head')[0];
r=o.createElement('script');r.async=1;
r.src=t+h._hjSettings.hjid+j+h._hjSettings.hjsv;
a.appendChild(r);
})(window,document,'https://static.hotjar.com/c/hotjar-','.js?sv=');
</script>

I was able to find a much simpler solution.
This is the code I used to inject the various scripts and have them run once inserted:
// Remove comments and HTML tags using the replace function and a regular expression
var plainText = code_before_end_head[key_cod].replace(/<!--[\s\S]*?-->|<[^>]*>/g, '');
// Create a new script element
const script = document.createElement('script');
// Assigns an ID to the script element
script.id = 'gcbi-'+key_con;
// Assigns the code to be executed to the script element
script.innerHTML = plainText;
// Injects the script element into the head section of the document
document.head.appendChild(script);
There was no need to use the listenCookieChange function since everything is done via AJAX.
The code shown above was just inserted inside the request when receiving response from the PHP file.
I hope it can help!

Related

Is using XMLHttpRequest() an outdated way to make an Ajax call?

I'm simply using an example from a book I'm reading. The example is labeled, "Loading HTML with Ajax." This is the JS portion of the code:
var xhr = new XMLHttpRequest();
xhr.onload = function() {
if(xhr.status === 200) {
document.getElementById('content').innerHTML = xhr.responseText;
}
};
xhr.open('GET', 'data/data.html', true);
xhr.send(null);
I'm getting the CSS portion of the code (headers, etc.) when I load the page onto the browser but none of the JS (there should be maps which would load onto the page). The example says I should comment out this portion of the code above:
xhr.onload = function() {
if(xhr.status === 200) {
document.getElementById('content').innerHTML = xhr.responseText;
...if I'm running the code locally without a server but that's not working, either.
Is using XMLHttpRequest() an outdated way to make an Ajax call?
Yes, but it still works and that's not the problem. The more modern way is fetch.
I'm getting the CSS portion of the code (headers, etc.) when I load the page onto the browser but none of the JS (there should be maps which would load onto the page).
That's because assigning HTML that contains script tags to innerHTML doesn't run the script defined by those tags. The script tags are effectively ignored.
To run those scripts, you'll need to find them in the result and then recreate them, something along these lines:
var content = document.getElementById('content');
content.innerHTML = xhr.responseText;
content.querySelectorAll("script").forEach(function(script) {
var newScript = document.createElement("script");
newScript.type = script.type;
if (script.src) {
newScript.src = script.src;
} else {
newScript.textContent = script.textContent;
}
document.body.appendChild(newScript);
});
Note that this is not the same as loading the page with script elements in it directly. The code within script tags without async or defer or type="module" is executed immediately when the closing script tag is encountered when loading a page directly (so that the loaded script can use document.write to output to the HTML stream; this is very mid 1990s). Whereas in the above, they're run afterward.
Note that on older browsers, querySelectorAll's NodeList may not have forEach, that was added just a few years ago. See my answer here if you need to polyfill it.
Because I didn't completely understand T.J.'s answer (no offense, T.J.), I wanted to provide a simple answer for anyone who might be reading this. I only recently found this answer on Mozilla.org: How do you set up a local testing server? (https://developer.mozilla.org/en-US/docs/Learn/Common_questions/set_up_a_local_testing_server). I won't go into details, I'll just leave the answer up to Mozilla. (Scroll down the page to the section titled, "Running a simple local HTTP server.")

Remove an injected analytics library from browser memory in Bigcommerce?

How can we remove this script injector system and clear functions from memory?
Briefing) Recently the malfeasants at Bigcommerce created an analytics injector (JS) under guise of "monitoring" that is locked in a global variable. They have pushed it to all their 50,000 front facing stores without consent from any OP's. This puts in 2 JS libraries and sets up (plain code) triggers for them to track customer, behavior, and store plans throwing data to their shared 3rd party analytics bay. The issue is that although they run the code, they do not own rights to put in 3rd party libraries like this across thousands of domains out of their realm. Does anyone have ideas on how we can kill this + remove from memory? Is this even legal for them to do?
1) The injector is found in the shared global %%GLOBAL_AdditionalScriptTags%% in the HTMLhead.html panel, which means it non-accessible. The AdditionalScriptTags is also dynamic, meaning it loads different JS helpers based on what page is being requested. Removing the variable is a no-go for that reason.
2) The injector uses various DSL variables PHP side to build out its settings. Here is what it looks like in <head> as I browse logged into our store as a customer. This is putting 2 lines for 2 separate libraries which I will define below (note certain tokens hidden as 1234)
(function(){
window.analytics||(window.analytics=[]),window.analytics.methods=["debug","identify","track","trackLink","trackForm","trackClick","trackSubmit","page","pageview","ab","alias","ready","group","on","once","off","initialize"],window.analytics.factory=function(a){return function(){var b=Array.prototype.slice.call(arguments);return b.unshift(a),window.analytics.push(b),window.analytics}};for(var i=0;i<window.analytics.methods.length;i++){var method=window.analytics.methods[i];window.analytics[method]=window.analytics.factory(method)}window.analytics.load=function(){var a=document.createElement("script");a.type="text/javascript",a.async=!0,a.src="http://cdn2.bigcommerce.com/r6cb05f0157ab6c6a38c325c12cfb4eb064cc3d6f/app/assets/js/analytics.min.js";var b=document.getElementsByTagName("script")[0];b.parentNode.insertBefore(a,b)},window.analytics.SNIPPET_VERSION="2.0.8",window.analytics.load();
// uncomment the following line to turn analytics.js debugging on
// shows verbose events and other useful information
// analytics.debug();
var storeId = '123456',
userId = '921';
// initialize with Fornax and Segment.io
var providers = {
Fornax: {
host: 'https://analytics.bigcommerce.com',
cdn: 'http://cdn2.bigcommerce.com/r6cb05f0157ab6c6a38c325c12cfb4eb064cc3d6f/app/assets/js/fornax.min.js',
defaultEventProperties: {
storeId: storeId
}
},
'Segment.io': {
apiKey: '1sbkkbifdq'
}
};
var fornaxEnabled = false;
var segmentIOEnabled = false;
var isStorefront = true;
if (!fornaxEnabled) {
delete providers.Fornax;
}
if (!segmentIOEnabled || isStorefront) {
delete providers['Segment.io'];
}
analytics.initialize(providers);
// identify this user
analytics.identify(
userId || null,
{"name":"Test Dude","email":"test#test.com","storeHash":"123456","storeId":123456,"namespace":"bc.customers","storeCountry":"United States","experiments":{"shopping.checkout.cart_to_paid":"legacy_ui","search.storefront.backend":"mysql"},"storefront_session_id":"6b546880d5c34eec4194b5825145ad60d312bdfe"}
);
})();
3) The output libraries are found as 2 references in the <head> and as you see if you own/demo a BC store, are rather un-touchable:
<script type="text/javascript" async="" src="http://cdn2.bigcommerce.com/r6cb05f0157ab6c6a38c325c12cfb4eb064cc3d6f/app/assets/js/fornax.min.js"></script>
<script type="text/javascript" async="" src="http://cdn2.bigcommerce.com/r6cb05f0157ab6c6a38c325c12cfb4eb064cc3d6f/app/assets/js/analytics.min.js"></script>
How can we break the injector and these trackers and prevent them from loading? Is there a way to remove their functions from memory? Speaking on behalf of many thousands of OP's and segment.io here, we are all at our wits end with this.
I've been hacking away at this too and I found something that works well to disable most/all of it.
Before this line:
%%GLOBAL_AdditionalScriptTags%%
Use this code:
<script type="text/javascript">
window.bcanalytics = function () {};
</script>
So you will end up with something like this:
%%GLOBAL_AdditionalScriptTags%%
<script type="text/javascript">
window.bcanalytics = function () {};
</script>
The <script> tags from part 3 of your question will still load as those are always PREpended before the first non-commented out <script> tag, but most, if not all, the analytics functionality will break, including external calls, and even fornax.js won't load. Hope this helps.
Per the question I linked, for you case to at least remove the scripts from Step 3 this is what you should do :
var xhr = new XMLHttpRequest,
content,
doc,
scripts;
xhr.open( "GET", document.URL, false );
xhr.send(null);
content = xhr.responseText;
doc = document.implementation.createHTMLDocument(""+(document.title || ""));
doc.open();
doc.write(content);
doc.close();
scripts = doc.getElementsByTagName("script");
//Modify scripts as you please
[].forEach.call( scripts, function( script ) {
if(script.getAttribute("src") == "http://cdn2.bigcommerce.com/r6cb05f0157ab6c6a38c325c12cfb4eb064cc3d6f/app/assets/js/fornax.min.js"
|| script.getAttribute("src") == "http://cdn2.bigcommerce.com/r6cb05f0157ab6c6a38c325c12cfb4eb064cc3d6f/app/assets/js/analytics.min.js") {
script.removeAttribute("src");
}
});
//Doing this will activate all the modified scripts and the "old page" will be gone as the document is replaced
document.replaceChild( document.importNode(doc.documentElement, true), document.documentElement);
You must make sure that this is the first thing to run, otherwise the other scripts can and will be executed.

Chrome extension regarding injected script + localstorage

I am puzzling my way through my first 'putting it all together' Chrome extension, I'll describe what I am trying to do and then how I have been going about it with some script excerpts:
I have an options.html page and an options.js script that lets the user set a url in a textfield -- this gets stored using localStorage.
function load_options() {
var repl_adurl = localStorage["repl_adurl"];
default_img.src = repl_adurl;
tf_default_ad.value = repl_adurl;
}
function save_options() {
var tf_ad = document.getElementById("tf_default_ad");
localStorage["repl_adurl"] = tf_ad.value;
}
document.addEventListener('DOMContentLoaded', function () {
document.querySelector('button').addEventListener('click', save_options);
});
document.addEventListener('DOMContentLoaded', load_options );
My contentscript injects a script 'myscript' into the page ( so it can have access to the img elements from the page's html )
var s = document.createElement('script');
s.src = chrome.extension.getURL("myscript.js");
console.log( s.src );
(document.head||document.documentElement).appendChild(s);
s.parentNode.removeChild(s);
myscript.js is supposed to somehow grab the local storage data and that determines how the image elements are manipulated.
I don't have any trouble grabbing the images from the html source, but I cannot seem to access the localStorage data. I realize it must have to do with the two scripts having different environments but I am unsure of how to overcome this issue -- as far as I know I need to have myscript.js injected from contentscript.js because contentscript.js doesn't have access to the html source.
Hopefully somebody here can suggest something I am missing.
Thank you, I appreciate any help you can offer!
-Andy
First of all: You do not need an injected script to access the page's DOM (<img> elements). The DOM is already available to the content script.
Content scripts cannot directly access the localStorage of the extension's process, you need to implement a communication channel between the background page and the content script in order to achieve this. Fortunately, Chrome offers a simple message passing API for this purpose.
I suggest to use the chrome.storage API instead of localStorage. The advantage of chrome.storage is that it's available to content scripts, which allows you to read/set values without a background page. Currently, your code looks quite manageable, so switching from the synchronous localStorage to the asynchronous chrome.storage API is doable.
Regardless of your choice, the content script's code has to read/write the preferences asynchronously:
// Example of preference name, used in the following two content script examples
var key = 'adurl';
// Example using message passing:
chrome.extension.sendMessage({type:'getPref',key:key}, function(result) {
// Do something with result
});
// Example using chrome.storage:
chrome.storage.local.get(key, function(items) {
var result = items[key];
// Do something with result
});
As you can see, there's hardly any difference between the two. However, to get the first to work, you also have to add more logic to the background page:
// Background page
chrome.extension.onMessage.addListener(function(message, sender, sendResponse) {
if (message.type === 'getPref') {
var result = localStorage.getItem(message.key);
sendResponse(result);
}
});
On the other hand, if you want to switch to chrome.storage, the logic in your options page has to be slightly rewritten, because the current code (using localStorage) is synchronous, while chrome.storage is asynchronous:
// Options page
function load_options() {
chrome.storage.local.get('repl_adurl', function(items) {
var repl_adurl = items.repl_adurl;
default_img.src = repl_adurl;
tf_default_ad.value = repl_adurl;
});
}
function save_options() {
var tf_ad = document.getElementById('tf_default_ad');
chrome.storage.local.set({
repl_adurl: tf_ad.value
});
}
Documentation
chrome.storage (method get, method set)
Message passing (note: this page uses chrome.runtime instead chrome.extension. For backwards-compatibility with Chrome 25-, use chrome.extension (example using both))
A simple and practical explanation of synchronous vs asynchronous ft. Chrome extensions

Handle Malformed Remote JSONP Response

this is a continuation of my original question here link
You can see through my rather lengthy conversion with aaronfrost that we determined the jquery was loading in the .php (as seen on the network tab in CHROME) however it's trying to be ran as a script immediately. My question is where or not it's possible to load that in as plain text and simply then do a js parse out the needed data. Doesn't have to be jQuery this was just the route we were going in this example. I've also tried with the following code and recieve the exact same "Unexpected token" error. I think if there were a way to just some how handle the malformed JSON client side we would be able to make this work, in a ugly sort of way.
If javascript doesn't work do you think going the route of a java applet (preserve client cookies, non-server side) would achieve the desired end result i'm looking for?
<script type="application/javascript" src="https://ajax.googleapis.com/ajax/libs/prototype/1.7.0.0/prototype.js"></script>
<script type="application/javascript">
var url = 'http://www.makecashnow.mobi/jsonp_test.php';
//<!-[CDATA[
function JSONscriptRequest(fullUrl) {
// REST request path
this.fullUrl = fullUrl;
// Keep IE from caching requests
//this.noCacheIE = '&noCacheIE=' + (new Date()).getTime();
// Get the DOM location to put the script tag
this.headLoc = document.getElementsByTagName("head").item(0);
// Generate a unique script tag id
this.scriptId = 'JscriptId' + JSONscriptRequest.scriptCounter++;
}
// Static script ID counter
JSONscriptRequest.scriptCounter = 1;
// buildScriptTag method
//
JSONscriptRequest.prototype.buildScriptTag = function () {
// Create the script tag
this.scriptObj = document.createElement("script");
// Add script object attributes
this.scriptObj.setAttribute("type", "text/javascript");
this.scriptObj.setAttribute("charset", "utf-8");
//this.scriptObj.setAttribute("src", this.fullUrl + this.noCacheIE);
this.scriptObj.setAttribute("src", this.fullUrl);
this.scriptObj.setAttribute("id", this.scriptId);
}
// removeScriptTag method
//
JSONscriptRequest.prototype.removeScriptTag = function () {
// Destroy the script tag
this.headLoc.removeChild(this.scriptObj);
}
// addScriptTag method
//
JSONscriptRequest.prototype.addScriptTag = function () {
// Create the script tag
this.headLoc.appendChild(this.scriptObj);
}
var obj = new JSONscriptRequest(url);
obj.buildScriptTag();
obj.addScriptTag();
//]]>
</script>

Using javascript for pinging a webapp to keep session open

I'm writing a greasemonkey script to keep session open on a webapp I use for work.
Which javascript command would you use to create some feedback with the server and ensure the session doesn't fall without having to bother the user making a complete refresh of the page?
I've solved the issue using:
function keepAlive() {
var httpRequest = new XMLHttpRequest();
httpRequest.open('GET', "/restricted_file_url");
httpRequest.send(null);
}
setInterval(keepAlive, 840000); //My session expires at 15 minutes
Using some tips from Jesse's answer, here's the complete code.
You will need to update the #match param with your site's domain
// ==UserScript==
// #name Auto session extender
// #namespace http://obive.net/
// #version 0.2
// #description Automatically extend server-side session
// #author Charlie Hayes
// #match http://obive.net/
// #grant GM_getValue
// #grant GM_setValue
// #noframes
// ==/UserScript==
(function() {
'use strict';
console.log('The session for this site will be extended automatically via userscript.');
var minute = 60*1000;
var refreshTime = 15 * minute;
var iframe = document.createElement("iframe");
iframe.style.width = 0;
iframe.style.height=0;
var loc = window.location;
var src = loc.protocol +'//' + loc.host + loc.pathname;
src += loc.search ? (loc.search + '&') : '?';
src += 'sessionextendercachebuster=';
var reloadIframe = function(){
var time = new Date().getTime();
var lastRefresh = GM_getValue('lastRefresh');
var timeSinceLastRefresh = time - lastRefresh;
if (!lastRefresh || timeSinceLastRefresh > refreshTime - minute) {
console.log('Auto-extending session');
iframe.src = src + time;
GM_setValue('lastRefresh',time);
setTimeout(reloadIframe, refreshTime);
setTimeout(function(){
// Unload the iframe contents since it might be taking a lot of memory
iframe.src='about:blank';
},10000);
}else{
console.log('Another tab/window probably refreshed the session, waiting a bit longer');
setTimeout(reloadIframe, refreshTime - timeSinceLastRefresh - minute);
}
};
setTimeout(reloadIframe, refreshTime);
document.body.appendChild(iframe);
})();
Second choice
If you absolutely insist on using Greasemonkey, any element.click() method on any event that submits an XMLHTTPrequest should do.
First choice
If you are willing to use a solution that does not require you to write a Greasemonkey script:
ReloadEvery 3.0.0, by Jaap Haitsma
This reloads web pages every so many seconds or minutes. The function is accessible via the context menu (the menu you get when you right click on a web page) or via a drop down menu on the reload button.
It's not what you asked for, but unless you simply want to brush up on your javascript skills, this is probably the most straight-forward method for solving your problem.
If you want to write JavaScript to solve this (the other answers are a lot easier and I would recommend going the easier route), here's what I would do:
Call document.createElement("iframe") to create an iframe
Size the iframe to 0x0, style.display = "none", set the ID to something, and set the src property to a page on the site that will extend your session
Use document.body.appendChild to add the iframe to the page
Using setTimeout refresh the iFrame using window.frames['iframeID'].location.reload(true); and also call setTimeout to refresh the page again.
See if the other answers would work because using JavaScript seems like it would be more trouble than it's worth.
I can definitely recommend the solution suggested by dreftymac!
If you don't want to worry about remembering to click the reloadevery option, your grease monkey script is simply
window.location.href = window.location.href
This is presuming you don't want to keep the page constantly refreshing, as suggested by others.
Ordinarily, if you were simply looking to ping the server, you would probably do best to do something like use an image that you know exists on the remote server, and check it's height - thus creating traffic.
However, your problem (I presume) needs to access some limited area of the website, in order to keep the session active... with this being the case (and the fact that you can't directly ping), find a page that is 'restricted' and do an Ajax call, or similar. Don't quote me on this, as I'm by no means a JavaScript expert... just throwing out a potential solution.

Categories