Last active
August 29, 2015 14:15
-
-
Save ShirtlessKirk/d99bb9994f6bd5d85d51 to your computer and use it in GitHub Desktop.
CORS (Cross-Origin Resource Sharing) library
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* @preserve CORS (Cross-Origin Resource Sharing) library (https://en.wikipedia.org/wiki/Cross-origin_resource_sharing) | |
* | |
* @author ShirtlessKirk copyright 2014 | |
* @license WTFPL (http://www.wtfpl.net/txt/copying) | |
*/ | |
/*jslint unparam: true */ | |
/*global define: false, module: false, require: false */ | |
(function (global, definition) { // non-exporting module magic dance | |
'use strict'; | |
var | |
amd = 'amd', | |
exports = 'exports'; // keeps the method names for CommonJS / AMD from being compiled to single character variable | |
if (typeof define === 'function' && define[amd]) { | |
define(function () { | |
return definition(global); | |
}); | |
} else if (typeof module === 'function' && module[exports]) { | |
module[exports] = definition(global); | |
} else { | |
definition(global); | |
} | |
}(this, function (window) { | |
'use strict'; | |
var | |
abort = 'abort', | |
arrayPrototypeSlice = Array.prototype.slice, | |
/** @type {CORS} */ | |
cors, | |
corsType, | |
customErrorPrototype, | |
document = window.document, | |
jsonpHttpRequestPrototype, | |
key, | |
onLoad = 'onload', | |
onReadyStateChange = 'onreadystatechange', | |
open = 'open', | |
readyState = 'readyState', | |
response = 'response', | |
responseText = 'responseText', | |
send = 'send', | |
status = 'status', | |
CORSPrototype, | |
CORSStr = 'CORS', | |
/** @enum {number} */ | |
HTTPCODE = { | |
'CONTINUE': 100, | |
'CREATED': 201, | |
'OK': 200, | |
'ACCEPTED': 202, | |
'SERVERERROR': 500 | |
}, | |
NULL = null, | |
/** @enum {number} */ | |
STATE = { | |
'UNSENT': 0, | |
'OPENED': 1, | |
'HEADERS_RECEIVED': 2, | |
'LOADING': 3, | |
'DONE': 4 | |
}, | |
TYPE = 'TYPE', | |
/** @enum {string} */ | |
TYPES = { | |
XMLHTTP: 'XMLHttpRequest', | |
XDOMAIN: 'XDomainRequest', | |
JSONP: 'JSONPHttpRequest' | |
}; | |
if (!!window[CORSStr]) { // sanity check | |
return; | |
} | |
/** | |
* @this {CustomError} | |
* @return {string} | |
*/ | |
function customErrorToString() { | |
var | |
message = this.name; | |
if (this.message.length !== 0) { | |
message += ': ' + this.message; | |
} | |
return message; | |
} | |
/** | |
* @constructor | |
* @param {string} name | |
* @param {string=} opt_message | |
*/ | |
function CustomError(name, opt_message) { | |
this.name = name; | |
this.message = opt_message || ''; | |
} | |
CustomError.prototype = new Error(); | |
customErrorPrototype = CustomError.prototype; | |
customErrorPrototype.toString = customErrorToString; | |
/** | |
* @this {CORS} | |
* @param {number} state | |
* @param {number=} opt_status | |
*/ | |
function changeState(state, opt_status) { | |
if (opt_status !== undefined && opt_status !== this[status]) { | |
this[status] = opt_status; | |
} | |
if (this[readyState] !== state) { | |
this[readyState] = state; | |
if (typeof this[onReadyStateChange] === 'function') { // run onreadystatechange function if defined | |
this[onReadyStateChange](); | |
} | |
} | |
} | |
/** | |
* @this {JSONPHttpRequest|XDomainRequest|XMLHttpRequest} | |
* propagates changes up to parent CORS object (if present) to trigger onreadystatechange (if defined) on that as well | |
*/ | |
function transportOnReadyStateChange() { | |
var | |
parent = this.parent, | |
state = HTTPCODE.OK; // default to OK | |
if (parent !== undefined) { | |
if (this[readyState] === STATE.DONE) { | |
parent[response] = this[response]; | |
parent[responseText] = this[responseText]; | |
} | |
try { | |
state = this[status]; // Firefox < 14 throws error on trying to read the status property of XmlHttpRequest if it is undefined | |
} catch (ignore) {} | |
changeState.call(parent, this[readyState], state); | |
} | |
} | |
/** | |
* @this {JSONPHttpRequest|XDomainRequest} | |
* @param {number} state | |
* @param {number=} opt_status | |
*/ | |
function transportReadyStateChange(state, opt_status) { | |
this[readyState] = state; | |
if (opt_status !== undefined) { | |
this[status] = opt_status; | |
} | |
if (typeof this[onReadyStateChange] === 'function') { | |
this[onReadyStateChange](); | |
} | |
} | |
/** | |
* @this {JSONPHttpRequest} | |
*/ | |
function jsonpHttpRequestOnLoad() { | |
var | |
complete = 'complete', | |
loaded = 'loaded', | |
script = this.script, | |
state; | |
state = script[readyState] || complete; | |
if ((state === loaded || state === complete) && !script[loaded]) { | |
script[loaded] = true; | |
script[onLoad] = script[onReadyStateChange] = NULL; | |
script.parentNode.removeChild(script); | |
delete this.script; | |
transportReadyStateChange.call(this, STATE.DONE, HTTPCODE.OK); | |
} | |
} | |
/** | |
* @this {JSONPHttpRequest} | |
* @param {string} method | |
* @param {string} url | |
* @param {boolean=} opt_async | |
*/ | |
function jsonpHttpRequestOpen(method, url, opt_async) { | |
var | |
async = opt_async !== undefined ? opt_async : true, | |
fnId, | |
script = document.createElement('script'), | |
that = this; | |
if (async) { | |
script.async = async; | |
} | |
script.id = TYPES.JSONP + '_' + (new Date()).getTime(); | |
script.loaded = false; | |
script[onLoad] = script[onReadyStateChange] = function () { | |
jsonpHttpRequestOnLoad.call(that); | |
}; | |
fnId = '__' + script.id; | |
window[fnId] = function (data) { // set up auto-callback function | |
that[response] = that[responseText] = data; | |
window[fnId] = undefined; // self-undefinition | |
if (typeof that.callback === 'function') { // if callback function sent, execute in global scope, passing request object | |
that.callback.call(window, that); | |
} | |
}; | |
script.src = (url.indexOf('//') === 0 ? document.location.protocol : '') + url; // prepend the protocol just in case for old browsers (see IE) | |
transportReadyStateChange.call(this, STATE.UNSENT, HTTPCODE.CONTINUE); | |
this.script = script; | |
transportReadyStateChange.call(this, STATE.OPENED, HTTPCODE.CREATED); | |
} | |
/** | |
* @this {JSONPHttpRequest} | |
* @param {Object=} opt_data | |
*/ | |
function jsonpHttpRequestSend(opt_data) { | |
var | |
callback = 'callback', | |
counter, | |
data, | |
head, | |
length, | |
param, | |
query, | |
qS, | |
queryString, | |
script; | |
if (this[readyState] !== STATE.OPENED) { | |
throw new CustomError('InvalidStateError', 'Failed to execute \'' + send + '\' on \'' + TYPES.JSONP + '\': the object\'s state must be OPENED.'); | |
} | |
script = this.script; | |
data = opt_data || NULL; | |
head = document.head || document.getElementsByTagName('head')[0]; | |
query = script.src.split('?'); | |
qS = queryString = []; | |
if (query.length > 1) { | |
qS = query[1].split('&'); | |
script.src = query[0]; | |
} | |
for (counter = 0, length = qS.length; counter < length; counter += 1) { | |
if (qS[counter].indexOf(callback + '=') === 0) { // was callback sent in querystring? | |
this[callback] = queryString[counter].split('=')[1]; // save in object | |
} else { | |
queryString.push(qS[counter]); | |
} | |
} | |
for (param in data) { | |
if (data.hasOwnProperty(param)) { | |
if (param === callback) { // callback sent in data, save in object (overwrites any existing one) | |
this[callback] = data[param]; | |
} else { | |
queryString.push(encodeURIComponent(param) + '=' + encodeURIComponent(data[param])); | |
} | |
} | |
} | |
queryString.push(callback + '=__' + script.id); // add auto-callback reference | |
script.src += '?' + queryString.join('&'); | |
head.appendChild(script); | |
transportReadyStateChange.call(this, STATE.LOADING, HTTPCODE.ACCEPTED); | |
} | |
/** | |
* @this {XDomainRequest} | |
*/ | |
function xDomainRequestOnError() { | |
transportReadyStateChange.call(this, STATE.DONE, HTTPCODE.SERVERERROR); | |
} | |
/** | |
* @this {XDomainRequest} | |
*/ | |
function xDomainRequestOnLoad() { | |
this[response] = this[responseText]; | |
transportReadyStateChange.call(this, STATE.DONE, HTTPCODE.OK); // OK | |
} | |
/** | |
* @this {CORS} | |
*/ | |
function xDomainRequestOpen() { | |
var | |
args = arrayPrototypeSlice.call(arguments), | |
transport = this.transport; | |
this.method = args[0].toUpperCase(); | |
transport.onerror = xDomainRequestOnError; | |
transport[onLoad] = xDomainRequestOnLoad; | |
transportReadyStateChange.call(transport, STATE.UNSENT, HTTPCODE.CONTINUE); | |
transport[open].apply(transport, args); | |
transportReadyStateChange.call(transport, STATE.OPENED, HTTPCODE.CREATED); | |
} | |
/** | |
* @this {CORS} | |
*/ | |
function xDomainRequestSend() { | |
var | |
transport = this.transport; | |
transport[send].apply(transport, arguments); | |
transportReadyStateChange.call(transport, STATE.LOADING, HTTPCODE.ACCEPTED); | |
} | |
/** | |
* @this {CORS} | |
*/ | |
function xmlHttpRequestOpen() { | |
var | |
args = arrayPrototypeSlice.call(arguments), | |
transport = this.transport; | |
this.method = args[0].toUpperCase(); | |
transport[open].apply(transport, args); // automatically triggers onreadystatechange, no need to call here | |
} | |
/** | |
* @this {CORS} | |
*/ | |
function xmlHttpRequestSend() { | |
var | |
setRequestHeader = 'setRequestHeader', | |
transport = this.transport; | |
transport[setRequestHeader]('Content-Type', (this.method === 'POST' ? 'multipart/form-data' : 'application/x-www-form-urlencoded') + '; charset=UTF-8'); | |
transport[setRequestHeader]('X-Requested-With', TYPES.XMLHTTP); | |
transport[send].apply(transport, arguments); // automatically triggers onreadystatechange, no need to call here | |
} | |
/** | |
* @this {CORS} | |
*/ | |
function corsAbort() { | |
try { | |
this.transport[abort](); | |
} catch (ignore) {} | |
} | |
/** | |
* @this {CORS} | |
*/ | |
function corsJSONPOpen() { | |
var | |
args = arrayPrototypeSlice.call(arguments), | |
transport = this.transport; | |
args[0] = 'get'; | |
this.method = args[0].toUpperCase(); | |
transport[onLoad] = jsonpHttpRequestOnLoad; | |
transportReadyStateChange.call(transport, STATE.UNSENT, HTTPCODE.CONTINUE); | |
transport[open].apply(transport, args); | |
transportReadyStateChange.call(transport, STATE.OPENED, HTTPCODE.CREATED); | |
} | |
/** | |
* @this {CORS} | |
*/ | |
function corsJSONPSend() { | |
var | |
transport = this.transport; | |
transport[send].apply(transport, arguments); | |
transportReadyStateChange.call(transport, STATE.LOADING, HTTPCODE.ACCEPTED); | |
} | |
/** | |
* @this {CORS} | |
* @param {string} type | |
*/ | |
function corsInit(type) { | |
var | |
transport; | |
this[readyState] = this[response] = this[status] = NULL; | |
this[responseText] = ''; | |
this.transport = new window[type](); | |
transport = this.transport; | |
transport[onReadyStateChange] = transportOnReadyStateChange; | |
transport.parent = this; | |
} | |
/** | |
* @constructor | |
* @param {boolean=} forceJSONP | |
*/ | |
function CORS(forceJSONP) { | |
var | |
type = forceJSONP ? TYPES.JSONP : corsType; | |
corsInit.call(this, type); | |
if (forceJSONP) { // override the prototype methods for this instance | |
this[TYPE] = TYPES.JSONP; | |
this[open] = corsJSONPOpen; | |
this[send] = corsJSONPSend; | |
} | |
} | |
/** | |
* @constructor | |
*/ | |
function JSONPHttpRequest() { | |
this[readyState] = this[status] = NULL; | |
this[responseText] = ''; | |
} | |
if (window.XMLHttpRequest !== undefined && (new window.XMLHttpRequest()).withCredentials !== undefined) { // XMLHttp v2 | |
corsType = TYPES.XMLHTTP; | |
} else if (window.XDomainRequest !== undefined) { // 7 < IE < 10 | |
corsType = TYPES.XDOMAIN; | |
} else { // JSONP call fallback | |
corsType = TYPES.JSONP; | |
} | |
jsonpHttpRequestPrototype = JSONPHttpRequest.prototype; | |
jsonpHttpRequestPrototype[open] = jsonpHttpRequestOpen; | |
jsonpHttpRequestPrototype[send] = jsonpHttpRequestSend; | |
CORSPrototype = CORS.prototype; | |
CORSPrototype[TYPE] = corsType; | |
CORSPrototype[abort] = corsAbort; | |
switch (corsType) { | |
case TYPES.JSONP: | |
CORSPrototype[open] = corsJSONPOpen; | |
CORSPrototype[send] = corsJSONPSend; | |
break; | |
case TYPES.XDOMAIN: | |
CORSPrototype[open] = xDomainRequestOpen; | |
CORSPrototype[send] = xDomainRequestSend; | |
break; | |
case TYPES.XMLHTTP: | |
CORSPrototype[open] = xmlHttpRequestOpen; | |
CORSPrototype[send] = xmlHttpRequestSend; | |
break; | |
} | |
for (key in STATE) { | |
if (STATE.hasOwnProperty(key)) { | |
CORSPrototype[key] = jsonpHttpRequestPrototype[key] = STATE[key]; | |
} | |
} | |
window[CORSStr] = CORS; | |
window[TYPES.JSONP] = JSONPHttpRequest; | |
if (!window.JSON) { // shim JSON if we don't have it intrinsically | |
cors = new CORS(true); | |
cors[open]('GET', '//cdnjs.cloudflare.com/ajax/libs/json3/3.3.2/json3.min.js', false); | |
cors[send](); | |
} | |
return CORS; | |
})); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
CORS (Cross-Origin Resource Sharing) library (https://en.wikipedia.org/wiki/Cross-origin_resource_sharing) | |
@author ShirtlessKirk copyright 2014 | |
@license WTFPL (http://www.wtfpl.net/txt/copying) | |
*/ | |
(function(c,q){"function"===typeof define&&define.amd?define(function(){return q(c)}):"function"===typeof module&&module.exports?module.exports=q(c):q(c)})(this,function(c){function q(){var a=this.name;0!==this.message.length&&(a+=": "+this.message);return a}function v(a,b){this.name=a;this.message=b||""}function B(a,b){void 0!==b&&b!==this.status&&(this.status=b);if(this.readyState!==a&&(this.readyState=a,"function"===typeof this.onreadystatechange))this.onreadystatechange()}function C(){var a=this.parent, | |
b=g.i;if(void 0!==a){this.readyState===d.DONE&&(a.response=this.response,a.responseText=this.responseText);try{b=this.status}catch(c){}B.call(a,this.readyState,b)}}function l(a,b){this.readyState=a;void 0!==b&&(this.status=b);if("function"===typeof this.onreadystatechange)this.onreadystatechange()}function x(){var a=this.e,b;b=a.readyState||"complete";"loaded"!==b&&"complete"!==b||a.loaded||(a.loaded=!0,a.onload=a.onreadystatechange=null,a.parentNode.removeChild(a),delete this.e,l.call(this,d.DONE, | |
g.i))}function D(a,b,n){a=void 0!==n?n:!0;var e;n=r.createElement("script");var h=this;a&&(n.async=a);n.id=m.b+"_"+(new Date).getTime();n.loaded=!1;n.onload=n.onreadystatechange=function(){x.call(h)};e="__"+n.id;c[e]=function(a){h.response=h.responseText=a;c[e]=void 0;"function"===typeof h.l&&h.l.call(c,h)};n.src=(0===b.indexOf("//")?r.location.protocol:"")+b;l.call(this,d.j,g.g);this.e=n;l.call(this,d.c,g.h)}function E(a){var b,c,e,h,f,k,p;if(this.readyState!==d.c)throw new v("InvalidStateError", | |
"Failed to execute 'send' on '"+m.b+"': the object's state must be OPENED.");p=this.e;a=a||null;c=r.head||r.getElementsByTagName("head")[0];b=p.src.split("?");f=k=[];1<b.length&&(f=b[1].split("&"),p.src=b[0]);b=0;for(e=f.length;b<e;b+=1)0===f[b].indexOf("callback=")?this.callback=k[b].split("=")[1]:k.push(f[b]);for(h in a)a.hasOwnProperty(h)&&("callback"===h?this.callback=a[h]:k.push(encodeURIComponent(h)+"="+encodeURIComponent(a[h])));k.push("callback=__"+p.id);p.src+="?"+k.join("&");c.appendChild(p); | |
l.call(this,d.LOADING,g.f)}function F(){l.call(this,d.DONE,g.m)}function G(){this.response=this.responseText;l.call(this,d.DONE,g.i)}function H(){var a=w.call(arguments),b=this.a;this.method=a[0].toUpperCase();b.onerror=F;b.onload=G;l.call(b,d.j,g.g);b.open.apply(b,a);l.call(b,d.c,g.h)}function I(){var a=this.a;a.send.apply(a,arguments);l.call(a,d.LOADING,g.f)}function J(){var a=w.call(arguments),b=this.a;this.method=a[0].toUpperCase();b.open.apply(b,a)}function K(){var a=this.a;a.setRequestHeader("Content-Type", | |
("POST"===this.method?"multipart/form-data":"application/x-www-form-urlencoded")+"; charset=UTF-8");a.setRequestHeader("X-Requested-With",m.d);a.send.apply(a,arguments)}function L(){try{this.a.abort()}catch(a){}}function y(){var a=w.call(arguments),b=this.a;a[0]="get";this.method=a[0].toUpperCase();b.onload=x;l.call(b,d.j,g.g);b.open.apply(b,a);l.call(b,d.c,g.h)}function z(){var a=this.a;a.send.apply(a,arguments);l.call(a,d.LOADING,g.f)}function t(a){var b=a?m.b:u;this.readyState=this.response=this.status= | |
null;this.responseText="";b=this.a=new c[b];b.onreadystatechange=C;b.parent=this;a&&(this.TYPE=m.b,this.open=y,this.send=z)}function A(){this.readyState=this.status=null;this.responseText=""}var w=Array.prototype.slice,f,u,k,r=c.document,e,g={CONTINUE:100,CREATED:201,OK:200,ACCEPTED:202,SERVERERROR:500},d={UNSENT:0,OPENED:1,HEADERS_RECEIVED:2,LOADING:3,DONE:4},m={d:"XMLHttpRequest",k:"XDomainRequest",b:"JSONPHttpRequest"};if(!c.CORS){v.prototype=Error();k=v.prototype;k.toString=q;u=void 0!==c.XMLHttpRequest&& | |
void 0!==(new c.XMLHttpRequest).withCredentials?m.d:void 0!==c.XDomainRequest?m.k:m.b;k=A.prototype;k.open=D;k.send=E;e=t.prototype;e.TYPE=u;e.abort=L;switch(u){case m.b:e.open=y;e.send=z;break;case m.k:e.open=H;e.send=I;break;case m.d:e.open=J,e.send=K}for(f in d)d.hasOwnProperty(f)&&(e[f]=k[f]=d[f]);c.CORS=t;c[m.b]=A;c.JSON||(f=new t(!0),f.open("GET","//cdnjs.cloudflare.com/ajax/libs/json3/3.3.2/json3.min.js",!1),f.send());return t}}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
CORS.js
A small (1.85KB minified and gzipped) cross-browser library to support Cross Origin Resource Sharing requests. The script exposes two global constructors;
window.CORS
andwindow.JSONPHttpRequest
.window.CORS
Acts as a wrapper around the transport used to communicate across origins. The transport is automatically chosen from what the browser supports, either
XmlHttpRequest
v2 (newer browsers) orXDomainRequest
(IE 8 - 9) orJSONPHttpRequest
(all others).Parameters
JSONPHttpRequest
object as the transport even if the browser can useXmlHttpRequest
v2 orXDomainRequest
(useful for script loading).var cors = new CORS(true);
window.JSONPHttpRequest
This is used as a fallback for browsers that support neither
XmlHttpRequest
v2 orXDomainRequest
and supports the same methods.Usage
Invoke as if using
XmlHttpRequest
. That's it.The same methods and properties as
XmlHttpRequest
are available and use the same parameters.readyState
andstatus
are updated as necessary on the object andonreadystatechange
is settable and is invoked appropriately.Example
Browser compatibility
(earlier versions of listed browsers will probably work as well)
File sizes
As reported by Closure Compiler:
Notes and interesting features
In the spirit of dogfooding, if a browser doesn't support
JSON
(I'm looking at you, old IE) the library uses itself to load a shim from cloudflare.com's CDN via aJSONPHttpRequest
object. Incidentally, this also means that any script can be loaded usingJSONPHttpRequest
.