KISSMetrics sometimes causes trouble when loading on my site. Those familiar with it will probably know about the following snippet which appears in their docs:
<!-- Kissmetrics tracking snippet -->
<script type="text/javascript">var _kmq = _kmq || [];
var _kmk = _kmk || '61d817358af517ab2975dbb768eeb1d6d0d07c4b';
function _kms(u){
setTimeout(function(){
var d = document, f = d.getElementsByTagName('script')[0],
s = d.createElement('script');
s.type = 'text/javascript'; s.async = true; s.src = u;
f.parentNode.insertBefore(s, f);
}, 1);
}
_kms('//i.kissmetrics.com/i.js');
_kms('//scripts.kissmetrics.com/' + _kmk + '.2.js');
</script>
cf.: https://app.kissmetrics.com/setup/integration/9ac2445636036f9151b84b444b1ae78d105d0f7a
This is really fancy and I want to know why I can't do it more simply than this.
Opening the file i.kissmetrics.com/i.js we see the one-liner:
if(typeof(_kmil) == 'function')_kmil();
which means 'run _kmil() if you recognize _kmil as a function.' Meanwhile, _kmil() is an alias of KM.ikmq(), a function defined in the second script that you see.
It seems that the trouble appears when that second script, scripts.kissmetrics.com/61d817358af517ab2975dbb768eeb1d6d0d07c4b.2.js° either fails to load or has a delay in loading.
I want to do the following instead:
Put scripts.kissmetrics... into the head node of my html, and then, rather than include i.js, simply run the function window.KM.ikmq() just before I register my listeners for KISSMetrics-tracked click events.
What would be the drawbacks of this, if any?
Totally optional bonus question: what is the purpose of the first line of code in the snippet above var _kmq = _kmq || []; if the variable _kmq is not used in the remainder of the script?
° the hexadecimal string is a fake key used here for demonstration purposes
Related
After doing an XXS site check, one error that pops up in the console is the inline use of the scripts needed for Google Analytics Tag Manager.
I have now created an external analytics.js file, which I load in the header and created an IIFE to load the script, then checked that it works in GA (it does):
var load_google_tag_manager = function(){
var script = document.createElement("script");
var head = document.getElementsByTagName('head')[0];
script.async = true;
script.type = "text/javascript";
script.src = "https://www.googletagmanager.com/gtag/js?id=UA-XXXXXX-1";
head.appendChild(script);
}();
window.dataLayer = window.dataLayer || [];
function gtag() {
dataLayer.push(arguments);
}
gtag('js', new Date());
gtag('config', 'UA-XXXXXX-1', {
"anonymize_ip": true,
"allow_display_features": false,
"link_attribution": false
});
I then checked the console again for error warnings and got one, this was the use of the function appendChild.
After reading some OWASP documentation, I understand that using this can be potentially dangerous, and some good examples of how to make it safer are given, such as: how to use the setAttribute method. e.g.:
SAFE and FUNCTIONALLY CORRECT example
var x = document.createElement("input");
x.setAttribute("name", "company_name");
x.setAttribute("value", '<%=Encoder.encodeForJS(companyName)%>');
var form1 = document.forms[0];
form1.appendChild(x);
But it does not say how to do this when you are e.g not using this method.
How would I e.g. set the src value safely?
Ultimately, I want to make my IIFE safe from XXS.
The encoding is needed in the example because of the mixing of server-side templating with client-side code. The quotes need to be escaped to prevent the string literal from being closed, and more code added after it.
So if companyName were '); alert(1);//, then:
x.setAttribute("value", '<%=companyName%>');
would become:
x.setAttribute("value", ''); alert(1);//');
Your URL looks like a fixed constant, so there is no possibility of something like this, so your code should be fine as it is.
I need to append timestamp to the javaScript file in tag url to avoid caching.
This function found here in stackoverflow seems to do it:
<script type="text/javascript">
(function(){
var randomh=Math.random();
var e = document.getElementsByTagName("script")[0];
var d = document.createElement("script");
d.src = "TESTINGX.JS?x="+randomh+"";
d.type = "text/javascript";
d.async = true;
d.defer = true;
e.parentNode.insertBefore(d,e);
})();
But the problem is that inside the file testingx.js I've placed the following code:
var hello = "Hello World!";
And for some reason the variable doesn't get the global scope (or maybe the problem is other).
My html is the following:
<!DOCTYPE html>
<HTML><HEAD>
<script type="text/javascript">
(function(){
var randomh=Math.random();
var e = document.getElementsByTagName("script")[0];
var d = document.createElement("script");
d.src = "TESTINGX.JS?x="+randomh+"";
d.type = "text/javascript";
d.async = true;
d.defer = true;
e.parentNode.insertBefore(d,e);
})();
</script>
</HEAD><BODY>
<h1>WiFi Mini Web Server</h1>
<script>
document.write(hello); //hello var is contained in the TESTINGX.JS file\n\
</script>
</BODY></HTML>
If you want the external script to block everything else until it loads but still need to load it from a dynamic URL (it sounds like you do), you should be able to document.write the script element directly instead of using the DOM methods.
<!DOCTYPE html>
<html><head>
<script>
var randomh = Math.random();
document.write('<script src="TESTINGX.JS?x=' + randomh + '"></' + 'script>');
</script>
</head><body>
<h1>WiFi Mini Web Server</h1>
<script>
document.write(hello); // hello var is contained in the TESTINGX.JS file
</script>
</body></html>
http://jsbin.com/ixagud/2/edit
You code for cache busting, also happens to ensure that the script is loaded in a "lazy", "non-blocking" manner. Which means the TESTING.js is loaded after your DOM is loaded. Which also means that the document.write script is run before the js file has loaded, and added the required var to the global scope.
There can be several solutions:
1. Remove defer and async from your loading script
Ensure variables from your script are accessed on your page, only after the script is actually loaded
Better yet, don't try to handle cache busting through clever javascript (I am assuming the html is question is served using some sort of server technology like a jsp or php script). Use the server technology to add a parameter with a random number or timestamp to the usual script tag in the html
Instead of using a script to bust cache, use a normal script tag, with a parameter that is same as the timestamp in millis, as on the day when you make the build, and embed in your microcontroller / server. Treat this like a version number of the js file. So clients can use the file cached in their browser, provided the version number is the latest.
This ant task http://code.google.com/p/ant-web-tasks/wiki/CacheBusting is a good way to implement point no 4. I have had good experience with it.
Hope this helps.
try this
<script type="text/javascript">
(function(){
var randomh=Math.random();
var e = document.getElementsByTagName("script")[0];
var d = document.createElement("script");
d.src = "TESTINGX.JS?x="+randomh+"";
d.type = "text/javascript";
d.async = true;
d.defer = true;
e.parentNode.insertBefore(d,e);
document.write(hello);
})();
</script>
Where is the JS file and is this Async the fastest way to call JS?
I guess they then have PHP calls in the .JS for updating the Ad stats??
The code:
<script type="text/javascript">
(function(){
var acc = "acc_230d269_pub";
var st = "nocss";
var or = "h";
var e = document.getElementsByTagName("script")[0];
var d = document.createElement("script");
d.src = ('https:' == document.location.protocol ?'https://' : 'http://')+"engine.influads.com/show/"+or+"/"+st+"/"+acc;
d.type = "text/javascript";
d.async = true;
d.defer = true;
e.parentNode.insertBefore(d,e);
})();
</script>
I've made your code more readable:
1 <script type="text/javascript">
2 (function () {
3 var acc = "acc_230d269_pub";
4 var st = "nocss";
5 var or = "h";
6 var e = document.getElementsByTagName("script")[0];
7 var d = document.createElement("script");
8 d.src = ('https:' == document.location.protocol ? 'https://' : 'http://') +
9 "engine.influads.com/show/" + or + "/" + st + "/" + acc;
10 d.type = "text/javascript";
11 d.async = true;
12 d.defer = true;
13 e.parentNode.insertBefore(d, e);
14 })();
15 </script>
2,14 An anonymous function wrapper is created, so that variables cannot be access from outside the function ("scope")
3 acc looks like the identifier of the advertiser
4,5 st = "nocss" and or = "h" looks like settings to adjust the appearance
7,10-12 A <script> tag is created. async = Loading the script will not block the execution of the document. defer=true prevents the script from not being executed (can be omitted)
6,13 The newly created script tag is inserted before (13) the first script tag in the document (6,13)
8,9 The URL is constructed:If the current page is transmitted over a secure connection, the injected script will also be transferred over the HTTPS protocol.
The extension of the requested file is omitted. This file could be served using the application/javascript MIME type by server configuration.
It inserts the script tag with a dynamically constructed file name and puts it in the document before the first script tag. The advantage of this approach is that it will run only when the document is loaded, so it will not block the document loading. This way, the user will experience no (or less) delay. It's a good practise to do this for analytical tools and such, because they don't add functionality for the user and you just want to track their actions. It doesn't matter if you miss one or two of those measurements.
There are a couple of ways to include js code into html, one is put the code directly into the tag, just like what you wondered about the code you posted, the other method is to use the following syntax:
<script type="text/javascript" src="path/to/external_file.js"></script>
As a side note, the code you posted uses a technique that prevents js name spacing conflicts by putting the code in the
(function() ...)(); block, which I find to be a very good practice.
Regarding the question about using async in tag, you might want to take a look at this:
http://davidwalsh.name/html5-async
Most of that code is for loading the JavaScript code asynchronously, in a way that works in different browsers. An explanation of how it works is here: http://friendlybit.com/js/lazy-loading-asyncronous-javascript/
Loading asynchronously means that the browser doesn't wait for it to finish. So the ad won't load faster, but the rest of the page will.
If you piece together the string, you'll find that the JavaScript file they're loading is: http://engine.influads.com/show/h/nocss/acc_230d269_pub
The benefits I see are:
Asynchronous loading that would help in faster rendering of the UI
The selective http or https used for the location of the js source following the protocol that current page is loaded with
I am wondering why the js source would not end with a .js extension though
I have code that looks like this:
<div>
<script type="text/javascript">
(function() {
var s = document.createElement('script');
s.type = 'text/javascript';
s.src = ('https:' == document.location.protocol ? 'https://' : 'http://') + 'localhost/chat/logo.js');
var t = document.getElementById('craftysyntax');
t.appendChild(s, t);
})();
</script>
<div>
I want the file that was loaded by async' to be able to do something like this (note: this is not working js code, but it is what I want to do)
parent.appendChild(abc);
which will add the child element abc in the parent div.
So, is there any way the script can reference its parent container?
Edit:
Why do I want to do this?
My website (an online store) uses a chat program with a link in the navigation bar that changes based on whether there is an operator logged in to the chat program. I am trying to convert the program to use an asynchronous loader, but the external js can not use document.write if it has been loaded asynchronously. I am trying to convert all of the document.write calls in the script to use the dom instead.
When your code is immediately executed like that, the last script element will be the current one (because of synchronous downloading and executing).
So you can get a reference to all script elements, and then get the last one using the length property minus one.
Then you can access its parent node with parentNode property.
<script type="text/javascript">
var allScripts = document.getElementsByTagName('script'),
thisScriptParent = allScripts[scripts.length - 1].parentNode;
</script>
jsFiddle.
Also, there is no need to use a ternary to check for https. Just use protocol-less (//localhost/chat/logo.js) and it will resolve to the parent site's protocol.
I don't think you can reliably get the script tag that the code is running from.
I'm not sure why your logo.js script doesn't just directly get the element by its id ("craftysyntax"), but if you are looking for a more general solution (where you could, say, add multiple such elements/scripts to the document in rapid succession, without having them mess each other up), you could do something like this:
<div>
<script type="text/javascript">
(function() {
var s = document.createElement('script');
s.type = 'text/javascript';
s.src = ('https:' == document.location.protocol ? 'https://' : 'http://') + 'localhost/chat/logo.js');
var t = document.getElementById('craftysyntax');
if (window.global_chatElems == null)
global_chatElems = [];
global_chatElems.push(t);
t.appendChild(s);
})();
</script>
<div>
then in your logo.js script:
if (window.globalChatElems) {
for (var i=0; i<globalChatElems.length; i++)
processChatElem(globalChatElems[i]);
window.globalChatElems = null;
}
Of course, if you are only allowing one of these elements per page, everything is much simpler....no need to make the global be an array, it can just be a single variable.
I ran into an old friend from school and we discussed this and figured out this solution.
I will have the inline script create its own div named based on the current time (or something like that).
Then I can pass the id of the div to the other script in the query string.
(I already have the getQueryString function)
Inline
<script type="text/javascript">
var randval = "helpbox" + Math.floor(Math.random()*10000);
document.write('<div id="' + randval + '"></div>');
(function() {
var csc = document.createElement('script');
csc.type = 'text/javascript';
csc.src = 'a1.js?divid=' + randval;
var s = document.getElementById('help');
s.appendChild(csc, s);
})();
External
var my_div = document.getElementById(getQuerystring('divid'));
I want to include jquery.js in myjs.js file. I wrote the code below for this.
var theNewScript=document.createElement("script");
theNewScript.type="text/javascript";
theNewScript.src="http://example.com/jquery.js";
document.getElementsByTagName("head")[0].appendChild(theNewScript);
$.get(myfile.php);
There shows an error on the 5th line that is '$ not defined'. I want to include jquery.js and then want to call $.get() function in myjs.js file. How can I do this?
Please help me
Appending a script tag inside the document head programmatically does not necessarily mean that the script will be available immediately. You should wait for the browser to download that file, parse and execute it. Some browsers fire an onload event for scripts in which you can hookup your logic. But this is not a cross-browser solution. I would rather "poll" for a specific symbol to become available, like this:
var theNewScript = document.createElement("script");
theNewScript.type = "text/javascript";
theNewScript.src = "http://example.com/jquery.js";
document.getElementsByTagName("head")[0].appendChild(theNewScript);
// jQuery MAY OR MAY NOT be loaded at this stage
var waitForLoad = function () {
if (typeof jQuery != "undefined") {
$.get("myfile.php");
} else {
window.setTimeout(waitForLoad, 1000);
}
};
window.setTimeout(waitForLoad, 1000);
The problem is that the script doesn't load instantly, it takes some time for the script file to download into your page and execute (in case of jQuery to define $).
I would recommend you to use HeadJS. then you can do:
head.js("/path/to/jQuery.js", function() {
$.get('myfile.php');
});
Simple answer, Dont. The jQuery file
is very touchy to intruders so dont
try. Joining other files into jQuery
file will often cause errors in the JS
console, PLUS jQuery isn't initialized
until the file is loaded into main
document.
Sorry, scratch that. Didnt quite know what you were doing.
Try this:
var s = document.createElement('script');
s.type = 'text/javascript';
s.async = true;
s.src = 'http://domain.com/jquery.js';
(document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(s);
I used this code before, and it worked:
var t=document;
var o=t.createElement('script');
o=t.standardCreateElement('script');
o.setAttribute('type','text/javascript');
o.setAttribute('src','http://www.example.com/js/jquery-1.3.2.js');
t.lastChild.firstChild.appendChild(o);