pz2.js briefly tracked elsewhere; add support for CORS and server-side sessions
authorJason Skomorowski <jason@indexdata.com>
Tue, 24 Sep 2013 12:47:42 +0000 (08:47 -0400)
committerJason Skomorowski <jason@indexdata.com>
Tue, 24 Sep 2013 12:47:42 +0000 (08:47 -0400)
js/pz2.js

index d252eaa..becef33 100644 (file)
--- a/js/pz2.js
+++ b/js/pz2.js
@@ -1,19 +1,4 @@
 /*
- * !! WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING !!
- *
- * Do not edit this file. The current version of pz2.js now lives in
- * its own git module ssh://git.indexdata.com/home/git/pub/pz2js and
- * ALL FURTHER CHANGES SHOULD BE MADE TO THAT FILE.
- *
- * This copy will be removed as soon as we have good, solid Debian and
- * Red Hat releases of the new independent pz2js package and have
- * modified the pazpar2 packages to depend on it.
- *
- * !! WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING !!
- */
-
-
-/*
  * $Id$
 ** pz2.js - pazpar2's javascript client library.
 */
@@ -88,6 +73,8 @@ var pz2 = function ( paramArray )
     this.initStatusOK = false;
     this.pingStatusOK = false;
     this.searchStatusOK = false;
+    this.mergekey = paramArray.mergekey || null;
+    this.rank = paramArray.rank || null;
     
     // for sorting
     this.currentSort = "relevance";
@@ -310,8 +297,9 @@ pz2.prototype =
             this.currentSort = sort;
            searchParams["sort"] = sort;
        }
-        if (filter !== undefined)
-               searchParams["filter"] = filter;
+        if (filter !== undefined) searchParams["filter"] = filter;
+        if (this.mergekey) searchParams["mergekey"] = this.mergekey;
+        if (this.rank) searchParams["rank"] = this.rank;
 
         // copy additional parmeters, do not overwrite
         if (addParamsArr != undefined) {
@@ -787,16 +775,20 @@ var pzHttpRequest = function ( url, errorHandler ) {
         this.errorHandler = errorHandler || null;
         this.async = true;
         this.requestHeaders = {};
-        
-        if ( window.XMLHttpRequest ) {
-            this.request = new XMLHttpRequest();
-        } else if ( window.ActiveXObject ) {
-            try {
-                this.request = new ActiveXObject( 'Msxml2.XMLHTTP' );
-            } catch (err) {
-                this.request = new ActiveXObject( 'Microsoft.XMLHTTP' );
-            }
+        this.isXDomain = false;
+        this.domainRegex = /https?:\/\/([^:/]+).*/;
+
+        var xhr = new XMLHttpRequest();
+        if ("withCredentials" in xhr) {
+          // XHR for Chrome/Firefox/Opera/Safari.
+        } else if (typeof XDomainRequest != "undefined") {
+          // XDomainRequest for IE.
+          xhr = new XDomainRequest();
+          this.isXDomain = true;
+        } else {
+          // CORS not supported.
         }
+        this.request = xhr;
 };
 
 
@@ -849,16 +841,100 @@ pzHttpRequest.prototype =
         return encoded;
     },
 
+    _getDomainFromUrl: function (url)
+    {
+      var m = this.domainRegex.exec(url);
+      return (m && m.length > 1) ? m[1] : null;
+    },
+
+    _strEndsWith: function (str, suffix) 
+    {
+      return str.indexOf(suffix, str.length - suffix.length) !== -1;
+    },
+
+    _isCrossDomain: function (domain)
+    {
+      return !this._strEndsWith(domain, document.domain); 
+    },
+
+    getCookie: function (sKey) {
+      return decodeURI(document.cookie.replace(new RegExp("(?:(?:^|.*;)\\s*" 
+        + encodeURI(sKey).replace(/[\-\.\+\*]/g, "\\$&") 
+        + "\\s*\\=\\s*([^;]*).*$)|^.*$"), "$1")) || null;
+    },
+
+    setCookie: function (sKey, sValue, vEnd, sPath, sDomain, bSecure) {
+      if (!sKey || /^(?:expires|max\-age|path|domain|secure)$/i.test(sKey)) { 
+        return false; 
+      }
+      var sExpires = "";
+      if (vEnd) {
+        switch (vEnd.constructor) {
+          case Number:
+            sExpires = vEnd === Infinity 
+              ? "; expires=Fri, 31 Dec 9999 23:59:59 GMT" 
+              : "; max-age=" + vEnd;
+            break;
+          case String:
+            sExpires = "; expires=" + vEnd;
+            break;
+          case Date:
+            sExpires = "; expires=" + vEnd.toGMTString();
+            break;
+        }
+      }
+      document.cookie = encodeURI(sKey) + "=" + encodeURI(sValue) 
+        + sExpires 
+        + (sDomain ? "; domain=" + sDomain : "") 
+        + (sPath ? "; path=" + sPath : "") 
+        + (bSecure ? "; secure" : "");
+      return true;
+    },
+    
     _send: function ( type, url, data, callback)
     {
         var context = this;
         this.callback = callback;
         this.async = true;
+        //we never do withCredentials, so if it's CORS and we have
+        //session cookie, resend it
+        var domain = this._getDomainFromUrl(url);
+        if (domain && this._isCrossDomain(domain) &&
+            this.getCookie(domain+":SESSID")) {
+          //rewrite the URL
+          var sessparam = ';jsessionid=' + this.getCookie(domain+":SESSID");
+          var q = url.indexOf('?');
+          if (q == -1) {
+            url += sessparam;            
+          } else {
+            url = url.substring(0, q) + sessparam + url.substring(q);
+          }
+        }
         this.request.open( type, url, this.async );
-        for (var key in this.requestHeaders)
+        if (!this.isXDomain) {
+          //setting headers is only allowed with XHR
+          for (var key in this.requestHeaders)
             this.request.setRequestHeader(key, this.requestHeaders[key]);
-        this.request.onreadystatechange = function () {
+        }
+        if (this.isXDomain) {
+          this.request.onload = function () {
+            //fake XHR props
+            context.request.status = 200;
+            context.request.readyState = 4;
+            //handle
+            context._handleResponse(url);
+          }
+          this.request.onerror = function () {
+            //fake XHR props
+            context.request.status = 417; //not really, but what can we do
+            context.request.readyState = 4;
+            //handle
+            context._handleResponse(url);
+          }
+        } else {
+          this.request.onreadystatechange = function () {
             context._handleResponse(url); /// url used ONLY for error reporting
+          }
         }
         this.request.send(data);
     },
@@ -871,11 +947,22 @@ pzHttpRequest.prototype =
             return this.url;
     },
 
-    _handleResponse: function (savedUrlForErrorReporting)
+    _handleResponse: function (requestUrl)
     {
         if ( this.request.readyState == 4 ) { 
             // pick up appplication errors first
             var errNode = null;
+            // xdomainreq does not have responseXML
+            if (this.isXDomain) {
+              if (this.request.contentType.match(/\/xml/)){                
+                var dom = new ActiveXObject('Microsoft.XMLDOM');
+                dom.async = false;                
+                dom.loadXML(this.request.responseText);
+                this.request.responseXML = dom;
+              } else {
+                this.request.responseXML = null;
+              }
+            }
             if (this.request.responseXML &&
                 (errNode = this.request.responseXML.documentElement)
                 && errNode.nodeName == 'error') {
@@ -894,40 +981,25 @@ pzHttpRequest.prototype =
                 else {
                     throw err;
                 }
-            } else if (this.request.status == 200 && 
-                       this.request.responseXML == null) {
-              if (this.request.responseText != null) {
+            } 
+            else if (this.request.status == 200 && 
+                     this.request.responseXML === null) {
+              if (this.request.responseText !== null) {
                 //assume JSON
-               
-               var json = null; 
-               var text = this.request.responseText;
-               if (typeof window.JSON == "undefined") 
-                   json = eval("(" + text + ")");
-               else { 
-                   try {
-                       json = JSON.parse(text);
-                   }
-                   catch (e) {
-                       // Safari: eval will fail as well. Considering trying JSON2 (non-native implementation) instead
-                       /* DEBUG only works in mk2-mobile
-                       if (document.getElementById("log")) 
-                           document.getElementById("log").innerHTML = "" + e + " " + length + ": " + text;
-                       */
-                       try {
-                           json = eval("(" + text + ")");
-                       }
-                       catch (e) {
-                           /* DEBUG only works in mk2-mobile
-                           if (document.getElementById("log")) 
-                               document.getElementById("log").innerHTML = "" + e + " " + length + ": " + text;
-                           */
-                       }
-                   }
-               } 
-               this.callback(json, "json");
+                       var json = null; 
+                       var text = this.request.responseText;
+                       if (typeof window.JSON == "undefined") {
+                         json = eval("(" + text + ")");
+                } else { 
+                         try {
+                           json = JSON.parse(text);
+                         } catch (e) {
+                  }
+                       } 
+                       this.callback(json, "json");
               } else {
-                var err = new Error("XML response is empty but no error " +
-                                    "for " + savedUrlForErrorReporting);
+                var err = new Error("XML/Text response is empty but no error " +
+                                    "for " + requestUrl);
                 err.code = -1;
                 if (this.errorHandler) {
                     this.errorHandler(err);
@@ -936,6 +1008,14 @@ pzHttpRequest.prototype =
                 }
               }
             } else if (this.request.status == 200) {
+                //set cookie manually only if cross-domain
+                var domain = this._getDomainFromUrl(requestUrl);
+                if (domain && this._isCrossDomain(domain)) {
+                  var jsessionId = this.request.responseXML
+                    .documentElement.getAttribute('jsessionId');
+                  if (jsessionId)                  
+                    this.setCookie(domain+":SESSID", jsessionId);
+                }
                 this.callback(this.request.responseXML);
             } else {
                 var err = new Error("HTTP response not OK: "