Load JavaScript file via AJAX without jQuery - javascript

I am dynamically loading a widget from another webservice that requires a script to be run immediately after it. The example uses document.write() to do this, but this does not work because it doesn't run until after the document has been closed, which means it overwrites the entire document. I am trying to load it via AJAX using pure JavaScript:
var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200) { // success
eval(this.responseText);
}
};
xhttp.open("GET", "https://d3gxy7nm8y4yjr.cloudfront.net/js/embed.js", true);
xhttp.setRequestHeader('Content-type', 'application/javascript');
xhttp.send();
but I get the following error:
XMLHttpRequest cannot load
https://d3gxy7nm8y4yjr.cloudfront.net/js/embed.js. No
'Access-Control-Allow-Origin' header is present on the requested
resource. Origin 'https://example.com' is therefore not allowed
access.
I was able to make it work using jQuery on my test server with this code:
$.ajax({
dataType: "script",
cache: true,
url: "https://d3gxy7nm8y4yjr.cloudfront.net/js/embed.js"
});
However, I cannot use jQuery on my production server because... well... work politics.
How is jQuery accomplishing this without an error, and how can I do it in pure JavaScript?
Solution
As stated by #shabs, $.ajax adds a script tag to the document.head and then immediately removes it. I checked this by adding a breakpoint and saw it added and removed in Chrome's inspector. It appears to remove the file as soon as the current script completes regardless of what the file is. This works well for immediately invoked scripts, but I don't know how this would work with something like a library.
For a pure JavaScript implementation I used the following code:
var widgetScript = document.createElement("script");
widgetScript.async = true;
widgetScript.src = "https://d3gxy7nm8y4yjr.cloudfront.net/js/embed.js";
document.head.append(widgetScript);
widgetScript.remove();

The resource in question doesn't support CORS.
This works through $.ajax because when you specify dataType: "script", jQuery actually appends a <script> tag to document.head! *
Is there a particular reason you're not just using something like <script type="text/javascript" src="https://d3gxy7nm8y4yjr.cloudfront.net/js/embed.js"></script>?
* (This is news to me! The documentation for $.ajax mentions that "script [...] requests are not subject to the same origin policy restrictions", and the source code confirms this.)

Related

CSRFGuard: How to inject CSRF token into URL returned by AJAX call

We are attempting to add CSRF protection to our existing java web application by using CSRFGuard. We've followed the OWASP's guide to token injection, and that has gotten us most of the way there. We're using the dynamic DOM manipulation method, and find that most URLS/forms/AJAX calls are properly formatted with the inserted CSRF token. Our issue is this:
Parts of some pages are generated dynamically by AJAX calls that return jspfs. The jspfs that are returned have links which were never subject to the CSRFGuard DOM Manipulation, and as such, don't have the CSRF token. Clicking on those links causes a CSRF violation because no token is present.
Furthermore, according to the OWASP guide for AJAX support, the dynamic script needs to be reference prior to the AJAX call so that the AJAX call can be intercepted and have the CSRF token inserted into the header. This is the same script that dynamically updates the DOM. So - to solve the issue posed in this question I would need to run the script after the AJAX call, but I also need to run it before the AJAX call to make it in the first place. Trying to run it twice causes issues.
What is the proper solution here? Does that CSRFGuard javascript file need to be modified so that dynamic token injection can be run against targeted elements? Has this issue been solved already?
I had the same problem. I modified csrfguard.js this way:
I moved out all functions from (function() {}) block and put them before the block.
I defined 2 new functions
function getTokenNameValuePair() {
var xhr = window.XMLHttpRequest ? new window.XMLHttpRequest : new window.ActiveXObject("Microsoft.XMLHTTP");
var csrfToken = {};
xhr.open("POST", "%SERVLET_PATH%", false);
xhr.setRequestHeader("FETCH-CSRF-TOKEN", "1");
xhr.send(null);
return xhr.responseText;
}
function injectTokensForNewTags() {
var token_pair = getTokenNameValuePair();
token_pair = token_pair.split(":");
var token_name = token_pair[0];
var token_value = token_pair[1];
injectTokens(token_name, token_value);
}
And your AJAX that is returning a HTML chunk with links should look like this:
$.post(loadurl, function(data) {
$(target).html(data);
injectTokensForNewTags();
});
Note: this answer requires modifying CSRFGuard.
I am using SkateJS (https://github.com/skatejs/skatejs) to watch for dynamic updates of <a>, <img>, and <form> tags and calling a routine I added to csrfguard.js to add the token to them.
This catches tags that are inserted after the initial DOM load by javascript toolkits.
I had to also modify the script to keep it from always scanning the entire DOM tree at load time to insert the tokens. This was very inefficient and was unnecessary with the above method.
Here's an example of the SkateJS config:
window.addEventListener('load',
function() {
var config = {
ready : function(element) {
if (CsrfGuard && CsrfGuard.isEnabled) {
CsrfGuard.injectTokens([element]);
} else {
skate.destroy();
}
},
type : skate.types.TAG
};
skate('a', config);
skate('img', config);
skate('form', config);
}
);
Note: this will not work on IE8. For that, I am using Microsoft DHTML behaviors.
<![if IE 8]>
<style>
a,img,form {
behavior: url("csrf_ie8.htc");
}
</style>
<![endif]>
csrf_ie8.htc:
<public:attach event="ondocumentready" onevent="CsrfGuard.injectTokens([element]);" />

detect XHR on a page using javascript

I want to develop a Chrome extension, just imagine when Facebook loads you are allowed to add extra JS on it.
But my problem is I can't modify the DOM of the later content, which means the newly loaded content that appear when the user scrolled down.
So I want to detect XHR using JavaScript.
I tried
send = XMLHttpRequest.prototype.send;
XMLHttpRequest.prototype.send = function() {
/* Wrap onreadystaechange callback */
var callback = this.onreadystatechange;
this.onreadystatechange = function() {
if (this.readyState == 4) {
/* We are in response; do something, like logging or anything you want */
alert('test');
}
callback.apply(this, arguments);
}
_send.apply(this, arguments);
}
But this is not working.. any ideas?
Besides Arun's correct remark that you should use _send for both, your approach doesn't work because of how Content Scripts work.
The code running in the content script works in an isolated environment, to prevent it from conflicting with page's own code. So it's not like you described - you're not simply adding JS to the page, you have it run isolated. As a result, your XHR replacement only affects XHR calls from your extension's content scripts and not the page.
It's possible to inject the code into the page itself. This will affect XHR's from the page, but might not work on all pages, if the Content Security Policy of the page in question disallows inline code. It seems like Facebook's CSP would allow this. Page's CSP should not be a problem according to the docs. So, this approach should work, see the question I linked.
That said, you're not specifically looking for AJAX calls, you're looking for new elements being inserted in the DOM. You can detect that without modifying the page's code, using DOM MutationObservers.
See this answer for more information.
to detect AJAX calls on a webpage you have to inject the code directly in that page and then call the .ajaxStart or .ajaxSuccess
Example:
// To Successfully Intercept AJAX calls, we had to embed the script directly in the Notifications page
var injectedCode = '(' + function() {
$('body').ajaxSuccess(function(evt, request, settings) {
if (evt.delegateTarget.baseURI == 'URL to check against if you want') {
// do your stuff
}
});
} + ')();';
// Inserting the script into the page
var script = document.createElement('script');
script.textContent = injectedCode;
(document.head || document.documentElement).appendChild(script);
script.parentNode.removeChild(script);

Get content of a custom 404 error page with ajax

I am trying to get the content of a 404 custom page via ajax (Ii need a counter value at this page using Greasemonkey).
Unfortunately jQuery's .fail method of ajax does not give a possibility to actually read the contents of the page like the data value on success.
Is there any workaround? I will also buy vanilla js.
Best rehards
You may do this in Vanilla JS :
var httpRequest = new XMLHttpRequest();
httpRequest.onreadystatechange = function() {
if (httpRequest.readyState === 4) {
console.log(httpRequest.responseText);
}
};
pmc.loading.start();
httpRequest.open('GET', url);
httpRequest.send();
Ajax's error callback can be used too :
$.ajax({
url: url,
error: function(httpRequest){
console.log(httpRequest.responseText);
}
});
This being said, I wonder, from your comments, if you're not subject to problems related to same origin policy : you can't read in javascript the content of a page issued from another domain if the site's owner didn't put the right headers.
If that's the case, you can't do anything purely client-side without the consent of the site owner. The easiest would be to add a proxy on your site to serve the page as if it was coming from your site.

How to load a script into a XUL app after initial loading

Greetings,
my xul app needs to load scripts dynamically, for this I derived a function that works in regular html/js apps :
function loadScript(url)
{
var e = document.createElement("script");
e.src = url;
e.type="text/javascript";
document.getElementsByTagName("head")[0].appendChild(e);
}
to something that ought work in XUL :
function loadScript( url)
{
var e = document.createElement("script");
//I can tell from statically loaded scripts that these 2 are set thru attributes
e.setAttribute( 'type' , "application/javascript" ); //type is as per MDC docs
e.setAttribute( 'src' , url );
//XUL apps attach scripts to the window I can tell from firebug, there is no head
document.getElementsByTagName("window")[0].appendChild(e);
}
The script tags get properly added, the attributes look fine,but it does not work at all, no code inside these loaded scripts is executed or even parsed.
Can any one give a hint as to what might be going on ?
T.
Okay,
as usual whenever I post on stack overflow, the answer will come pretty soon thru one last desperate Google search.
This works :
//Check this for how the url should look like :
//https://developer.mozilla.org/en/mozIJSSubScriptLoader
function loadScript( url)
{
var loader = Components.classes["#mozilla.org/moz/jssubscript-loader;1"].getService(Components.interfaces.mozIJSSubScriptLoader);
//The magic happens here
loader.loadSubScript( url );
}
This will only load local files, which is what I need for my app.
I am fairly disappointed by Mozilla, why not do this the same way like html, in a standard way ?
I've tried this, and I think you're right - I can't seem to get XUL to run dynamically appended script tags - perhaps it's a bug.
I'm curious as to why you would want to though - I can't think of any situation where one would need to do this - perhaps whatever you're trying could be achieved another way. Why is it they need to be dynamically loaded?
Off-topic: on the changes you made to the script.
e.setAttribute('src',url); is valid in normal webpages as well, and is actually technically more "correct" than e.src=url; anyway (although longer and not well supported in old browsers).
Types application/javascript or application/ecmascript are supposed to work in normal webpages and are more "correct" than text/javascript, but IE doesn't support them so they're not normally used.
Inside xul environment you are only allowed to use XHR+eval like the following:
function loadScript (url) {
var xhr = new XMLHttpRequest();
xhr.open("GET", url, false); // sync
xhr.send(null);
if (xhr.status && xhr.status != 200)
throw xhr.statusText;
try {
eval(xhr.responseText, window);
} catch (x) {
throw new Error("ERROR in loadScript: Can't load script '" + url+ "'\nError message is:" + x.message);
}
};

Dynamic Script Tags for JSON requests... detecting if there is a XXX error?

I do a bunch of json requests with dynamic script tags. Is it possible to detect if there's an error in the request (eg. 503 error, 404 error) and run something on detection of the error?
use ajax instead. AFAIK there is no way to detect if a script tag loads or not, and if not, why it didn't load. Using ajax you can load the json and it will tell you why it didn't load.
Using a library like jQuery this becomes very simple:
$.ajax({
type: "GET",
url: "test.js",
dataType: "script",
error: function(xhr, error, exception){
alert(xhr.status); //Will alert 404 if the script does not exist
}
});
AFAIK, there's no way to access status code of some external asset loaded from the document (such as script, style or image). Even detecting error (via, say, onerror event handler) is not that widely supported across browsers.
If whatever you're loading falls under SOP, use XHR which gives you access to response headers. Otherwise, you can try looking into recently introduced X-domain XHR.
I'm assuming you want this to work cross-domain, which is why you can't use XHR?
Try creating two script tags for each request, the first does your standard JSONP request, the second is basically an error handler.
If the first script tag executes, then clear the error handler in your callback. But if the first gets a 404, the error handler inside the second script tag will be run.
You probably also want to set a timeout, to cope with a slow JSONP response.
http://www.phpied.com/javascript-include-ready-onload/ ?
If you're using jQuery, check out jQuery-JSONP which is a jQuery plugin that does a fairly decent job of doing the <script> insertion for you as well as detecting fetch errors.
Quoting from the project page, jQuery-JSONP features:
error recovery in case of network failure or ill-formed JSON responses,
precise control over callback naming and how it is transmitted in the URL,
multiple requests with the same callback name running concurrently,
two caching mechanisms (browser-based and page based),
the possibility to manually abort the request just like any other AJAX request,
a timeout mechanism.
If you need to cross domains (and need the page to work portably), you have to use dynamic script tags.
If you have access to the remote server, you can pass back an error code from the server, and have the server page return 200.
Whether you have access or not, you can use setTimeout when you create the script tag, passing a function that will trigger an error if it expires before the jsonp handler is called. Make sure that the jsonp handler aborts if the error handler has been called.
You'll need to track each request through a global collection, but you'll gain the ability to cancel and count requests. This is similar to the way that XHR objects are managed by a library like jQuery.
If you want to detect errors, listen for an error event and compare the fileName property of the error with the file name of the script. If they match, you then handle the error. The thing is, I think that the fileName property is Firefox and Opera-only. Most browsers that have a stacktrace for errors can also simulate this behaviour.
Here's an example, as requested by Eric Bréchemier:
var getErrorScriptNode = (function () {
var getErrorSource = function (error) {
var loc, replacer = function (stack, matchedLoc) {
loc = matchedLoc;
};
if ("fileName" in error) {
loc = error.fileName;
} else if ("stacktrace" in error) { // Opera
error.stacktrace.replace(/Line \d+ of .+ script (.*)/gm, replacer);
} else if ("stack" in error) { // WebKit
error.stack.replace(/at (.*)/gm, replacer);
loc = loc.replace(/:\d+:\d+$/, "");
}
return loc;
},
anchor = document.createElement("a");
return function (error) {
anchor.href = getErrorSource(error);
var src = anchor.href,
scripts = document.getElementsByTagName("script");
anchor.removeAttribute("href");
for (var i = 0, l = scripts.length; i < l; i++) {
anchor.href = scripts.item(i).src;
if (anchor.href === src) {
anchor.removeAttribute("href");
return scripts.item(i);
}
}
};
}());

Categories

Resources