/** @namespace */ (function(module, ns) { var Native; if (typeof(window) === 'undefined') { Native = require('xmlhttprequest').XMLHttpRequest; } else { Native = window.XMLHttpRequest; } /** * Creates a XHR wrapper. * Depending on the platform this is loaded * from the correct wrapper type will be used. * * Options are derived from properties on the prototype. * See each property for its default value. * * @class * @name Caldav.Xhr * @param {Object} options options for xhr. * @param {String} [options.method="GET"] any HTTP verb like 'GET' or 'POST'. * @param {Boolean} [options.async] false will indicate * a synchronous request. * @param {Object} [options.headers] full of http headers. * @param {Object} [options.data] post data. */ function Xhr(options) { var key; if (typeof(options) === 'undefined') { options = {}; } for (key in options) { if (Object.hasOwnProperty.call(options, key)) { this[key] = options[key]; } } } Xhr.prototype = { globalXhrOptions: null, xhrClass: Native, method: 'GET', async: true, waiting: false, user: null, password: null, url: null, headers: {}, data: null, _seralize: function _seralize() { return this.data; }, /** * @param {String} user basic auth user. * @param {String} password basic auth pass. * @return {String} basic auth token. */ _credentials: function(user, pass) { // this code should never run in nodejs. return 'Basic ' + window.btoa( user + ':' + pass ); }, /** * Sends request to server. * * @param {Function} callback success/failure handler. */ send: function send(callback) { var header, xhr; if (typeof(callback) === 'undefined') { callback = this.callback; } if (this.globalXhrOptions) { xhr = new this.xhrClass(this.globalXhrOptions); } else { xhr = new this.xhrClass(); } // This hack is in place due to some platform // bug in gecko when using mozSystem xhr // the credentials only seem to work as expected // when constructing them manually. if (!this.globalXhrOptions || !this.globalXhrOptions.mozSystem) { xhr.open(this.method, this.url, this.async, this.user, this.password); } else { xhr.open(this.method, this.url, this.async); xhr.setRequestHeader('Authorization', this._credentials( this.user, this.password )); } var useMozChunkedText = false; if (this.globalXhrOptions && this.globalXhrOptions.useMozChunkedText) { useMozChunkedText = true; xhr.responseType = 'moz-chunked-text'; } for (header in this.headers) { if (Object.hasOwnProperty.call(this.headers, header)) { xhr.setRequestHeader(header, this.headers[header]); } } var hasProgressEvents = false; // check for progress event support. if ('onprogress' in xhr) { hasProgressEvents = true; var last = 0; if (useMozChunkedText) { xhr.onprogress = function onChunkedProgress(event) { this.ondata(xhr.responseText); }.bind(this); } else { xhr.onprogress = function onProgress(event) { var chunk = xhr.responseText.substr(last, event.loaded); last = event.loaded; if (this.ondata) { this.ondata(chunk); } }.bind(this); } } xhr.onreadystatechange = function onReadyStateChange() { var data; if (xhr.readyState === 4) { data = xhr.responseText; // emulate progress events for node... // this really lame we should probably just // use a real http request for node but this // will let us do some testing via node for now. if (!hasProgressEvents && this.ondata) { this.ondata(data); } this.waiting = false; callback(null, xhr); } }.bind(this); this.waiting = true; xhr.send(this._seralize()); return xhr; } }; module.exports = Xhr; }.apply( this, (this.Caldav) ? [Caldav('xhr'), Caldav] : [module, require('./caldav')] ));