Return-Path: X-Original-To: apmail-couchdb-commits-archive@www.apache.org Delivered-To: apmail-couchdb-commits-archive@www.apache.org Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by minotaur.apache.org (Postfix) with SMTP id B1FD0EA3E for ; Mon, 11 Feb 2013 13:51:22 +0000 (UTC) Received: (qmail 65926 invoked by uid 500); 11 Feb 2013 13:51:21 -0000 Delivered-To: apmail-couchdb-commits-archive@couchdb.apache.org Received: (qmail 65134 invoked by uid 500); 11 Feb 2013 13:51:20 -0000 Mailing-List: contact commits-help@couchdb.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@couchdb.apache.org Delivered-To: mailing list commits@couchdb.apache.org Received: (qmail 63116 invoked by uid 99); 11 Feb 2013 13:51:17 -0000 Received: from tyr.zones.apache.org (HELO tyr.zones.apache.org) (140.211.11.114) by apache.org (qpsmtpd/0.29) with ESMTP; Mon, 11 Feb 2013 13:51:17 +0000 Received: by tyr.zones.apache.org (Postfix, from userid 65534) id BF6723C859; Mon, 11 Feb 2013 13:51:16 +0000 (UTC) Content-Type: text/plain; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit From: jhs@apache.org To: commits@couchdb.apache.org X-Mailer: ASF-Git Admin Mailer Subject: [4/19] git commit: Import browser-request v0.2.1 Message-Id: <20130211135116.BF6723C859@tyr.zones.apache.org> Date: Mon, 11 Feb 2013 13:51:16 +0000 (UTC) Import browser-request v0.2.1 Project: http://git-wip-us.apache.org/repos/asf/couchdb/repo Commit: http://git-wip-us.apache.org/repos/asf/couchdb/commit/5ad24876 Tree: http://git-wip-us.apache.org/repos/asf/couchdb/tree/5ad24876 Diff: http://git-wip-us.apache.org/repos/asf/couchdb/diff/5ad24876 Branch: refs/heads/nodejs_couchdb Commit: 5ad2487692932dd67fd3e0033225cddfd5d2fa71 Parents: 63c59c7 Author: Jason Smith (work) Authored: Thu Feb 7 07:12:47 2013 +0000 Committer: Jason Smith (work) Committed: Thu Feb 7 07:33:46 2013 +0000 ---------------------------------------------------------------------- share/www/script/request-min.js | 32 ++ share/www/script/request.js | 943 ++++++++++++++++++++++++++++++++++ 2 files changed, 975 insertions(+), 0 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/couchdb/blob/5ad24876/share/www/script/request-min.js ---------------------------------------------------------------------- diff --git a/share/www/script/request-min.js b/share/www/script/request-min.js new file mode 100644 index 0000000..b18c2aa --- /dev/null +++ b/share/www/script/request-min.js @@ -0,0 +1,32 @@ +/** +* XMLHttpRequest.js Copyright (C) 2011 Sergey Ilinsky (http://www.ilinsky.com) +* +* This work is free software; you can redistribute it and/or modify +* it under the terms of the GNU Lesser General Public License as published by +* the Free Software Foundation; either version 2.1 of the License, or +* (at your option) any later version. +* +* This work is distributed in the hope that it will be useful, +* but without any warranty; without even the implied warranty of +* merchantability or fitness for a particular purpose. See the +* GNU Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License +* along with this library; if not, write to the Free Software Foundation, Inc., +* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +// Browser-Request +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy of +// the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. +(function(){function e(){this._object=a&&!d?new a:new window.ActiveXObject("Microsoft.XMLHTTP"),this._listeners=[]}function f(){return new e}function g(a){a._object.send(a._data);if(b&&!a._async){a.readyState=f.OPENED,j(a);while(a.readyState4?this._object.open(a,d,e,g,i):arguments.length>3?this._object.open(a,d,e,g):this._object.open(a,d,e),this.readyState=f.OPENED,h(this),this._object.onreadystatechange=function(){if(b&&!e)return;m.readyState=m._object.readyState,j(m);if(m._aborted){m.readyState=f.UNSENT;return}m.readyState==f.DONE&&(delete m._data,k(m),c&&e&&window.detachEvent("onunload",o),n!=m.readyState&&h(m),n=m.readyState)}},f.prototype.send=function(a){f.onsend&&f.onsend.apply(this,arguments),arguments.length||(a=null),a&&a.nodeType&&(a=window.XMLSerializer?(new window.XMLSerializer).serializeToString(a):a.xml,this._headers["Content-Type"]||this._object.setRequestHeader("Content-Type","application/xml")),this._data=a,g(this)},f.prototype.abort=function(){f.onabort&&f.onabort.apply(this,arguments),this.readyState>f.UNSENT&&(this._aborted=!0),this._object.abort(),k(this),this.readyState=f. UNSENT,delete this._data},f.prototype.getAllResponseHeaders=function(){return this._object.getAllResponseHeaders()},f.prototype.getResponseHeader=function(a){return this._object.getResponseHeader(a)},f.prototype.setRequestHeader=function(a,b){return this._headers||(this._headers={}),this._headers[a]=b,this._object.setRequestHeader(a,b)},f.prototype.addEventListener=function(a,b,c){for(var d=0,e;e=this._listeners[d];d++)if(e[0]==a&&e[1]==b&&e[2]==c)return;this._listeners.push([a,b,c])},f.prototype.removeEventListener=function(a,b,c){for(var d=0,e;e=this._listeners[d];d++)if(e[0]==a&&e[1]==b&&e[2]==c)break;e&&this._listeners.splice(d,1)},f.prototype.dispatchEvent=function(a){var b={type:a.type,target:this,currentTarget:this,eventPhase:2,bubbles:a.bubbles,cancelable:a.cancelable,timeStamp:a.timeStamp,stopPropagation:function(){},preventDefault:function(){},initEvent:function(){}};b.type=="readystatechange"&&this.onreadystatechange&&(this.onreadystatechange.handleEvent||this.onreadystat echange).apply(this,[b]);for(var c=0,d;d=this._listeners[c];c++)d[0]==b.type&&!d[2]&&(d[1].handleEvent||d[1]).apply(this,[b])},f.prototype.toString=function(){return"[object XMLHttpRequest]"},f.toString=function(){return"[XMLHttpRequest]"},window.Function.prototype.apply||(window.Function.prototype.apply=function(a,b){b||(b=[]),a.__func=this,a.__func(b[0],b[1],b[2],b[3],b[4]),delete a.__func}),window.XMLHttpRequest=f})(),!function(){function a(a){if(a==="./xmlhttprequest")return window&&{XMLHttpRequest:window.XMLHttpRequest};throw new Error("require() unsupported in the browser build. Use RequireJS or Ender.")}function d(){function f(a,b){if(typeof b!="function")throw new Error("Bad callback given: "+b);if(!a)throw new Error("No options given");var c=a.onResponse;typeof a=="string"?a={uri:a}:a=JSON.parse(JSON.stringify(a)),a.onResponse=c,a.url&&(a.uri=a.url,delete a.url);if(!a.uri&&a.uri!=="")throw new Error("options.uri is a required argument");if(typeof a.uri!="string")throw new E rror("options.uri must be a string");var d=["proxy","_redirectsFollowed","maxRedirects","followRedirect"];for(var e=0;e>18&63,g=j>>12&63,h=j>>6&63,i=j&63,n[l++]=b.charAt(f)+b.charAt(g)+b.charAt(h)+b.charAt(i);while(k299)&&d.error){a=new Error("CouchDB error: "+(d.error.reason||d.error.error));for(var e in d)a[e]=d[e];return b(a,c,d)}return b(a,c,d)}typeof a=="string"&&(a={uri:a}),a.json=!0,a.body&&(a.json=a.body),delete a.body,b=b||j;var c=f(a,d);return c}}var b={},c={exports:b};typeof window=="undefined"&&(typeof console!="undefined"&&console&&console.error&&console.error('Cannot find global "window" object. Is this a browser?'),window={}),d(),window.request=c.exports}(); http://git-wip-us.apache.org/repos/asf/couchdb/blob/5ad24876/share/www/script/request.js ---------------------------------------------------------------------- diff --git a/share/www/script/request.js b/share/www/script/request.js new file mode 100644 index 0000000..2ba6585 --- /dev/null +++ b/share/www/script/request.js @@ -0,0 +1,943 @@ +/** +* XMLHttpRequest.js Copyright (C) 2011 Sergey Ilinsky (http://www.ilinsky.com) +* +* This work is free software; you can redistribute it and/or modify +* it under the terms of the GNU Lesser General Public License as published by +* the Free Software Foundation; either version 2.1 of the License, or +* (at your option) any later version. +* +* This work is distributed in the hope that it will be useful, +* but without any warranty; without even the implied warranty of +* merchantability or fitness for a particular purpose. See the +* GNU Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License +* along with this library; if not, write to the Free Software Foundation, Inc., +* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +(function () { + + // Save reference to earlier defined object implementation (if any) + var oXMLHttpRequest = window.XMLHttpRequest; + + // Define on browser type + var bGecko = !!window.controllers; + var bIE = !!window.document.namespaces; + var bIE7 = bIE && window.navigator.userAgent.match(/MSIE 7.0/); + + // Enables "XMLHttpRequest()" call next to "new XMLHttpRequest()" + function fXMLHttpRequest() { + this._object = oXMLHttpRequest && !bIE7 ? new oXMLHttpRequest : new window.ActiveXObject("Microsoft.XMLHTTP"); + this._listeners = []; + } + + // Constructor + function cXMLHttpRequest() { + return new fXMLHttpRequest; + } + cXMLHttpRequest.prototype = fXMLHttpRequest.prototype; + + // BUGFIX: Firefox with Firebug installed would break pages if not executed + if (bGecko && oXMLHttpRequest.wrapped) { + cXMLHttpRequest.wrapped = oXMLHttpRequest.wrapped; + } + + // Constants + cXMLHttpRequest.UNSENT = 0; + cXMLHttpRequest.OPENED = 1; + cXMLHttpRequest.HEADERS_RECEIVED = 2; + cXMLHttpRequest.LOADING = 3; + cXMLHttpRequest.DONE = 4; + + // Interface level constants + cXMLHttpRequest.prototype.UNSENT = cXMLHttpRequest.UNSENT; + cXMLHttpRequest.prototype.OPENED = cXMLHttpRequest.OPENED; + cXMLHttpRequest.prototype.HEADERS_RECEIVED = cXMLHttpRequest.HEADERS_RECEIVED; + cXMLHttpRequest.prototype.LOADING = cXMLHttpRequest.LOADING; + cXMLHttpRequest.prototype.DONE = cXMLHttpRequest.DONE; + + // Public Properties + cXMLHttpRequest.prototype.readyState = cXMLHttpRequest.UNSENT; + cXMLHttpRequest.prototype.responseText = ''; + cXMLHttpRequest.prototype.responseXML = null; + cXMLHttpRequest.prototype.status = 0; + cXMLHttpRequest.prototype.statusText = ''; + + // Priority proposal + cXMLHttpRequest.prototype.priority = "NORMAL"; + + // Instance-level Events Handlers + cXMLHttpRequest.prototype.onreadystatechange = null; + + // Class-level Events Handlers + cXMLHttpRequest.onreadystatechange = null; + cXMLHttpRequest.onopen = null; + cXMLHttpRequest.onsend = null; + cXMLHttpRequest.onabort = null; + + // Public Methods + cXMLHttpRequest.prototype.open = function(sMethod, sUrl, bAsync, sUser, sPassword) { + // http://www.w3.org/TR/XMLHttpRequest/#the-open-method + var sLowerCaseMethod = sMethod.toLowerCase(); + if (sLowerCaseMethod == "connect" || sLowerCaseMethod == "trace" || sLowerCaseMethod == "track") { + // Using a generic error and an int - not too sure all browsers support correctly + // http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#securityerror, so, this is safer + // XXX should do better than that, but this is OT to XHR. + throw new Error(18); + } + + // Delete headers, required when object is reused + delete this._headers; + + // When bAsync parameter value is omitted, use true as default + if (arguments.length < 3) { + bAsync = true; + } + + // Save async parameter for fixing Gecko bug with missing readystatechange in synchronous requests + this._async = bAsync; + + // Set the onreadystatechange handler + var oRequest = this; + var nState = this.readyState; + var fOnUnload = null; + + // BUGFIX: IE - memory leak on page unload (inter-page leak) + if (bIE && bAsync) { + fOnUnload = function() { + if (nState != cXMLHttpRequest.DONE) { + fCleanTransport(oRequest); + // Safe to abort here since onreadystatechange handler removed + oRequest.abort(); + } + }; + window.attachEvent("onunload", fOnUnload); + } + + // Add method sniffer + if (cXMLHttpRequest.onopen) { + cXMLHttpRequest.onopen.apply(this, arguments); + } + + if (arguments.length > 4) { + this._object.open(sMethod, sUrl, bAsync, sUser, sPassword); + } else if (arguments.length > 3) { + this._object.open(sMethod, sUrl, bAsync, sUser); + } else { + this._object.open(sMethod, sUrl, bAsync); + } + + this.readyState = cXMLHttpRequest.OPENED; + fReadyStateChange(this); + + this._object.onreadystatechange = function() { + if (bGecko && !bAsync) { + return; + } + + // Synchronize state + oRequest.readyState = oRequest._object.readyState; + fSynchronizeValues(oRequest); + + // BUGFIX: Firefox fires unnecessary DONE when aborting + if (oRequest._aborted) { + // Reset readyState to UNSENT + oRequest.readyState = cXMLHttpRequest.UNSENT; + + // Return now + return; + } + + if (oRequest.readyState == cXMLHttpRequest.DONE) { + // Free up queue + delete oRequest._data; + + // Uncomment these lines for bAsync + /** + * if (bAsync) { + * fQueue_remove(oRequest); + * } + */ + + fCleanTransport(oRequest); + + // Uncomment this block if you need a fix for IE cache + /** + * // BUGFIX: IE - cache issue + * if (!oRequest._object.getResponseHeader("Date")) { + * // Save object to cache + * oRequest._cached = oRequest._object; + * + * // Instantiate a new transport object + * cXMLHttpRequest.call(oRequest); + * + * // Re-send request + * if (sUser) { + * if (sPassword) { + * oRequest._object.open(sMethod, sUrl, bAsync, sUser, sPassword); + * } else { + * oRequest._object.open(sMethod, sUrl, bAsync); + * } + * + * oRequest._object.setRequestHeader("If-Modified-Since", oRequest._cached.getResponseHeader("Last-Modified") || new window.Date(0)); + * // Copy headers set + * if (oRequest._headers) { + * for (var sHeader in oRequest._headers) { + * // Some frameworks prototype objects with functions + * if (typeof oRequest._headers[sHeader] == "string") { + * oRequest._object.setRequestHeader(sHeader, oRequest._headers[sHeader]); + * } + * } + * } + * oRequest._object.onreadystatechange = function() { + * // Synchronize state + * oRequest.readyState = oRequest._object.readyState; + * + * if (oRequest._aborted) { + * // + * oRequest.readyState = cXMLHttpRequest.UNSENT; + * + * // Return + * return; + * } + * + * if (oRequest.readyState == cXMLHttpRequest.DONE) { + * // Clean Object + * fCleanTransport(oRequest); + * + * // get cached request + * if (oRequest.status == 304) { + * oRequest._object = oRequest._cached; + * } + * + * // + * delete oRequest._cached; + * + * // + * fSynchronizeValues(oRequest); + * + * // + * fReadyStateChange(oRequest); + * + * // BUGFIX: IE - memory leak in interrupted + * if (bIE && bAsync) { + * window.detachEvent("onunload", fOnUnload); + * } + * + * } + * }; + * oRequest._object.send(null); + * + * // Return now - wait until re-sent request is finished + * return; + * }; + */ + + // BUGFIX: IE - memory leak in interrupted + if (bIE && bAsync) { + window.detachEvent("onunload", fOnUnload); + } + + // BUGFIX: Some browsers (Internet Explorer, Gecko) fire OPEN readystate twice + if (nState != oRequest.readyState) { + fReadyStateChange(oRequest); + } + + nState = oRequest.readyState; + } + }; + }; + + cXMLHttpRequest.prototype.send = function(vData) { + // Add method sniffer + if (cXMLHttpRequest.onsend) { + cXMLHttpRequest.onsend.apply(this, arguments); + } + + if (!arguments.length) { + vData = null; + } + + // BUGFIX: Safari - fails sending documents created/modified dynamically, so an explicit serialization required + // BUGFIX: IE - rewrites any custom mime-type to "text/xml" in case an XMLNode is sent + // BUGFIX: Gecko - fails sending Element (this is up to the implementation either to standard) + if (vData && vData.nodeType) { + vData = window.XMLSerializer ? new window.XMLSerializer().serializeToString(vData) : vData.xml; + if (!this._headers["Content-Type"]) { + this._object.setRequestHeader("Content-Type", "application/xml"); + } + } + + this._data = vData; + + /** + * // Add to queue + * if (this._async) { + * fQueue_add(this); + * } else { */ + fXMLHttpRequest_send(this); + /** + * } + */ + }; + + cXMLHttpRequest.prototype.abort = function() { + // Add method sniffer + if (cXMLHttpRequest.onabort) { + cXMLHttpRequest.onabort.apply(this, arguments); + } + + // BUGFIX: Gecko - unnecessary DONE when aborting + if (this.readyState > cXMLHttpRequest.UNSENT) { + this._aborted = true; + } + + this._object.abort(); + + // BUGFIX: IE - memory leak + fCleanTransport(this); + + this.readyState = cXMLHttpRequest.UNSENT; + + delete this._data; + + /* if (this._async) { + * fQueue_remove(this); + * } + */ + }; + + cXMLHttpRequest.prototype.getAllResponseHeaders = function() { + return this._object.getAllResponseHeaders(); + }; + + cXMLHttpRequest.prototype.getResponseHeader = function(sName) { + return this._object.getResponseHeader(sName); + }; + + cXMLHttpRequest.prototype.setRequestHeader = function(sName, sValue) { + // BUGFIX: IE - cache issue + if (!this._headers) { + this._headers = {}; + } + + this._headers[sName] = sValue; + + return this._object.setRequestHeader(sName, sValue); + }; + + // EventTarget interface implementation + cXMLHttpRequest.prototype.addEventListener = function(sName, fHandler, bUseCapture) { + for (var nIndex = 0, oListener; oListener = this._listeners[nIndex]; nIndex++) { + if (oListener[0] == sName && oListener[1] == fHandler && oListener[2] == bUseCapture) { + return; + } + } + + // Add listener + this._listeners.push([sName, fHandler, bUseCapture]); + }; + + cXMLHttpRequest.prototype.removeEventListener = function(sName, fHandler, bUseCapture) { + for (var nIndex = 0, oListener; oListener = this._listeners[nIndex]; nIndex++) { + if (oListener[0] == sName && oListener[1] == fHandler && oListener[2] == bUseCapture) { + break; + } + } + + // Remove listener + if (oListener) { + this._listeners.splice(nIndex, 1); + } + }; + + cXMLHttpRequest.prototype.dispatchEvent = function(oEvent) { + var oEventPseudo = { + 'type': oEvent.type, + 'target': this, + 'currentTarget': this, + 'eventPhase': 2, + 'bubbles': oEvent.bubbles, + 'cancelable': oEvent.cancelable, + 'timeStamp': oEvent.timeStamp, + 'stopPropagation': function() {}, // There is no flow + 'preventDefault': function() {}, // There is no default action + 'initEvent': function() {} // Original event object should be initialized + }; + + // Execute onreadystatechange + if (oEventPseudo.type == "readystatechange" && this.onreadystatechange) { + (this.onreadystatechange.handleEvent || this.onreadystatechange).apply(this, [oEventPseudo]); + } + + + // Execute listeners + for (var nIndex = 0, oListener; oListener = this._listeners[nIndex]; nIndex++) { + if (oListener[0] == oEventPseudo.type && !oListener[2]) { + (oListener[1].handleEvent || oListener[1]).apply(this, [oEventPseudo]); + } + } + + }; + + // + cXMLHttpRequest.prototype.toString = function() { + return '[' + "object" + ' ' + "XMLHttpRequest" + ']'; + }; + + cXMLHttpRequest.toString = function() { + return '[' + "XMLHttpRequest" + ']'; + }; + + /** + * // Queue manager + * var oQueuePending = {"CRITICAL":[],"HIGH":[],"NORMAL":[],"LOW":[],"LOWEST":[]}, + * aQueueRunning = []; + * function fQueue_add(oRequest) { + * oQueuePending[oRequest.priority in oQueuePending ? oRequest.priority : "NORMAL"].push(oRequest); + * // + * setTimeout(fQueue_process); + * }; + * + * function fQueue_remove(oRequest) { + * for (var nIndex = 0, bFound = false; nIndex < aQueueRunning.length; nIndex++) + * if (bFound) { + * aQueueRunning[nIndex - 1] = aQueueRunning[nIndex]; + * } else { + * if (aQueueRunning[nIndex] == oRequest) { + * bFound = true; + * } + * } + * + * if (bFound) { + * aQueueRunning.length--; + * } + * + * + * // + * setTimeout(fQueue_process); + * }; + * + * function fQueue_process() { + * if (aQueueRunning.length < 6) { + * for (var sPriority in oQueuePending) { + * if (oQueuePending[sPriority].length) { + * var oRequest = oQueuePending[sPriority][0]; + * oQueuePending[sPriority] = oQueuePending[sPriority].slice(1); + * // + * aQueueRunning.push(oRequest); + * // Send request + * fXMLHttpRequest_send(oRequest); + * break; + * } + * } + * } + * }; + */ + + // Helper function + function fXMLHttpRequest_send(oRequest) { + oRequest._object.send(oRequest._data); + + // BUGFIX: Gecko - missing readystatechange calls in synchronous requests + if (bGecko && !oRequest._async) { + oRequest.readyState = cXMLHttpRequest.OPENED; + + // Synchronize state + fSynchronizeValues(oRequest); + + // Simulate missing states + while (oRequest.readyState < cXMLHttpRequest.DONE) { + oRequest.readyState++; + fReadyStateChange(oRequest); + // Check if we are aborted + if (oRequest._aborted) { + return; + } + } + } + } + + function fReadyStateChange(oRequest) { + // Sniffing code + if (cXMLHttpRequest.onreadystatechange){ + cXMLHttpRequest.onreadystatechange.apply(oRequest); + } + + + // Fake event + oRequest.dispatchEvent({ + 'type': "readystatechange", + 'bubbles': false, + 'cancelable': false, + 'timeStamp': new Date + 0 + }); + } + + function fGetDocument(oRequest) { + var oDocument = oRequest.responseXML; + var sResponse = oRequest.responseText; + // Try parsing responseText + if (bIE && sResponse && oDocument && !oDocument.documentElement && oRequest.getResponseHeader("Content-Type").match(/[^\/]+\/[^\+]+\+xml/)) { + oDocument = new window.ActiveXObject("Microsoft.XMLDOM"); + oDocument.async = false; + oDocument.validateOnParse = false; + oDocument.loadXML(sResponse); + } + + // Check if there is no error in document + if (oDocument){ + if ((bIE && oDocument.parseError !== 0) || !oDocument.documentElement || (oDocument.documentElement && oDocument.documentElement.tagName == "parsererror")) { + return null; + } + } + return oDocument; + } + + function fSynchronizeValues(oRequest) { + try { oRequest.responseText = oRequest._object.responseText; } catch (e) {} + try { oRequest.responseXML = fGetDocument(oRequest._object); } catch (e) {} + try { oRequest.status = oRequest._object.status; } catch (e) {} + try { oRequest.statusText = oRequest._object.statusText; } catch (e) {} + } + + function fCleanTransport(oRequest) { + // BUGFIX: IE - memory leak (on-page leak) + oRequest._object.onreadystatechange = new window.Function; + } + + // Internet Explorer 5.0 (missing apply) + if (!window.Function.prototype.apply) { + window.Function.prototype.apply = function(oRequest, oArguments) { + if (!oArguments) { + oArguments = []; + } + oRequest.__func = this; + oRequest.__func(oArguments[0], oArguments[1], oArguments[2], oArguments[3], oArguments[4]); + delete oRequest.__func; + }; + } + + // Register new object with window + window.XMLHttpRequest = cXMLHttpRequest; + +})(); + + + +!function() { + function require(path) { + if(path === './xmlhttprequest') + return window && {'XMLHttpRequest':window.XMLHttpRequest} + throw new Error('require() unsupported in the browser build. Use RequireJS or Ender.') + } + + var exports = {} + , module = { 'exports': exports } + + if(typeof window === 'undefined') { + if(typeof console !== 'undefined' && console && console.error) + console.error('Cannot find global "window" object. Is this a browser?') + + // XXX Global variable + window = {} + } + + run_module() + window["request"] = module.exports + + function run_module() { + // Begin CommonJS file +// Browser Request +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +var xmlhttprequest = require('./xmlhttprequest') +if(!xmlhttprequest || typeof xmlhttprequest !== 'object') + throw new Error('Could not find ./xmlhttprequest') + +var XHR = xmlhttprequest.XMLHttpRequest +if(!XHR) + throw new Error('Bad xmlhttprequest.XMLHttpRequest') +if(! ('_object' in (new XHR))) + throw new Error('This is not portable XMLHttpRequest') + +module.exports = request +request.XMLHttpRequest = XHR +request.log = getLogger() + +var DEFAULT_TIMEOUT = 3 * 60 * 1000 // 3 minutes + +// +// request +// + +function request(options, callback) { + // The entry-point to the API: prep the options object and pass the real work to run_xhr. + if(typeof callback !== 'function') + throw new Error('Bad callback given: ' + callback) + + if(!options) + throw new Error('No options given') + + var options_onResponse = options.onResponse; // Save this for later. + + if(typeof options === 'string') + options = {'uri':options}; + else + options = JSON.parse(JSON.stringify(options)); // Use a duplicate for mutating. + + options.onResponse = options_onResponse // And put it back. + + if(options.url) { + options.uri = options.url; + delete options.url; + } + + if(!options.uri && options.uri !== "") + throw new Error("options.uri is a required argument"); + + if(typeof options.uri != "string") + throw new Error("options.uri must be a string"); + + var unsupported_options = ['proxy', '_redirectsFollowed', 'maxRedirects', 'followRedirect'] + for (var i = 0; i < unsupported_options.length; i++) + if(options[ unsupported_options[i] ]) + throw new Error("options." + unsupported_options[i] + " is not supported") + + options.callback = callback + options.method = options.method || 'GET'; + options.headers = options.headers || {}; + options.body = options.body || null + options.timeout = options.timeout || request.DEFAULT_TIMEOUT + + if(options.headers.host) + throw new Error("Options.headers.host is not supported"); + + if(options.json) { + options.headers.accept = options.headers.accept || 'application/json' + if(options.method !== 'GET') + options.headers['content-type'] = 'application/json' + + if(typeof options.json !== 'boolean') + options.body = JSON.stringify(options.json) + else if(typeof options.body !== 'string') + options.body = JSON.stringify(options.body) + } + + // If onResponse is boolean true, call back immediately when the response is known, + // not when the full request is complete. + options.onResponse = options.onResponse || noop + if(options.onResponse === true) { + options.onResponse = callback + options.callback = noop + } + + // XXX Browsers do not like this. + //if(options.body) + // options.headers['content-length'] = options.body.length; + + // HTTP basic authentication + if(!options.headers.authorization && options.auth) + options.headers.authorization = 'Basic ' + b64_enc(options.auth.username + ':' + options.auth.password); + + return run_xhr(options) +} + +var req_seq = 0 +function run_xhr(options) { + var xhr = new XHR + , timed_out = false + , is_cors = is_crossDomain(options.uri) + , supports_cors = ('withCredentials' in xhr._object) + + req_seq += 1 + xhr.seq_id = req_seq + xhr.id = req_seq + ': ' + options.method + ' ' + options.uri + xhr._id = xhr.id // I know I will type "_id" from habit all the time. + + if(is_cors && !supports_cors) { + var cors_err = new Error('Browser does not support cross-origin request: ' + options.uri) + cors_err.cors = 'unsupported' + return options.callback(cors_err, xhr) + } + + xhr.timeoutTimer = setTimeout(too_late, options.timeout) + function too_late() { + timed_out = true + var er = new Error('ETIMEDOUT') + er.code = 'ETIMEDOUT' + er.duration = options.timeout + + request.log.error('Timeout', { 'id':xhr._id, 'milliseconds':options.timeout }) + return options.callback(er, xhr) + } + + // Some states can be skipped over, so remember what is still incomplete. + var did = {'response':false, 'loading':false, 'end':false} + + xhr.onreadystatechange = on_state_change + xhr.open(options.method, options.uri, true) // asynchronous + if(is_cors) + xhr._object.withCredentials = !! options.withCredentials + xhr.send(options.body) + return xhr + + function on_state_change(event) { + if(timed_out) + return request.log.debug('Ignoring timed out state change', {'state':xhr.readyState, 'id':xhr.id}) + + request.log.debug('State change', {'state':xhr.readyState, 'id':xhr.id, 'timed_out':timed_out}) + + if(xhr.readyState === XHR.OPENED) { + request.log.debug('Request started', {'id':xhr.id}) + for (var key in options.headers) + xhr.setRequestHeader(key, options.headers[key]) + } + + else if(xhr.readyState === XHR.HEADERS_RECEIVED) + on_response() + + else if(xhr.readyState === XHR.LOADING) { + on_response() + on_loading() + } + + else if(xhr.readyState === XHR.DONE) { + on_response() + on_loading() + on_end() + } + } + + function on_response() { + if(did.response) + return + + did.response = true + request.log.debug('Got response', {'id':xhr.id, 'status':xhr.status}) + clearTimeout(xhr.timeoutTimer) + xhr.statusCode = xhr.status // Node request compatibility + + // Detect failed CORS requests. + if(is_cors && xhr.statusCode == 0) { + var cors_err = new Error('CORS request rejected: ' + options.uri) + cors_err.cors = 'rejected' + + // Do not process this request further. + did.loading = true + did.end = true + + return options.callback(cors_err, xhr) + } + + options.onResponse(null, xhr) + } + + function on_loading() { + if(did.loading) + return + + did.loading = true + request.log.debug('Response body loading', {'id':xhr.id}) + // TODO: Maybe simulate "data" events by watching xhr.responseText + } + + function on_end() { + if(did.end) + return + + did.end = true + request.log.debug('Request done', {'id':xhr.id}) + + xhr.body = xhr.responseText + if(options.json) { + try { xhr.body = JSON.parse(xhr.responseText) } + catch (er) { return options.callback(er, xhr) } + } + + options.callback(null, xhr, xhr.body) + } + +} // request + +request.withCredentials = false; +request.DEFAULT_TIMEOUT = DEFAULT_TIMEOUT; + +// +// HTTP method shortcuts +// + +var shortcuts = [ 'get', 'put', 'post', 'head' ]; +shortcuts.forEach(function(shortcut) { + var method = shortcut.toUpperCase(); + var func = shortcut.toLowerCase(); + + request[func] = function(opts) { + if(typeof opts === 'string') + opts = {'method':method, 'uri':opts}; + else { + opts = JSON.parse(JSON.stringify(opts)); + opts.method = method; + } + + var args = [opts].concat(Array.prototype.slice.apply(arguments, [1])); + return request.apply(this, args); + } +}) + +// +// CouchDB shortcut +// + +request.couch = function(options, callback) { + if(typeof options === 'string') + options = {'uri':options} + + // Just use the request API to do JSON. + options.json = true + if(options.body) + options.json = options.body + delete options.body + + callback = callback || noop + + var xhr = request(options, couch_handler) + return xhr + + function couch_handler(er, resp, body) { + if(er) + return callback(er, resp, body) + + if((resp.statusCode < 200 || resp.statusCode > 299) && body.error) { + // The body is a Couch JSON object indicating the error. + er = new Error('CouchDB error: ' + (body.error.reason || body.error.error)) + for (var key in body) + er[key] = body[key] + return callback(er, resp, body); + } + + return callback(er, resp, body); + } +} + +// +// Utility +// + +function noop() {} + +function getLogger() { + var logger = {} + , levels = ['trace', 'debug', 'info', 'warn', 'error'] + , level, i + + for(i = 0; i < levels.length; i++) { + level = levels[i] + + logger[level] = noop + if(typeof console !== 'undefined' && console && console[level]) + logger[level] = formatted(console, level) + } + + return logger +} + +function formatted(obj, method) { + return formatted_logger + + function formatted_logger(str, context) { + if(typeof context === 'object') + str += ' ' + JSON.stringify(context) + + return obj[method].call(obj, str) + } +} + +// Return whether a URL is a cross-domain request. +function is_crossDomain(url) { + var rurl = /^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+))?)?/ + + // jQuery #8138, IE may throw an exception when accessing + // a field from window.location if document.domain has been set + var ajaxLocation + try { ajaxLocation = location.href } + catch (e) { + // Use the href attribute of an A element since IE will modify it given document.location + ajaxLocation = document.createElement( "a" ); + ajaxLocation.href = ""; + ajaxLocation = ajaxLocation.href; + } + + var ajaxLocParts = rurl.exec(ajaxLocation.toLowerCase()) || [] + , parts = rurl.exec(url.toLowerCase() ) + + var result = !!( + parts && + ( parts[1] != ajaxLocParts[1] + || parts[2] != ajaxLocParts[2] + || (parts[3] || (parts[1] === "http:" ? 80 : 443)) != (ajaxLocParts[3] || (ajaxLocParts[1] === "http:" ? 80 : 443)) + ) + ) + + //console.debug('is_crossDomain('+url+') -> ' + result) + return result +} + +// MIT License from http://phpjs.org/functions/base64_encode:358 +function b64_enc (data) { + // Encodes string using MIME base64 algorithm + var b64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; + var o1, o2, o3, h1, h2, h3, h4, bits, i = 0, ac = 0, enc="", tmp_arr = []; + + if (!data) { + return data; + } + + // assume utf8 data + // data = this.utf8_encode(data+''); + + do { // pack three octets into four hexets + o1 = data.charCodeAt(i++); + o2 = data.charCodeAt(i++); + o3 = data.charCodeAt(i++); + + bits = o1<<16 | o2<<8 | o3; + + h1 = bits>>18 & 0x3f; + h2 = bits>>12 & 0x3f; + h3 = bits>>6 & 0x3f; + h4 = bits & 0x3f; + + // use hexets to index into b64, and append result to encoded string + tmp_arr[ac++] = b64.charAt(h1) + b64.charAt(h2) + b64.charAt(h3) + b64.charAt(h4); + } while (i < data.length); + + enc = tmp_arr.join(''); + + switch (data.length % 3) { + case 1: + enc = enc.slice(0, -2) + '=='; + break; + case 2: + enc = enc.slice(0, -1) + '='; + break; + } + + return enc; +} + + // End CommonJS file + } +}();