From 99a86f482c8724ce8ee0dc9d5c831ae8bf60e373 Mon Sep 17 00:00:00 2001 From: Iain Nash Date: Fri, 6 Jan 2012 19:20:19 -0500 Subject: [PATCH 01/53] Starting timezone stuff --- visitor.js | 13 ++++++++++++- visitor.min.js | 9 ++++++++- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/visitor.js b/visitor.js index 3856e4b..571f5a8 100644 --- a/visitor.js +++ b/visitor.js @@ -253,6 +253,16 @@ utils.set_cookie(cookie_name, JSON.stringify(sess), expires) } return sess; + }, + time: function(){ + var now = new Date() + , dst_start = new Date(now.getYear() + 1900, 2, 1, 2, 0, 0) + , dst_end = new Date(now.getYear() + 1900, 10, 1, 2, 0, 0) + , daylight_savings = null + , offset_ms = now.getTimezoneOffset(); + if (dst_start.getDay()){ dst_start.setDate(dst_start.getDate() + (7-dst_start.getDay())); } + if (dst_end.getDay()){ dst_end.setDate(dst_end.getDate() + (7-dst_end.getDay())); } + return {daylight_savings: (now > dst_start && now < dst_end), offset_ms: offset_ms, offset_hours: -offset_ms/60}; } } var visitor_loader = { @@ -262,7 +272,8 @@ orig_session: modules.visitor('first_session', 1000 * 60 * 60 * 24 * (opts.session_days || 32)), browser: modules.browser(), plugins: modules.plugins(), - device: modules.device() + device: modules.device(), + time: modules.time() }, init: function(){ if (opts.enable_location){ diff --git a/visitor.min.js b/visitor.min.js index 66dc905..538352d 100644 --- a/visitor.min.js +++ b/visitor.min.js @@ -1 +1,8 @@ -undefined \ No newline at end of file +/** + * Visitor.js 0.0.2 + * (c) 2012 Iain, CodeJoust + * Visitor is freely distributable under the MIT license. + * Portions of Visitor.js are inspired or borrowed from Underscore.js, and quirksmode.org demo javascript. + * This version uses google's jsapi library for location services. + * For details, see: https://github.com/codejoust/visitor.js + */(function(win,doc){var opts={enable_location:!0,session_days:32};"session_opts"in window&&(opts=session_opts);var BrowserDetect={detect_browser:function(){return{browser:this.searchString(this.dataBrowser),version:this.searchVersion(navigator.userAgent)||this.searchVersion(navigator.appVersion),OS:this.searchString(this.dataOS)}},searchString:function(a){for(var b=0;b0){c_start=document.cookie.indexOf(a+"=");if(c_start!=-1){c_start=c_start+a.length+1,c_end=document.cookie.indexOf(";",c_start),c_end==-1&&(c_end=document.cookie.length);return unescape(document.cookie.substring(c_start,c_end))}}return null},each:function(a,b,c){if(a!=null)if(a.length===+a.length){for(var d=0,e=a.length;db&&a Date: Sat, 7 Jan 2012 18:49:13 -0500 Subject: [PATCH 02/53] Prelim test stuff --- test/test.coffee | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 test/test.coffee diff --git a/test/test.coffee b/test/test.coffee new file mode 100644 index 0000000..41906ff --- /dev/null +++ b/test/test.coffee @@ -0,0 +1,8 @@ +jsdom = require('jsdom') +test = require('tap').test +fs = require('fs') + +raw_lib = fs.readFileSync(__dirname + '/../session.js') + +test("Testing session.js -- navigator") + From 8381e28265ae794728dc99707529f1304d536ddb Mon Sep 17 00:00:00 2001 From: Iain Nash Date: Sat, 7 Jan 2012 22:06:41 -0500 Subject: [PATCH 03/53] Readme fixes --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index a47c8a5..331df90 100644 --- a/README.md +++ b/README.md @@ -19,8 +19,8 @@ Include `session.js` in the head or footer. Edge: [uncompressed](https://raw.github.com/codejoust/session.js/master/session.js), [compressed](https://raw.github.com/codejoust/session.js/master/session.min.js) -Lock version to v0.3 (last stable version): -[uncompressed](https://raw.github.com/codejoust/session.js/v0.4/session.js) [compressed](https://raw.github.com/codejoust/session.js/v0.4/session.min.js) +Lock version to v0.4 (last stable version): +[uncompressed](https://raw.github.com/codejoust/session.js/v0.4/session.js), [compressed](https://raw.github.com/codejoust/session.js/v0.4/session.min.js). If used in the footer (before the `` tag), you can use the `window.session_loaded = function(session){}` callback). From 8928df4f016c0af7eb98db6f0b81a74e3ac00742 Mon Sep 17 00:00:00 2001 From: Iain Nash Date: Sat, 7 Jan 2012 22:09:44 -0500 Subject: [PATCH 04/53] Readme copy updates --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 331df90..f191a92 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ Include `session.js` in the head or footer. Edge: [uncompressed](https://raw.github.com/codejoust/session.js/master/session.js), [compressed](https://raw.github.com/codejoust/session.js/master/session.min.js) -Lock version to v0.4 (last stable version): +Lock version to v0.4: [uncompressed](https://raw.github.com/codejoust/session.js/v0.4/session.js), [compressed](https://raw.github.com/codejoust/session.js/v0.4/session.min.js). If used in the footer (before the `` tag), you can use the `window.session_loaded = function(session){}` callback). From b0695fa5092c6d506a488998e3ad4d415d21915e Mon Sep 17 00:00:00 2001 From: Iain Nash Date: Sat, 7 Jan 2012 22:11:39 -0500 Subject: [PATCH 05/53] Adding back in try{}catch{} for failing modules. --- session.js | 6 +++--- session.min.js | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/session.js b/session.js index 5229b2b..063de98 100644 --- a/session.js +++ b/session.js @@ -78,17 +78,17 @@ for (var name in this.modules){ module = self.modules[name]; if(typeof module === "function"){ - //try { + try { asynchs++; module(function(data){ self.modules[name] = data; asynchs--; check_asynch(); }); - /*} catch(err){ + } catch(err){ if (win.console && typeof(console.log) === "function"){ console.log(err); } - }*/ + } } else { self.modules[name] = module; } diff --git a/session.min.js b/session.min.js index 9eb68d8..a302a30 100644 --- a/session.min.js +++ b/session.min.js @@ -5,4 +5,4 @@ * Portions of session.js are inspired or borrowed from Underscore.js, and quirksmode.org demo javascript. * This version uses google's jsapi library for location services. * For details, see: https://github.com/codejoust/session.js - */(function(a,b){var c=.4,d={use_html5_location:!1,ipinfodb_key:!1,gapi_location:!0,location_cookie:"location",location_cookie_timeout:2,session_timeout:32,session_cookie:"first_session"},e=function(){if(a.session&&a.session.options)for(option in a.session.options)d[option]=a.session.options[option];this.modules={api_version:c,locale:g.locale(),current_session:g.session(),original_session:g.session(d.session_cookie,d.session_timeout*24*60*60*1e3),browser:g.browser(),plugins:g.plugins(),device:g.device()},d.use_html5_location?this.modules.location=g.html5_location():d.ipinfodb_key?this.modules.location=g.ipinfodb_location(d.ipinfodb_key):d.gapi_location&&(this.modules.location=g.gapi_location());if(a.session&&a.session.start)var b=a.session.start;var e=0,f,h,i=this,j=function(){e===0&&(a.session=i.modules,b&&b(i.modules))};for(var k in this.modules)f=i.modules[k],typeof f=="function"?(e++,f(function(a){i.modules[k]=a,e--,j()})):i.modules[k]=f;j()},f={detect:function(){return{browser:this.search(this.data.browser),version:this.search(navigator.userAgent)||this.search(navigator.appVersion),os:this.search(this.data.os)}},search:function(a){if(typeof a!="object"){var e=a.indexOf(this.version_string);if(e==-1)return;return parseFloat(a.substr(e+this.version_string.length+1))}for(var b=0;b1)for(k=0;k1)for(k=0;k Date: Sat, 7 Jan 2012 23:23:42 -0500 Subject: [PATCH 06/53] cookie encoding fix --- session.js | 6 +++--- session.min.js | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/session.js b/session.js index 063de98..70df11d 100644 --- a/session.js +++ b/session.js @@ -340,7 +340,7 @@ expires = "; expires=" + date.toGMTString(); } else { expires = ""; } // set cookie - return (doc.cookie = (name + "=" + value + expires + path)); + return (doc.cookie = (name + "=" + encodeURIComponent(value) + expires + path)); }, get_cookie: function(cookie_name){ // from quirksmode.org var nameEQ = cookie_name + "="; @@ -348,7 +348,7 @@ for (var i = 0; i < ca.length; i++){ var c = ca[i]; while (c.charAt(0)==' '){ c = c.substring(1, c.length); } - if (c.indexOf(nameEQ) == 0){ return c.substring(nameEQ.length, c.length); } + if (c.indexOf(nameEQ) == 0){ return decodeURIComponent(c.substring(nameEQ.length, c.length)); } } return null; }, embed_script: function(url){ @@ -399,4 +399,4 @@ // Initialize SessionRunner SessionRunner(); -})(window, document); \ No newline at end of file +})(window, document); diff --git a/session.min.js b/session.min.js index a302a30..e22baa4 100644 --- a/session.min.js +++ b/session.min.js @@ -5,4 +5,4 @@ * Portions of session.js are inspired or borrowed from Underscore.js, and quirksmode.org demo javascript. * This version uses google's jsapi library for location services. * For details, see: https://github.com/codejoust/session.js - */(function(a,b){var c=.4,d={use_html5_location:!1,ipinfodb_key:!1,gapi_location:!0,location_cookie:"location",location_cookie_timeout:2,session_timeout:32,session_cookie:"first_session"},e=function(){if(a.session&&a.session.options)for(option in a.session.options)d[option]=a.session.options[option];this.modules={api_version:c,locale:g.locale(),current_session:g.session(),original_session:g.session(d.session_cookie,d.session_timeout*24*60*60*1e3),browser:g.browser(),plugins:g.plugins(),device:g.device()},d.use_html5_location?this.modules.location=g.html5_location():d.ipinfodb_key?this.modules.location=g.ipinfodb_location(d.ipinfodb_key):d.gapi_location&&(this.modules.location=g.gapi_location());if(a.session&&a.session.start)var b=a.session.start;var e=0,f,h,i=this,j=function(){e===0&&(a.session=i.modules,b&&b(i.modules))};for(var k in this.modules){f=i.modules[k];if(typeof f=="function")try{e++,f(function(a){i.modules[k]=a,e--,j()})}catch(l){a.console&&typeof console.log=="function"&&console.log(l)}else i.modules[k]=f}j()},f={detect:function(){return{browser:this.search(this.data.browser),version:this.search(navigator.userAgent)||this.search(navigator.appVersion),os:this.search(this.data.os)}},search:function(a){if(typeof a!="object"){var e=a.indexOf(this.version_string);if(e==-1)return;return parseFloat(a.substr(e+this.version_string.length+1))}for(var b=0;b1)for(k=0;k1)for(k=0;k Date: Sat, 7 Jan 2012 23:27:43 -0500 Subject: [PATCH 07/53] Removing util object from window root --- session.js | 2 +- session.min.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/session.js b/session.js index 70df11d..40fa70d 100644 --- a/session.js +++ b/session.js @@ -307,7 +307,7 @@ }; // Utilities - var util = win.util = { + var util = { parse_url: function(url_str){ var a = doc.createElement("a"), query = {}; a.href = url_str; query_str = a.search.substr(1); diff --git a/session.min.js b/session.min.js index e22baa4..6e3f733 100644 --- a/session.min.js +++ b/session.min.js @@ -5,4 +5,4 @@ * Portions of session.js are inspired or borrowed from Underscore.js, and quirksmode.org demo javascript. * This version uses google's jsapi library for location services. * For details, see: https://github.com/codejoust/session.js - */(function(a,b){var c=.4,d={use_html5_location:!1,ipinfodb_key:!1,gapi_location:!0,location_cookie:"location",location_cookie_timeout:2,session_timeout:32,session_cookie:"first_session"},e=function(){if(a.session&&a.session.options)for(option in a.session.options)d[option]=a.session.options[option];this.modules={api_version:c,locale:g.locale(),current_session:g.session(),original_session:g.session(d.session_cookie,d.session_timeout*24*60*60*1e3),browser:g.browser(),plugins:g.plugins(),device:g.device()},d.use_html5_location?this.modules.location=g.html5_location():d.ipinfodb_key?this.modules.location=g.ipinfodb_location(d.ipinfodb_key):d.gapi_location&&(this.modules.location=g.gapi_location());if(a.session&&a.session.start)var b=a.session.start;var e=0,f,h,i=this,j=function(){e===0&&(a.session=i.modules,b&&b(i.modules))};for(var k in this.modules){f=i.modules[k];if(typeof f=="function")try{e++,f(function(a){i.modules[k]=a,e--,j()})}catch(l){a.console&&typeof console.log=="function"&&console.log(l)}else i.modules[k]=f}j()},f={detect:function(){return{browser:this.search(this.data.browser),version:this.search(navigator.userAgent)||this.search(navigator.appVersion),os:this.search(this.data.os)}},search:function(a){if(typeof a!="object"){var e=a.indexOf(this.version_string);if(e==-1)return;return parseFloat(a.substr(e+this.version_string.length+1))}for(var b=0;b1)for(k=0;k1)for(k=0;k Date: Sun, 8 Jan 2012 08:29:52 -0500 Subject: [PATCH 08/53] fixing locale bug --- session.js | 11 +++++++---- session.min.js | 2 +- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/session.js b/session.js index 40fa70d..ce586dc 100644 --- a/session.js +++ b/session.js @@ -41,6 +41,8 @@ options[option] = win.session.options[option]; } } // Modules to run + // If the module has arguments, + // it _needs_ to return a callback function. this.modules = { api_version: API_VERSION, locale: modules.locale(), @@ -163,10 +165,11 @@ navigator.systemLanguage || navigator.userLanguage ).split("-"); - return { - country: lang[1].toLowerCase(), - lang: lang[0].toLowerCase() - }; + if (lang.length == 2){ + return { country: lang[1].toLowerCase(), lang: lang[0].toLowerCase() }; + } else if (lang) { + return {lang: lang[0].toLowerCase(), country: null }; + } else { return{lang: null, country: null }; } }, device: function() { var device = { diff --git a/session.min.js b/session.min.js index 6e3f733..1ffe69b 100644 --- a/session.min.js +++ b/session.min.js @@ -5,4 +5,4 @@ * Portions of session.js are inspired or borrowed from Underscore.js, and quirksmode.org demo javascript. * This version uses google's jsapi library for location services. * For details, see: https://github.com/codejoust/session.js - */(function(a,b){var c=.4,d={use_html5_location:!1,ipinfodb_key:!1,gapi_location:!0,location_cookie:"location",location_cookie_timeout:2,session_timeout:32,session_cookie:"first_session"},e=function(){if(a.session&&a.session.options)for(option in a.session.options)d[option]=a.session.options[option];this.modules={api_version:c,locale:g.locale(),current_session:g.session(),original_session:g.session(d.session_cookie,d.session_timeout*24*60*60*1e3),browser:g.browser(),plugins:g.plugins(),device:g.device()},d.use_html5_location?this.modules.location=g.html5_location():d.ipinfodb_key?this.modules.location=g.ipinfodb_location(d.ipinfodb_key):d.gapi_location&&(this.modules.location=g.gapi_location());if(a.session&&a.session.start)var b=a.session.start;var e=0,f,h,i=this,j=function(){e===0&&(a.session=i.modules,b&&b(i.modules))};for(var k in this.modules){f=i.modules[k];if(typeof f=="function")try{e++,f(function(a){i.modules[k]=a,e--,j()})}catch(l){a.console&&typeof console.log=="function"&&console.log(l)}else i.modules[k]=f}j()},f={detect:function(){return{browser:this.search(this.data.browser),version:this.search(navigator.userAgent)||this.search(navigator.appVersion),os:this.search(this.data.os)}},search:function(a){if(typeof a!="object"){var e=a.indexOf(this.version_string);if(e==-1)return;return parseFloat(a.substr(e+this.version_string.length+1))}for(var b=0;b1)for(k=0;k1)for(k=0;k Date: Sun, 8 Jan 2012 09:18:15 -0500 Subject: [PATCH 09/53] cookie rework --- session.js | 46 +++++++++++++++++++++------------------------- session.min.js | 2 +- 2 files changed, 22 insertions(+), 26 deletions(-) diff --git a/session.js b/session.js index ce586dc..5914ca9 100644 --- a/session.js +++ b/session.js @@ -332,27 +332,24 @@ search: a.search, query: query } }, - set_cookie: function(name, value, expires, path){ - if (!doc.cookie || !name || !value){ return null; } - // set path - path = path ? "; path=" + path : "; path=/"; - // calculate expiration date - if (expires) { - var date = new Date(); - date.setTime(date.getTime() + expires); - expires = "; expires=" + date.toGMTString(); - } else { expires = ""; } - // set cookie - return (doc.cookie = (name + "=" + encodeURIComponent(value) + expires + path)); + set_cookie: function(cname, value, expires, options){ // from jquery.cookie.j + if (!doc.cookie || !cname || !value){ return null; } + if (!options){ var options = {}; } + if (value === null || value === undefined){ expires = -1; } + if (typeof expires === 'number') { + var days = expires, t = expires = new Date(); + t.setDate(t.getDate() + days); } + return (document.cookie = [ + encodeURIComponent(cname), '=', + options.raw ? value : encodeURIComponent(String(value)), + options.expires ? '; expires=' + options.expires.toUTCString() : '', // use expires attribute, max-age is not supported by IE + options.path ? '; path=' + options.path : '', + options.domain ? '; domain=' + options.domain : '', + options.secure ? '; secure' : '' + ].join('')); }, - get_cookie: function(cookie_name){ // from quirksmode.org - var nameEQ = cookie_name + "="; - var ca = doc.cookie.split(';'); - for (var i = 0; i < ca.length; i++){ - var c = ca[i]; - while (c.charAt(0)==' '){ c = c.substring(1, c.length); } - if (c.indexOf(nameEQ) == 0){ return decodeURIComponent(c.substring(nameEQ.length, c.length)); } - } return null; + get_cookie: function(cookie_name, result){ // from jquery.cookie.js + return (result = new RegExp('(?:^|; )' + encodeURIComponent(cookie_name) + '=([^;]*)').exec(document.cookie)) ? decodeURIComponent(result[1]) : null; }, embed_script: function(url){ var element = doc.createElement("script"); @@ -366,7 +363,8 @@ delete obj.version; return ret; }, get_obj: function(cookie_name){ - var obj = JSON.parse(util.get_cookie(cookie_name)); + var obj; + try { obj = JSON.parse(util.get_cookie(cookie_name)); } catch(e){}; if (obj && obj.version == API_VERSION){ delete obj.version; return obj; } @@ -375,13 +373,11 @@ // JSON var JSON = { - parse: (win.JSON && win.JSON.parse) || function(data) { - try { + parse: (win.JSON && win.JSON.parse) || function(data){ if (typeof data !== "string" || !data){ return null; } return (new Function("return " + data))(); - } catch (e){ return null; } }, - stringify: (win.JSON && win.JSON.stringify) || function(object) { + stringify: (win.JSON && win.JSON.stringify) || function(object){ var type = typeof object; if (type !== "object" || object === null) { if (type === "string"){ return '"' + object + '"'; } diff --git a/session.min.js b/session.min.js index 1ffe69b..888e0cd 100644 --- a/session.min.js +++ b/session.min.js @@ -5,4 +5,4 @@ * Portions of session.js are inspired or borrowed from Underscore.js, and quirksmode.org demo javascript. * This version uses google's jsapi library for location services. * For details, see: https://github.com/codejoust/session.js - */(function(a,b){var c=.4,d={use_html5_location:!1,ipinfodb_key:!1,gapi_location:!0,location_cookie:"location",location_cookie_timeout:2,session_timeout:32,session_cookie:"first_session"},e=function(){if(a.session&&a.session.options)for(option in a.session.options)d[option]=a.session.options[option];this.modules={api_version:c,locale:g.locale(),current_session:g.session(),original_session:g.session(d.session_cookie,d.session_timeout*24*60*60*1e3),browser:g.browser(),plugins:g.plugins(),device:g.device()},d.use_html5_location?this.modules.location=g.html5_location():d.ipinfodb_key?this.modules.location=g.ipinfodb_location(d.ipinfodb_key):d.gapi_location&&(this.modules.location=g.gapi_location());if(a.session&&a.session.start)var b=a.session.start;var e=0,f,h,i=this,j=function(){e===0&&(a.session=i.modules,b&&b(i.modules))};for(var k in this.modules){f=i.modules[k];if(typeof f=="function")try{e++,f(function(a){i.modules[k]=a,e--,j()})}catch(l){a.console&&typeof console.log=="function"&&console.log(l)}else i.modules[k]=f}j()},f={detect:function(){return{browser:this.search(this.data.browser),version:this.search(navigator.userAgent)||this.search(navigator.appVersion),os:this.search(this.data.os)}},search:function(a){if(typeof a!="object"){var e=a.indexOf(this.version_string);if(e==-1)return;return parseFloat(a.substr(e+this.version_string.length+1))}for(var b=0;b1)for(k=0;k1)for(k=0;k Date: Sun, 8 Jan 2012 12:53:55 -0500 Subject: [PATCH 10/53] Updating readme / fixing set cookie --- README.md | 28 +++++++++++++++++----------- session.js | 30 ++++++++++++++---------------- session.min.js | 2 +- 3 files changed, 32 insertions(+), 28 deletions(-) diff --git a/README.md b/README.md index f191a92..4fecabe 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,7 @@ Include `session.js` in the head or footer. Edge: [uncompressed](https://raw.github.com/codejoust/session.js/master/session.js), [compressed](https://raw.github.com/codejoust/session.js/master/session.min.js) + Lock version to v0.4: [uncompressed](https://raw.github.com/codejoust/session.js/v0.4/session.js), [compressed](https://raw.github.com/codejoust/session.js/v0.4/session.min.js). @@ -35,14 +36,14 @@ If used in the footer (before the `` tag), you can use the `window.sessio }, "current_session": { "visits": 1, - "start": 1325991619228, - "last_visit": 1325991619228, + "start": 1326044899285, + "last_visit": 1326044899285, "url": "http://localhost:8000/demo.html", "path": "/demo.html", - "referrer": "http://localhost:8000/", + "referrer": "", "referrer_info": { "host": "localhost:8000", - "path": "/", + "path": "/demo.html", "protocol": "http:", "port": "8000", "search": "", @@ -54,9 +55,9 @@ If used in the footer (before the `` tag), you can use the `window.sessio } }, "original_session": { - "visits": 41, - "start": 1325990490065, - "last_visit": 1325991619229, + "visits": 20, + "start": 1326031569673, + "last_visit": 1326044899289, "url": "http://localhost:8000/demo.html", "path": "/demo.html", "referrer": "", @@ -66,7 +67,7 @@ If used in the footer (before the `` tag), you can use the `window.sessio "protocol": "http:", "port": "8000", "search": "", - "query": "" + "query": {} }, "search": { "engine": null, @@ -117,9 +118,13 @@ Default options are shown below. ipinfodb.com location [demo](http://codejoust.github.com/session.js/ipinfodb_demo.html). +Synchronous information (everything but location not cached in a cookie), +is available immediately after including session.js. + ```js window.session = { options: { + // Default Settings Example // Use the HTML5 Geolocation API // this ONLY returns lat & long, no city/address use_html5_location: false, @@ -129,16 +134,17 @@ window.session = { // Leaving true allows for fallback for both // the HTML5 location and the IPInfoDB gapi_location: true, - // Name of the location cookie + // Name of the location cookie (set blank to disable cookie) // - WARNING: different providers use the same cookie // - if switching providers, remember to use another cookie or provide checks for old cookies location_cookie: "location", // Location cookie expiration in hours - location_cookie_timeout: 2, + location_cookie_timeout: 5, // Session expiration in days session_timeout: 32, - // Session cookie name + // Session cookie name (set blank to disable cookie) session_cookie: "first_session" + }; }, start: { // Session location loaded. diff --git a/session.js b/session.js index 5914ca9..233a123 100644 --- a/session.js +++ b/session.js @@ -21,15 +21,15 @@ // Leaving true allows for fallback for both // the HTML5 location and the IPInfoDB gapi_location: true, - // Name of the location cookie + // Name of the location cookie (set blank to disable cookie) // - WARNING: different providers use the same cookie // - if switching providers, remember to use another cookie or provide checks for old cookies location_cookie: "location", // Location cookie expiration in hours - location_cookie_timeout: 2, + location_cookie_timeout: 5, // Session expiration in days session_timeout: 32, - // Session cookie name + // Session cookie name (set blank to disable cookie) session_cookie: "first_session" }; @@ -254,7 +254,7 @@ util.set_cookie(cookie, util.package_obj(session), expires); return session; }, - html5_location: function() { + html5_location: function(){ return function(callback){ navigator.geolocation.getCurrentPosition(function(pos){ pos.source = 'html5'; @@ -267,11 +267,11 @@ }); }; }, - gapi_location: function() { + gapi_location: function(){ return function(callback){ var location = util.get_obj(options.location_cookie); if (!location || location.source !== 'google'){ - win.gloaderReady = function() { + win.gloader_ready = function() { if ("google" in win){ if (win.google.loader.ClientLocation){ win.google.loader.ClientLocation.source = "google"; @@ -284,12 +284,12 @@ util.package_obj(win.google.loader.ClientLocation), options.location_cookie_timeout * 60 * 60 * 1000); }} - util.embed_script("https://www.google.com/jsapi?callback=gloaderReady"); + util.embed_script("https://www.google.com/jsapi?callback=gloader_ready"); } else { callback(location); }} }, - ipinfodb_location: function(apiKey){ + ipinfodb_location: function(api_key){ return function (callback){ var location_cookie = util.get_obj(options.location_cookie); if (location_cookie && location_cookie.source === 'ipinfodb'){ callback(location_cookie); } @@ -305,7 +305,7 @@ if (options.gapi_location){ return modules.gapi_location()(callback); } else { callback({error: true, source: "ipinfodb", message: data.statusMessage}); } }} - util.embed_script("http://api.ipinfodb.com/v3/ip-city/?key=" + apiKey + "&format=json&callback=ipinfocb"); + util.embed_script("http://api.ipinfodb.com/v3/ip-city/?key=" + api_key + "&format=json&callback=ipinfocb"); }} }; @@ -332,20 +332,18 @@ search: a.search, query: query } }, - set_cookie: function(cname, value, expires, options){ // from jquery.cookie.j + set_cookie: function(cname, value, expires, options){ // from jquery.cookie.js if (!doc.cookie || !cname || !value){ return null; } if (!options){ var options = {}; } if (value === null || value === undefined){ expires = -1; } - if (typeof expires === 'number') { - var days = expires, t = expires = new Date(); - t.setDate(t.getDate() + days); } + if (expires){ options.expires = (new Date().getTime()) + expires; } return (document.cookie = [ encodeURIComponent(cname), '=', - options.raw ? value : encodeURIComponent(String(value)), - options.expires ? '; expires=' + options.expires.toUTCString() : '', // use expires attribute, max-age is not supported by IE + encodeURIComponent(String(value)), + options.expires ? '; expires=' + new Date(options.expires).toUTCString() : '', // use expires attribute, max-age is not supported by IE options.path ? '; path=' + options.path : '', options.domain ? '; domain=' + options.domain : '', - options.secure ? '; secure' : '' + (win.location && win.location.protocol === 'https:') ? '; secure' : '' ].join('')); }, get_cookie: function(cookie_name, result){ // from jquery.cookie.js diff --git a/session.min.js b/session.min.js index 888e0cd..06b1f04 100644 --- a/session.min.js +++ b/session.min.js @@ -5,4 +5,4 @@ * Portions of session.js are inspired or borrowed from Underscore.js, and quirksmode.org demo javascript. * This version uses google's jsapi library for location services. * For details, see: https://github.com/codejoust/session.js - */(function(a,b){var c=.4,d={use_html5_location:!1,ipinfodb_key:!1,gapi_location:!0,location_cookie:"location",location_cookie_timeout:2,session_timeout:32,session_cookie:"first_session"},e=function(){if(a.session&&a.session.options)for(option in a.session.options)d[option]=a.session.options[option];this.modules={api_version:c,locale:g.locale(),current_session:g.session(),original_session:g.session(d.session_cookie,d.session_timeout*24*60*60*1e3),browser:g.browser(),plugins:g.plugins(),device:g.device()},d.use_html5_location?this.modules.location=g.html5_location():d.ipinfodb_key?this.modules.location=g.ipinfodb_location(d.ipinfodb_key):d.gapi_location&&(this.modules.location=g.gapi_location());if(a.session&&a.session.start)var b=a.session.start;var e=0,f,h,i=this,j=function(){e===0&&(a.session=i.modules,b&&b(i.modules))};for(var k in this.modules){f=i.modules[k];if(typeof f=="function")try{e++,f(function(a){i.modules[k]=a,e--,j()})}catch(l){a.console&&typeof console.log=="function"&&console.log(l)}else i.modules[k]=f}j()},f={detect:function(){return{browser:this.search(this.data.browser),version:this.search(navigator.userAgent)||this.search(navigator.appVersion),os:this.search(this.data.os)}},search:function(a){if(typeof a!="object"){var e=a.indexOf(this.version_string);if(e==-1)return;return parseFloat(a.substr(e+this.version_string.length+1))}for(var b=0;b1)for(k=0;k1)for(k=0;k Date: Sun, 8 Jan 2012 13:27:41 -0500 Subject: [PATCH 11/53] Add in prelim time checking, Closes #1 --- session.js | 21 ++++++++++++++++++++- session.min.js | 2 +- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/session.js b/session.js index 233a123..c1f471d 100644 --- a/session.js +++ b/session.js @@ -52,6 +52,7 @@ options.session_timeout * 24 * 60 * 60 * 1000), browser: modules.browser(), plugins: modules.plugins(), + time: modules.time(), // uses callback device: modules.device() }; // Location switch @@ -155,9 +156,27 @@ }; var modules = { - browser: function() { + browser: function(){ return browser.detect(); }, + time: function(){ + // wrapping in a callback to catch failures... + return function(cb){ + var d = new Date('Sat, 07 Jan 2012 04:10:00 +0000'); + d = d.toString(); + d = d.match(/\(([^]+)\)/)[1]; + // split date and grab timezone estimation. + // timezone estimation: http://www.onlineaspect.com/2007/06/08/auto-detect-a-time-zone-with-javascript/ + var now = new Date(), jan1 = new Date(now.getFullYear(), 0, 1, 0, 0, 0, 0), jan1_str = jan1.toGMTString() + , jan2 = new Date(jan1_str.substring(0, jan1_str.lastIndexOf(" ")-1)) + , std_time_offset = (jan1 - jan2) / (1000 * 60 * 60) + , june1 = new Date(rightNow.getFullYear(), 6, 1, 0, 0, 0, 0), june1_str = june1.toGMTString() + , june2 = new Date(temp.substring(0, temp.lastIndexOf(" ")-1)) + , daylight_time_offset = (june1 - june2) / (1000 * 60 * 60); + cb({timezone: d, tz_offset: -(new Date().getTimezoneOffset()) / 60, observes_dst: (std_time_offset === daylight_time_offset) }); + // Gives a browser estimation, not guaranteed to be correct. + }; + }, locale: function() { var lang = ( navigator.language || diff --git a/session.min.js b/session.min.js index 06b1f04..78c7b97 100644 --- a/session.min.js +++ b/session.min.js @@ -5,4 +5,4 @@ * Portions of session.js are inspired or borrowed from Underscore.js, and quirksmode.org demo javascript. * This version uses google's jsapi library for location services. * For details, see: https://github.com/codejoust/session.js - */(function(a,b){var c=.4,d={use_html5_location:!1,ipinfodb_key:!1,gapi_location:!0,location_cookie:"location",location_cookie_timeout:5,session_timeout:32,session_cookie:"first_session"},e=function(){if(a.session&&a.session.options)for(option in a.session.options)d[option]=a.session.options[option];this.modules={api_version:c,locale:g.locale(),current_session:g.session(),original_session:g.session(d.session_cookie,d.session_timeout*24*60*60*1e3),browser:g.browser(),plugins:g.plugins(),device:g.device()},d.use_html5_location?this.modules.location=g.html5_location():d.ipinfodb_key?this.modules.location=g.ipinfodb_location(d.ipinfodb_key):d.gapi_location&&(this.modules.location=g.gapi_location());if(a.session&&a.session.start)var b=a.session.start;var e=0,f,h,i=this,j=function(){e===0&&(a.session=i.modules,b&&b(i.modules))};for(var k in this.modules){f=i.modules[k];if(typeof f=="function")try{e++,f(function(a){i.modules[k]=a,e--,j()})}catch(l){a.console&&typeof console.log=="function"&&console.log(l)}else i.modules[k]=f}j()},f={detect:function(){return{browser:this.search(this.data.browser),version:this.search(navigator.userAgent)||this.search(navigator.appVersion),os:this.search(this.data.os)}},search:function(a){if(typeof a!="object"){var e=a.indexOf(this.version_string);if(e==-1)return;return parseFloat(a.substr(e+this.version_string.length+1))}for(var b=0;b1)for(k=0;k1)for(k=0;k Date: Sun, 8 Jan 2012 19:22:03 -0500 Subject: [PATCH 12/53] Adding date/time stuff --- session.js | 14 +++++--------- session.min.js | 2 +- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/session.js b/session.js index c1f471d..4c7c1db 100644 --- a/session.js +++ b/session.js @@ -161,21 +161,17 @@ }, time: function(){ // wrapping in a callback to catch failures... - return function(cb){ + //return function(cb){ var d = new Date('Sat, 07 Jan 2012 04:10:00 +0000'); d = d.toString(); d = d.match(/\(([^]+)\)/)[1]; // split date and grab timezone estimation. // timezone estimation: http://www.onlineaspect.com/2007/06/08/auto-detect-a-time-zone-with-javascript/ - var now = new Date(), jan1 = new Date(now.getFullYear(), 0, 1, 0, 0, 0, 0), jan1_str = jan1.toGMTString() - , jan2 = new Date(jan1_str.substring(0, jan1_str.lastIndexOf(" ")-1)) - , std_time_offset = (jan1 - jan2) / (1000 * 60 * 60) - , june1 = new Date(rightNow.getFullYear(), 6, 1, 0, 0, 0, 0), june1_str = june1.toGMTString() - , june2 = new Date(temp.substring(0, temp.lastIndexOf(" ")-1)) - , daylight_time_offset = (june1 - june2) / (1000 * 60 * 60); - cb({timezone: d, tz_offset: -(new Date().getTimezoneOffset()) / 60, observes_dst: (std_time_offset === daylight_time_offset) }); + var d1 = new Date(), d2 = new Date(); + d1.setMonth(0); d1.setDate(1); d2.setMonth(6); d2.setDate(1); + return({timezone: d, tz_offset: -(new Date().getTimezoneOffset()) / 60, observes_dst: (d1.getTimezoneOffset() !== d2.getTimezoneOffset()) }); // Gives a browser estimation, not guaranteed to be correct. - }; + //}; }, locale: function() { var lang = ( diff --git a/session.min.js b/session.min.js index 78c7b97..6dc67c5 100644 --- a/session.min.js +++ b/session.min.js @@ -5,4 +5,4 @@ * Portions of session.js are inspired or borrowed from Underscore.js, and quirksmode.org demo javascript. * This version uses google's jsapi library for location services. * For details, see: https://github.com/codejoust/session.js - */(function(a,b){var c=.4,d={use_html5_location:!1,ipinfodb_key:!1,gapi_location:!0,location_cookie:"location",location_cookie_timeout:5,session_timeout:32,session_cookie:"first_session"},e=function(){if(a.session&&a.session.options)for(option in a.session.options)d[option]=a.session.options[option];this.modules={api_version:c,locale:g.locale(),current_session:g.session(),original_session:g.session(d.session_cookie,d.session_timeout*24*60*60*1e3),browser:g.browser(),plugins:g.plugins(),time:g.time(),device:g.device()},d.use_html5_location?this.modules.location=g.html5_location():d.ipinfodb_key?this.modules.location=g.ipinfodb_location(d.ipinfodb_key):d.gapi_location&&(this.modules.location=g.gapi_location());if(a.session&&a.session.start)var b=a.session.start;var e=0,f,h,i=this,j=function(){e===0&&(a.session=i.modules,b&&b(i.modules))};for(var k in this.modules){f=i.modules[k];if(typeof f=="function")try{e++,f(function(a){i.modules[k]=a,e--,j()})}catch(l){a.console&&typeof console.log=="function"&&console.log(l)}else i.modules[k]=f}j()},f={detect:function(){return{browser:this.search(this.data.browser),version:this.search(navigator.userAgent)||this.search(navigator.appVersion),os:this.search(this.data.os)}},search:function(a){if(typeof a!="object"){var e=a.indexOf(this.version_string);if(e==-1)return;return parseFloat(a.substr(e+this.version_string.length+1))}for(var b=0;b1)for(k=0;k1)for(k=0;k Date: Sun, 8 Jan 2012 19:27:12 -0500 Subject: [PATCH 13/53] updating readme --- README.md | 35 ++++++++++++++++++++--------------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 4fecabe..9958b55 100644 --- a/README.md +++ b/README.md @@ -36,16 +36,16 @@ If used in the footer (before the `` tag), you can use the `window.sessio }, "current_session": { "visits": 1, - "start": 1326044899285, - "last_visit": 1326044899285, - "url": "http://localhost:8000/demo.html", - "path": "/demo.html", + "start": 1326068806791, + "last_visit": 1326068806791, + "url": "http://codejoust.github.com/session.js/", + "path": "/session.js/", "referrer": "", "referrer_info": { - "host": "localhost:8000", - "path": "/demo.html", + "host": "codejoust.github.com", + "path": "/session.js/", "protocol": "http:", - "port": "8000", + "port": 80, "search": "", "query": {} }, @@ -55,17 +55,17 @@ If used in the footer (before the `` tag), you can use the `window.sessio } }, "original_session": { - "visits": 20, - "start": 1326031569673, - "last_visit": 1326044899289, - "url": "http://localhost:8000/demo.html", - "path": "/demo.html", + "visits": 14, + "start": 1326032481755, + "last_visit": 1326068806793, + "url": "http://codejoust.github.com/session.js/", + "path": "/session.js/", "referrer": "", "referrer_info": { - "host": "localhost:8000", - "path": "/demo.html", + "host": "codejoust.github.com", + "path": "/session.js/", "protocol": "http:", - "port": "8000", + "port": 80, "search": "", "query": {} }, @@ -85,6 +85,11 @@ If used in the footer (before the `` tag), you can use the `window.sessio "java": true, "quicktime": true }, + "time": { + "timezone": "EST", + "tz_offset": -5, + "observes_dst": true + }, "device": { "screen": { "width": 1280, From 63d9b7808b27a9911003b2bee6e9adf738abdf11 Mon Sep 17 00:00:00 2001 From: Iain Nash Date: Mon, 9 Jan 2012 08:04:10 -0500 Subject: [PATCH 14/53] building tests --- test/test.coffee | 38 +++++++++++++++++++++++++++++++++++--- test/test.js | 42 ++++++++++++++++++++++++++++++++++++++++++ test/ua.js | 0 3 files changed, 77 insertions(+), 3 deletions(-) create mode 100644 test/test.js create mode 100644 test/ua.js diff --git a/test/test.coffee b/test/test.coffee index 41906ff..fb816fc 100644 --- a/test/test.coffee +++ b/test/test.coffee @@ -1,8 +1,40 @@ jsdom = require('jsdom') -test = require('tap').test fs = require('fs') +assert = require('assert') +raw_lib = fs.readFileSync(__dirname + '/../session.js').toString() -raw_lib = fs.readFileSync(__dirname + '/../session.js') +config = 'window.session = ' + JSON.stringify({ + config: { + gapi_location: false + location_cookie: '' + session_cookie: '' + } +}) + ';' -test("Testing session.js -- navigator") +win = jsdom.jsdom('').createWindow(); +add_script = (content) -> + scr = win.document.createElement('script'); + scr.innerHTML = content + win.document.getElementsByTagName('head')[0].appendChild(scr) +add_script(config) +add_script(raw_lib) +setTimeout(-> + console.log(win.document.innerHTML) +, 400) + +### +jsdom.env('', ['../session.js'], (err, win) -> + win.onload= -> + console.log 'load' + console.log win.session + console.log([err, win.onload]) +) + +### +doc = jsdom.jsdom('loading...') +win = doc.createWindow() +console.log doc.innerHTML +win.onload = -> + console.log 'load' + console.log win.session \ No newline at end of file diff --git a/test/test.js b/test/test.js new file mode 100644 index 0000000..29854e1 --- /dev/null +++ b/test/test.js @@ -0,0 +1,42 @@ +(function() { + var add_script, assert, config, doc, fs, jsdom, raw_lib, win; + jsdom = require('jsdom'); + fs = require('fs'); + assert = require('assert'); + raw_lib = fs.readFileSync(__dirname + '/../session.js').toString(); + config = 'window.session = ' + JSON.stringify({ + config: { + gapi_location: false, + location_cookie: '', + session_cookie: '' + } + }) + ';'; + win = jsdom.jsdom('').createWindow(); + add_script = function(content) { + var scr; + scr = win.document.createElement('script'); + scr.innerHTML = content; + return win.document.getElementsByTagName('head')[0].appendChild(scr); + }; + add_script(config); + add_script(raw_lib); + setTimeout(function() { + return console.log(win.document.innerHTML); + }, 400); + /* + jsdom.env('', ['../session.js'], (err, win) -> + win.onload= -> + console.log 'load' + console.log win.session + console.log([err, win.onload]) + ) + + */ + doc = jsdom.jsdom('loading...'); + win = doc.createWindow(); + console.log(doc.innerHTML); + win.onload = function() { + console.log('load'); + return console.log(win.session); + }; +}).call(this); diff --git a/test/ua.js b/test/ua.js new file mode 100644 index 0000000..e69de29 From 70858f121994156c5a496d101e7fe307f97343c7 Mon Sep 17 00:00:00 2001 From: Iain Nash Date: Mon, 9 Jan 2012 08:07:30 -0500 Subject: [PATCH 15/53] adding in time checks --- session.js | 64 +++++++++++++++++++++++++------------------------- session.min.js | 2 +- 2 files changed, 33 insertions(+), 33 deletions(-) diff --git a/session.js b/session.js index 4c7c1db..d397b70 100644 --- a/session.js +++ b/session.js @@ -6,7 +6,7 @@ * This version uses google's jsapi library for location services. * For details, see: https://github.com/codejoust/session.js */ -(function(win, doc){ +(function(win, doc, nav){ // Changing the API Version invalidates olde cookies with previous api version tags. var API_VERSION = 0.4; @@ -105,7 +105,7 @@ detect: function(){ return { browser: this.search(this.data.browser), - version: this.search(navigator.userAgent) || this.search(navigator.appVersion), + version: this.search(nav.userAgent) || this.search(nav.appVersion), os: this.search(this.data.os) } }, search: function(data) { @@ -132,26 +132,26 @@ }, data: { browser: [ - { string: navigator.userAgent, subString: "Chrome", identity: "Chrome" }, - { string: navigator.userAgent, subString: "OmniWeb", versionSearch: "OmniWeb/", identity: "OmniWeb" }, - { string: navigator.vendor, subString: "Apple", identity: "Safari", versionSearch: "Version" }, + { string: nav.userAgent, subString: "Chrome", identity: "Chrome" }, + { string: nav.userAgent, subString: "OmniWeb", versionSearch: "OmniWeb/", identity: "OmniWeb" }, + { string: nav.vendor, subString: "Apple", identity: "Safari", versionSearch: "Version" }, { prop: win.opera, identity: "Opera", versionSearch: "Version" }, - { string: navigator.vendor, subString: "iCab",identity: "iCab" }, - { string: navigator.vendor, subString: "KDE", identity: "Konqueror" }, - { string: navigator.userAgent, subString: "Firefox", identity: "Firefox" }, - { string: navigator.vendor, subString: "Camino", identity: "Camino" }, - { string: navigator.userAgent, subString: "Netscape", identity: "Netscape" }, - { string: navigator.userAgent, subString: "MSIE", identity: "Explorer", versionSearch: "MSIE" }, - { string: navigator.userAgent, subString: "Gecko", identity: "Mozilla", versionSearch: "rv" }, - { string: navigator.userAgent, subString: "Mozilla", identity: "Netscape", versionSearch: "Mozilla" } + { string: nav.vendor, subString: "iCab",identity: "iCab" }, + { string: nav.vendor, subString: "KDE", identity: "Konqueror" }, + { string: nav.userAgent, subString: "Firefox", identity: "Firefox" }, + { string: nav.vendor, subString: "Camino", identity: "Camino" }, + { string: nav.userAgent, subString: "Netscape", identity: "Netscape" }, + { string: nav.userAgent, subString: "MSIE", identity: "Explorer", versionSearch: "MSIE" }, + { string: nav.userAgent, subString: "Gecko", identity: "Mozilla", versionSearch: "rv" }, + { string: nav.userAgent, subString: "Mozilla", identity: "Netscape", versionSearch: "Mozilla" } ], os: [ - { string: navigator.platform, subString: "Win", identity: "Windows" }, - { string: navigator.platform, subString: "Mac", identity: "Mac" }, - { string: navigator.userAgent, subString: "iPhone", identity: "iPhone/iPod" }, - { string: navigator.userAgent, subString: "iPad", identitiy: "iPad" }, - { string: navigator.platform, subString: "Linux", identity: "Linux" }, - { string: navigator.userAgent, subString: "Android", identity: "Android" } + { string: nav.platform, subString: "Win", identity: "Windows" }, + { string: nav.platform, subString: "Mac", identity: "Mac" }, + { string: nav.userAgent, subString: "iPhone", identity: "iPhone/iPod" }, + { string: nav.userAgent, subString: "iPad", identitiy: "iPad" }, + { string: nav.platform, subString: "Linux", identity: "Linux" }, + { string: nav.userAgent, subString: "Android", identity: "Android" } ]} }; @@ -161,7 +161,7 @@ }, time: function(){ // wrapping in a callback to catch failures... - //return function(cb){ + try { var d = new Date('Sat, 07 Jan 2012 04:10:00 +0000'); d = d.toString(); d = d.match(/\(([^]+)\)/)[1]; @@ -171,14 +171,14 @@ d1.setMonth(0); d1.setDate(1); d2.setMonth(6); d2.setDate(1); return({timezone: d, tz_offset: -(new Date().getTimezoneOffset()) / 60, observes_dst: (d1.getTimezoneOffset() !== d2.getTimezoneOffset()) }); // Gives a browser estimation, not guaranteed to be correct. - //}; + } catch (e){ return {err: true, msg: e}; } }, locale: function() { var lang = ( - navigator.language || - navigator.browserLanguage || - navigator.systemLanguage || - navigator.userLanguage + nav.language || + nav.browserLanguage || + nav.systemLanguage || + nav.userLanguage ).split("-"); if (lang.length == 2){ return { country: lang[1].toLowerCase(), lang: lang[0].toLowerCase() }; @@ -199,17 +199,17 @@ width: win.innerWidth || html.clientWidth || body.clientWidth, height: win.innerHeight || html.clientHeight || body.clientHeight }; - device.is_tablet = !!navigator.userAgent.match(/(iPad|SCH-I800|xoom|kindle)/i); - device.is_phone = !device.isTablet && !!navigator.userAgent.match(/(iPhone|iPod|blackberry|android 0.5|htc|lg|midp|mmp|mobile|nokia|opera mini|palm|pocket|psp|sgh|smartphone|symbian|treo mini|Playstation Portable|SonyEricsson|Samsung|MobileExplorer|PalmSource|Benq|Windows Phone|Windows Mobile|IEMobile|Windows CE|Nintendo Wii)/i); + device.is_tablet = !!nav.userAgent.match(/(iPad|SCH-I800|xoom|kindle)/i); + device.is_phone = !device.isTablet && !!nav.userAgent.match(/(iPhone|iPod|blackberry|android 0.5|htc|lg|midp|mmp|mobile|nokia|opera mini|palm|pocket|psp|sgh|smartphone|symbian|treo mini|Playstation Portable|SonyEricsson|Samsung|MobileExplorer|PalmSource|Benq|Windows Phone|Windows Mobile|IEMobile|Windows CE|Nintendo Wii)/i); device.is_mobile = (device.is_tablet || device.is_phone); return device; }, plugins: function(){ var check_plugin = function(name){ - if (navigator.plugins){ - var plugin, i = 0, length = navigator.plugins.length; + if (nav.plugins){ + var plugin, i = 0, length = nav.plugins.length; for (; i < length; i++ ){ - plugin = navigator.plugins[i]; + plugin = nav.plugins[i]; if (plugin && plugin.name && plugin.name.toLowerCase().indexOf(name) !== -1){ return true; } } @@ -271,7 +271,7 @@ }, html5_location: function(){ return function(callback){ - navigator.geolocation.getCurrentPosition(function(pos){ + nav.geolocation.getCurrentPosition(function(pos){ pos.source = 'html5'; callback(pos); }, function(err) { @@ -411,4 +411,4 @@ // Initialize SessionRunner SessionRunner(); -})(window, document); +})(window, document, navigator); diff --git a/session.min.js b/session.min.js index 6dc67c5..8aabc62 100644 --- a/session.min.js +++ b/session.min.js @@ -5,4 +5,4 @@ * Portions of session.js are inspired or borrowed from Underscore.js, and quirksmode.org demo javascript. * This version uses google's jsapi library for location services. * For details, see: https://github.com/codejoust/session.js - */(function(a,b){var c=.4,d={use_html5_location:!1,ipinfodb_key:!1,gapi_location:!0,location_cookie:"location",location_cookie_timeout:5,session_timeout:32,session_cookie:"first_session"},e=function(){if(a.session&&a.session.options)for(option in a.session.options)d[option]=a.session.options[option];this.modules={api_version:c,locale:g.locale(),current_session:g.session(),original_session:g.session(d.session_cookie,d.session_timeout*24*60*60*1e3),browser:g.browser(),plugins:g.plugins(),time:g.time(),device:g.device()},d.use_html5_location?this.modules.location=g.html5_location():d.ipinfodb_key?this.modules.location=g.ipinfodb_location(d.ipinfodb_key):d.gapi_location&&(this.modules.location=g.gapi_location());if(a.session&&a.session.start)var b=a.session.start;var e=0,f,h,i=this,j=function(){e===0&&(a.session=i.modules,b&&b(i.modules))};for(var k in this.modules){f=i.modules[k];if(typeof f=="function")try{e++,f(function(a){i.modules[k]=a,e--,j()})}catch(l){a.console&&typeof console.log=="function"&&console.log(l)}else i.modules[k]=f}j()},f={detect:function(){return{browser:this.search(this.data.browser),version:this.search(navigator.userAgent)||this.search(navigator.appVersion),os:this.search(this.data.os)}},search:function(a){if(typeof a!="object"){var e=a.indexOf(this.version_string);if(e==-1)return;return parseFloat(a.substr(e+this.version_string.length+1))}for(var b=0;b1)for(k=0;k1)for(k=0;k Date: Mon, 9 Jan 2012 09:46:20 -0500 Subject: [PATCH 16/53] Moving to github pages as cdn due to mime types --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 9958b55..27ca068 100644 --- a/README.md +++ b/README.md @@ -17,11 +17,11 @@ Include `session.js` in the head or footer. #### Download/Linking: Edge: -[uncompressed](https://raw.github.com/codejoust/session.js/master/session.js), -[compressed](https://raw.github.com/codejoust/session.js/master/session.min.js) +[uncompressed](http://codejoust.github.com/session.js/session.js), +[compressed](http://codejoust.github.com/session.js/session.min.js) Lock version to v0.4: -[uncompressed](https://raw.github.com/codejoust/session.js/v0.4/session.js), [compressed](https://raw.github.com/codejoust/session.js/v0.4/session.min.js). +[uncompressed](http://codejoust.github.com/session.js/session-0.4.js), [compressed](http://codejoust.github.com/session.js/session-0.4.min.js). If used in the footer (before the `` tag), you can use the `window.session_loaded = function(session){}` callback). @@ -157,4 +157,4 @@ window.session = { } ``` - \ No newline at end of file + From dcf154ea2cef09bb7f300297deefdc6a9312a06a Mon Sep 17 00:00:00 2001 From: Iain Nash Date: Mon, 9 Jan 2012 10:02:48 -0500 Subject: [PATCH 17/53] Removing timezone abbr, more trouble than it's worth (Close #1) --- session.js | 18 ++++++------------ session.min.js | 2 +- 2 files changed, 7 insertions(+), 13 deletions(-) diff --git a/session.js b/session.js index d397b70..9ffb4cd 100644 --- a/session.js +++ b/session.js @@ -160,18 +160,12 @@ return browser.detect(); }, time: function(){ - // wrapping in a callback to catch failures... - try { - var d = new Date('Sat, 07 Jan 2012 04:10:00 +0000'); - d = d.toString(); - d = d.match(/\(([^]+)\)/)[1]; - // split date and grab timezone estimation. - // timezone estimation: http://www.onlineaspect.com/2007/06/08/auto-detect-a-time-zone-with-javascript/ - var d1 = new Date(), d2 = new Date(); - d1.setMonth(0); d1.setDate(1); d2.setMonth(6); d2.setDate(1); - return({timezone: d, tz_offset: -(new Date().getTimezoneOffset()) / 60, observes_dst: (d1.getTimezoneOffset() !== d2.getTimezoneOffset()) }); - // Gives a browser estimation, not guaranteed to be correct. - } catch (e){ return {err: true, msg: e}; } + // split date and grab timezone estimation. + // timezone estimation: http://www.onlineaspect.com/2007/06/08/auto-detect-a-time-zone-with-javascript/ + var d1 = new Date(), d2 = new Date(); + d1.setMonth(0); d1.setDate(1); d2.setMonth(6); d2.setDate(1); + return({timezone: d, tz_offset: -(new Date().getTimezoneOffset()) / 60, observes_dst: (d1.getTimezoneOffset() !== d2.getTimezoneOffset()) }); + // Gives a browser estimation, not guaranteed to be correct. }, locale: function() { var lang = ( diff --git a/session.min.js b/session.min.js index 8aabc62..89a0f12 100644 --- a/session.min.js +++ b/session.min.js @@ -5,4 +5,4 @@ * Portions of session.js are inspired or borrowed from Underscore.js, and quirksmode.org demo javascript. * This version uses google's jsapi library for location services. * For details, see: https://github.com/codejoust/session.js - */(function(a,b,c){var d=.4,e={use_html5_location:!1,ipinfodb_key:!1,gapi_location:!0,location_cookie:"location",location_cookie_timeout:5,session_timeout:32,session_cookie:"first_session"},f=function(){if(a.session&&a.session.options)for(option in a.session.options)e[option]=a.session.options[option];this.modules={api_version:d,locale:h.locale(),current_session:h.session(),original_session:h.session(e.session_cookie,e.session_timeout*24*60*60*1e3),browser:h.browser(),plugins:h.plugins(),time:h.time(),device:h.device()},e.use_html5_location?this.modules.location=h.html5_location():e.ipinfodb_key?this.modules.location=h.ipinfodb_location(e.ipinfodb_key):e.gapi_location&&(this.modules.location=h.gapi_location());if(a.session&&a.session.start)var b=a.session.start;var c=0,f,g,i=this,j=function(){c===0&&(a.session=i.modules,b&&b(i.modules))};for(var k in this.modules){f=i.modules[k];if(typeof f=="function")try{c++,f(function(a){i.modules[k]=a,c--,j()})}catch(l){a.console&&typeof console.log=="function"&&console.log(l)}else i.modules[k]=f}j()},g={detect:function(){return{browser:this.search(this.data.browser),version:this.search(c.userAgent)||this.search(c.appVersion),os:this.search(this.data.os)}},search:function(a){if(typeof a!="object"){var e=a.indexOf(this.version_string);if(e==-1)return;return parseFloat(a.substr(e+this.version_string.length+1))}for(var b=0;b1)for(k=0;k1)for(k=0;k Date: Mon, 9 Jan 2012 10:04:31 -0500 Subject: [PATCH 18/53] Removing time zone result --- session.js | 8 ++++---- session.min.js | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/session.js b/session.js index 9ffb4cd..f66d87f 100644 --- a/session.js +++ b/session.js @@ -52,7 +52,7 @@ options.session_timeout * 24 * 60 * 60 * 1000), browser: modules.browser(), plugins: modules.plugins(), - time: modules.time(), // uses callback + time: modules.time(), device: modules.device() }; // Location switch @@ -162,9 +162,9 @@ time: function(){ // split date and grab timezone estimation. // timezone estimation: http://www.onlineaspect.com/2007/06/08/auto-detect-a-time-zone-with-javascript/ - var d1 = new Date(), d2 = new Date(); - d1.setMonth(0); d1.setDate(1); d2.setMonth(6); d2.setDate(1); - return({timezone: d, tz_offset: -(new Date().getTimezoneOffset()) / 60, observes_dst: (d1.getTimezoneOffset() !== d2.getTimezoneOffset()) }); + var d1 = new Date(), d2 = new Date(); + d1.setMonth(0); d1.setDate(1); d2.setMonth(6); d2.setDate(1); + return({tz_offset: -(new Date().getTimezoneOffset()) / 60, observes_dst: (d1.getTimezoneOffset() !== d2.getTimezoneOffset()) }); // Gives a browser estimation, not guaranteed to be correct. }, locale: function() { diff --git a/session.min.js b/session.min.js index 89a0f12..1021418 100644 --- a/session.min.js +++ b/session.min.js @@ -5,4 +5,4 @@ * Portions of session.js are inspired or borrowed from Underscore.js, and quirksmode.org demo javascript. * This version uses google's jsapi library for location services. * For details, see: https://github.com/codejoust/session.js - */(function(a,b,c){var e=.4,f={use_html5_location:!1,ipinfodb_key:!1,gapi_location:!0,location_cookie:"location",location_cookie_timeout:5,session_timeout:32,session_cookie:"first_session"},g=function(){if(a.session&&a.session.options)for(option in a.session.options)f[option]=a.session.options[option];this.modules={api_version:e,locale:i.locale(),current_session:i.session(),original_session:i.session(f.session_cookie,f.session_timeout*24*60*60*1e3),browser:i.browser(),plugins:i.plugins(),time:i.time(),device:i.device()},f.use_html5_location?this.modules.location=i.html5_location():f.ipinfodb_key?this.modules.location=i.ipinfodb_location(f.ipinfodb_key):f.gapi_location&&(this.modules.location=i.gapi_location());if(a.session&&a.session.start)var b=a.session.start;var c=0,d,g,h=this,j=function(){c===0&&(a.session=h.modules,b&&b(h.modules))};for(var k in this.modules){d=h.modules[k];if(typeof d=="function")try{c++,d(function(a){h.modules[k]=a,c--,j()})}catch(l){a.console&&typeof console.log=="function"&&console.log(l)}else h.modules[k]=d}j()},h={detect:function(){return{browser:this.search(this.data.browser),version:this.search(c.userAgent)||this.search(c.appVersion),os:this.search(this.data.os)}},search:function(a){if(typeof a!="object"){var e=a.indexOf(this.version_string);if(e==-1)return;return parseFloat(a.substr(e+this.version_string.length+1))}for(var b=0;b1)for(k=0;k1)for(k=0;k Date: Mon, 9 Jan 2012 20:44:49 -0500 Subject: [PATCH 19/53] fixing blocking api --- demo.html | 3 ++- session.js | 40 +++++++++++++++++++++++----------------- session.min.js | 2 +- 3 files changed, 26 insertions(+), 19 deletions(-) diff --git a/demo.html b/demo.html index d7f1118..26c7146 100644 --- a/demo.html +++ b/demo.html @@ -141,10 +141,11 @@

Session.js Demo Page

window.session = { options: { use_html5_location: false, + gapi_location: true, session_timeout: 5 }, start: function( data ) { - document.getElementsByTagName('pre')[0].innerHTML = JSONf.stringify( data, undefined, 2 ); + document.getElementsByTagName('pre')[0].innerHTML = JSONf.stringify( session, undefined, 2 ); } }; diff --git a/session.js b/session.js index f66d87f..b2befb3 100644 --- a/session.js +++ b/session.js @@ -35,15 +35,23 @@ // Session object var SessionRunner = function(){ + // Helper for querying. + // Usage: session.current_session.referrer_info.hostname.contains(['github.com','news.ycombinator.com']) + String.prototype.contains = function(other_str){ + if (typeof(other_str) === 'string'){ + return (this.indexOf(other_str) !== -1); } + for (var i = 0; i < other_str.length; i++){ + if (this.indexOf(other_str[i]) !== -1){ return true; } } + return false; } // Merge options - if(win.session && win.session.options) { + if (win.session && win.session.options) { for (option in win.session.options){ options[option] = win.session.options[option]; } } // Modules to run // If the module has arguments, // it _needs_ to return a callback function. - this.modules = { + var unloaded_modules = { api_version: API_VERSION, locale: modules.locale(), current_session: modules.session(), @@ -57,45 +65,43 @@ }; // Location switch if (options.use_html5_location){ - this.modules.location = modules.html5_location(); + unloaded_modules.location = modules.html5_location(); } else if (options.ipinfodb_key){ - this.modules.location = modules.ipinfodb_location(options.ipinfodb_key); + unloaded_modules.location = modules.ipinfodb_location(options.ipinfodb_key); } else if (options.gapi_location){ - this.modules.location = modules.gapi_location(); + unloaded_modules.location = modules.gapi_location(); } // Cache win.session.start if (win.session && win.session.start){ var start = win.session.start; } // Set up checking, if all modules are ready - var asynchs = 0, module, result, self = this, + var asynchs = 0, module, result, check_asynch = function(){ if (asynchs === 0){ - // Map over results - win.session = self.modules; // Run start calback - if (start){ start(self.modules); } + if (start){ start(win.session); } } }; + win.session = {}; // Run asynchronous methods - for (var name in this.modules){ - module = self.modules[name]; - if(typeof module === "function"){ + for (var name in unloaded_modules){ + module = unloaded_modules[name]; + if (typeof module === "function"){ try { - asynchs++; module(function(data){ - self.modules[name] = data; + win.session[name] = data; asynchs--; check_asynch(); }); + asynchs++; } catch(err){ if (win.console && typeof(console.log) === "function"){ console.log(err); } } } else { - self.modules[name] = module; - } - } + win.session[name] = module; + } } check_asynch(); }; diff --git a/session.min.js b/session.min.js index 1021418..346e035 100644 --- a/session.min.js +++ b/session.min.js @@ -5,4 +5,4 @@ * Portions of session.js are inspired or borrowed from Underscore.js, and quirksmode.org demo javascript. * This version uses google's jsapi library for location services. * For details, see: https://github.com/codejoust/session.js - */(function(a,b,c){var d=.4,e={use_html5_location:!1,ipinfodb_key:!1,gapi_location:!0,location_cookie:"location",location_cookie_timeout:5,session_timeout:32,session_cookie:"first_session"},f=function(){if(a.session&&a.session.options)for(option in a.session.options)e[option]=a.session.options[option];this.modules={api_version:d,locale:h.locale(),current_session:h.session(),original_session:h.session(e.session_cookie,e.session_timeout*24*60*60*1e3),browser:h.browser(),plugins:h.plugins(),time:h.time(),device:h.device()},e.use_html5_location?this.modules.location=h.html5_location():e.ipinfodb_key?this.modules.location=h.ipinfodb_location(e.ipinfodb_key):e.gapi_location&&(this.modules.location=h.gapi_location());if(a.session&&a.session.start)var b=a.session.start;var c=0,f,g,i=this,j=function(){c===0&&(a.session=i.modules,b&&b(i.modules))};for(var k in this.modules){f=i.modules[k];if(typeof f=="function")try{c++,f(function(a){i.modules[k]=a,c--,j()})}catch(l){a.console&&typeof console.log=="function"&&console.log(l)}else i.modules[k]=f}j()},g={detect:function(){return{browser:this.search(this.data.browser),version:this.search(c.userAgent)||this.search(c.appVersion),os:this.search(this.data.os)}},search:function(a){if(typeof a!="object"){var e=a.indexOf(this.version_string);if(e==-1)return;return parseFloat(a.substr(e+this.version_string.length+1))}for(var b=0;b1)for(k=0;k1)for(k=0;k Date: Mon, 9 Jan 2012 23:47:50 -0500 Subject: [PATCH 20/53] Updating readme, adding scripts --- README.md | 45 ++++++++++++++++++++++++++++++++++----------- session.js | 8 ++++---- session.min.js | 2 +- 3 files changed, 39 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 27ca068..2ae2ed0 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ To use: include the file session.js, then access the visitor object. It uses the google javascript loader to get location data. For async loading, use the window.session_loaded callback. -[Live Demo](http://go.iain.in/sessionjslivedemo01) +[Live API Demo](http://go.iain.in/sessionjslivedemo01) | [Example Page]() Configurable options are below. @@ -16,12 +16,36 @@ Configurable options are below. Include `session.js` in the head or footer. #### Download/Linking: +Recommended: +[Api v0.4 Uncompressed](http://codejoust.github.com/session.js/session-0.4.js) + +Quick Example: +```js + + +``` +#### Other Options: Edge: [uncompressed](http://codejoust.github.com/session.js/session.js), -[compressed](http://codejoust.github.com/session.js/session.min.js) - +[compressed](http://codejoust.github.com/session.js/session.min.js) Lock version to v0.4: -[uncompressed](http://codejoust.github.com/session.js/session-0.4.js), [compressed](http://codejoust.github.com/session.js/session-0.4.min.js). +[uncompressed](http://codejoust.github.com/session.js/session-0.4.js), +[compressed](http://codejoust.github.com/session.js/session-0.4.min.js). If used in the footer (before the `` tag), you can use the `window.session_loaded = function(session){}` callback). @@ -36,8 +60,8 @@ If used in the footer (before the `` tag), you can use the `window.sessio }, "current_session": { "visits": 1, - "start": 1326068806791, - "last_visit": 1326068806791, + "start": 1326170811877, + "last_visit": 1326170811877, "url": "http://codejoust.github.com/session.js/", "path": "/session.js/", "referrer": "", @@ -55,9 +79,9 @@ If used in the footer (before the `` tag), you can use the `window.sessio } }, "original_session": { - "visits": 14, + "visits": 29, "start": 1326032481755, - "last_visit": 1326068806793, + "last_visit": 1326170811879, "url": "http://codejoust.github.com/session.js/", "path": "/session.js/", "referrer": "", @@ -86,7 +110,6 @@ If used in the footer (before the `` tag), you can use the `window.sessio "quicktime": true }, "time": { - "timezone": "EST", "tz_offset": -5, "observes_dst": true }, @@ -96,8 +119,8 @@ If used in the footer (before the `` tag), you can use the `window.sessio "height": 1024 }, "viewport": { - "width": 1063, - "height": 860 + "width": 1230, + "height": 952 }, "is_tablet": false, "is_phone": false, diff --git a/session.js b/session.js index b2befb3..c870213 100644 --- a/session.js +++ b/session.js @@ -77,7 +77,8 @@ } // Set up checking, if all modules are ready var asynchs = 0, module, result, - check_asynch = function(){ + check_asynch = function(deinc){ + if (deinc){ asynchs--; } if (asynchs === 0){ // Run start calback if (start){ start(win.session); } @@ -91,13 +92,12 @@ try { module(function(data){ win.session[name] = data; - asynchs--; - check_asynch(); + check_asynch(true); }); asynchs++; } catch(err){ if (win.console && typeof(console.log) === "function"){ - console.log(err); } + console.log(err); check_asynch(true); } } } else { win.session[name] = module; diff --git a/session.min.js b/session.min.js index 346e035..a570f28 100644 --- a/session.min.js +++ b/session.min.js @@ -5,4 +5,4 @@ * Portions of session.js are inspired or borrowed from Underscore.js, and quirksmode.org demo javascript. * This version uses google's jsapi library for location services. * For details, see: https://github.com/codejoust/session.js - */(function(a,b,c){var d=.4,e={use_html5_location:!1,ipinfodb_key:!1,gapi_location:!0,location_cookie:"location",location_cookie_timeout:5,session_timeout:32,session_cookie:"first_session"},f=function(){String.prototype.contains=function(a){if(typeof a=="string")return this.indexOf(a)!==-1;for(var b=0;b1)for(k=0;k1)for(k=0;k Date: Mon, 9 Jan 2012 23:51:49 -0500 Subject: [PATCH 21/53] Update README.md --- README.md | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 2ae2ed0..002b14e 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ To use: include the file session.js, then access the visitor object. It uses the google javascript loader to get location data. For async loading, use the window.session_loaded callback. -[Live API Demo](http://go.iain.in/sessionjslivedemo01) | [Example Page]() +[Live API Demo](http://go.iain.in/sessionjslivedemo01) | [Example Usage Page](http://go.iain.in/sessionjslivedemo02) Configurable options are below. @@ -39,15 +39,17 @@ Quick Example: ``` -#### Other Options: +#### Other Source Options: +Lock version to v0.4 (current stable): +[uncompressed](http://codejoust.github.com/session.js/session-0.4.js), +[compressed](http://codejoust.github.com/session.js/session-0.4.min.js). + Edge: [uncompressed](http://codejoust.github.com/session.js/session.js), [compressed](http://codejoust.github.com/session.js/session.min.js) -Lock version to v0.4: -[uncompressed](http://codejoust.github.com/session.js/session-0.4.js), -[compressed](http://codejoust.github.com/session.js/session-0.4.min.js). -If used in the footer (before the `` tag), you can use the `window.session_loaded = function(session){}` callback). + +If used in the footer (before the `` tag), you can use the `window.session = {start: function(sess){ /* loaded session data */ }}` callback, before including the session.js. This is recommended when using session.js with location data. ### API demo dump of `window.session`: From c8453ec206d652dde214b0e09394a8d8e50b2da6 Mon Sep 17 00:00:00 2001 From: Iain Nash Date: Mon, 9 Jan 2012 23:54:17 -0500 Subject: [PATCH 22/53] Update README.md --- README.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 002b14e..e99d633 100644 --- a/README.md +++ b/README.md @@ -20,11 +20,12 @@ Recommended: [Api v0.4 Uncompressed](http://codejoust.github.com/session.js/session-0.4.js) Quick Example: -```js - - + ``` + #### Other Source Options: Lock version to v0.4 (current stable): [uncompressed](http://codejoust.github.com/session.js/session-0.4.js), From 437818a14e5403d6e53c638b89640ef0fbfbbebd Mon Sep 17 00:00:00 2001 From: Iain Nash Date: Tue, 10 Jan 2012 00:02:21 -0500 Subject: [PATCH 23/53] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index e99d633..021b0e2 100644 --- a/README.md +++ b/README.md @@ -178,8 +178,8 @@ window.session = { session_cookie: "first_session" }; }, - start: { - // Session location loaded. + start: function(session){ + // Session location loaded into window.session and first argument. } } ``` From 5117fe94c7999c332694caa440170b35a8031000 Mon Sep 17 00:00:00 2001 From: Iain Nash Date: Wed, 11 Jan 2012 20:29:36 -0500 Subject: [PATCH 24/53] Explitictly setting cookie session path --- session.js | 2 +- session.min.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/session.js b/session.js index c870213..29e682e 100644 --- a/session.js +++ b/session.js @@ -356,7 +356,7 @@ encodeURIComponent(cname), '=', encodeURIComponent(String(value)), options.expires ? '; expires=' + new Date(options.expires).toUTCString() : '', // use expires attribute, max-age is not supported by IE - options.path ? '; path=' + options.path : '', + options.path ? '; path=' + options.path : '/', options.domain ? '; domain=' + options.domain : '', (win.location && win.location.protocol === 'https:') ? '; secure' : '' ].join('')); diff --git a/session.min.js b/session.min.js index a570f28..dbb37f0 100644 --- a/session.min.js +++ b/session.min.js @@ -5,4 +5,4 @@ * Portions of session.js are inspired or borrowed from Underscore.js, and quirksmode.org demo javascript. * This version uses google's jsapi library for location services. * For details, see: https://github.com/codejoust/session.js - */(function(a,b,c){var d=.4,e={use_html5_location:!1,ipinfodb_key:!1,gapi_location:!0,location_cookie:"location",location_cookie_timeout:5,session_timeout:32,session_cookie:"first_session"},f=function(){String.prototype.contains=function(a){if(typeof a=="string")return this.indexOf(a)!==-1;for(var b=0;b1)for(k=0;k1)for(k=0;k Date: Wed, 11 Jan 2012 20:35:20 -0500 Subject: [PATCH 25/53] Fix for fix of cookies path --- session.js | 2 +- session.min.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/session.js b/session.js index 29e682e..0aec7d3 100644 --- a/session.js +++ b/session.js @@ -356,7 +356,7 @@ encodeURIComponent(cname), '=', encodeURIComponent(String(value)), options.expires ? '; expires=' + new Date(options.expires).toUTCString() : '', // use expires attribute, max-age is not supported by IE - options.path ? '; path=' + options.path : '/', + '; path=' + options.path ? options.path : '/', options.domain ? '; domain=' + options.domain : '', (win.location && win.location.protocol === 'https:') ? '; secure' : '' ].join('')); diff --git a/session.min.js b/session.min.js index dbb37f0..b64ba10 100644 --- a/session.min.js +++ b/session.min.js @@ -5,4 +5,4 @@ * Portions of session.js are inspired or borrowed from Underscore.js, and quirksmode.org demo javascript. * This version uses google's jsapi library for location services. * For details, see: https://github.com/codejoust/session.js - */(function(a,b,c){var d=.4,e={use_html5_location:!1,ipinfodb_key:!1,gapi_location:!0,location_cookie:"location",location_cookie_timeout:5,session_timeout:32,session_cookie:"first_session"},f=function(){String.prototype.contains=function(a){if(typeof a=="string")return this.indexOf(a)!==-1;for(var b=0;b1)for(k=0;k1)for(k=0;k Date: Wed, 11 Jan 2012 20:44:20 -0500 Subject: [PATCH 26/53] Setting path improperly --- session.js | 4 ++-- session.min.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/session.js b/session.js index 0aec7d3..0b8534e 100644 --- a/session.js +++ b/session.js @@ -348,7 +348,7 @@ query: query } }, set_cookie: function(cname, value, expires, options){ // from jquery.cookie.js - if (!doc.cookie || !cname || !value){ return null; } + if (!cname){ return null; } if (!options){ var options = {}; } if (value === null || value === undefined){ expires = -1; } if (expires){ options.expires = (new Date().getTime()) + expires; } @@ -356,7 +356,7 @@ encodeURIComponent(cname), '=', encodeURIComponent(String(value)), options.expires ? '; expires=' + new Date(options.expires).toUTCString() : '', // use expires attribute, max-age is not supported by IE - '; path=' + options.path ? options.path : '/', + '; path=' + (options.path ? options.path : '/'), options.domain ? '; domain=' + options.domain : '', (win.location && win.location.protocol === 'https:') ? '; secure' : '' ].join('')); diff --git a/session.min.js b/session.min.js index b64ba10..1ae942b 100644 --- a/session.min.js +++ b/session.min.js @@ -5,4 +5,4 @@ * Portions of session.js are inspired or borrowed from Underscore.js, and quirksmode.org demo javascript. * This version uses google's jsapi library for location services. * For details, see: https://github.com/codejoust/session.js - */(function(a,b,c){var d=.4,e={use_html5_location:!1,ipinfodb_key:!1,gapi_location:!0,location_cookie:"location",location_cookie_timeout:5,session_timeout:32,session_cookie:"first_session"},f=function(){String.prototype.contains=function(a){if(typeof a=="string")return this.indexOf(a)!==-1;for(var b=0;b1)for(k=0;k1)for(k=0;k Date: Thu, 12 Jan 2012 09:49:39 -0500 Subject: [PATCH 27/53] Starting testing with jasmine --- session.js | 14 +- test/SpecRunner.html | 67 + test/spec/helpers/cookiejar.js | 216 +++ test/spec/helpers/date.format.js | 126 ++ test/spec/helpers/session-mocker.js | 43 + test/spec/jasmine/MIT.LICENSE | 20 + test/spec/jasmine/jasmine-html.js | 190 ++ test/spec/jasmine/jasmine.css | 166 ++ test/spec/jasmine/jasmine.js | 2471 +++++++++++++++++++++++++ test/spec/jasmine/jasmine_favicon.png | Bin 0 -> 905 bytes test/spec/session-api-spec.js | 27 + test/spec/session-info-spec.js | 106 ++ test/spec/timezone-spec.js | 6 + test/test.coffee | 40 - test/test.js | 42 - test/ua.js | 0 16 files changed, 3448 insertions(+), 86 deletions(-) create mode 100644 test/SpecRunner.html create mode 100644 test/spec/helpers/cookiejar.js create mode 100644 test/spec/helpers/date.format.js create mode 100644 test/spec/helpers/session-mocker.js create mode 100644 test/spec/jasmine/MIT.LICENSE create mode 100644 test/spec/jasmine/jasmine-html.js create mode 100644 test/spec/jasmine/jasmine.css create mode 100644 test/spec/jasmine/jasmine.js create mode 100644 test/spec/jasmine/jasmine_favicon.png create mode 100644 test/spec/session-api-spec.js create mode 100644 test/spec/session-info-spec.js create mode 100644 test/spec/timezone-spec.js delete mode 100644 test/test.coffee delete mode 100644 test/test.js delete mode 100644 test/ua.js diff --git a/session.js b/session.js index 0b8534e..8eb963e 100644 --- a/session.js +++ b/session.js @@ -6,7 +6,7 @@ * This version uses google's jsapi library for location services. * For details, see: https://github.com/codejoust/session.js */ -(function(win, doc, nav){ +var session_fetch = (function(win, doc, nav){ // Changing the API Version invalidates olde cookies with previous api version tags. var API_VERSION = 0.4; @@ -352,7 +352,7 @@ if (!options){ var options = {}; } if (value === null || value === undefined){ expires = -1; } if (expires){ options.expires = (new Date().getTime()) + expires; } - return (document.cookie = [ + return (doc.cookie = [ encodeURIComponent(cname), '=', encodeURIComponent(String(value)), options.expires ? '; expires=' + new Date(options.expires).toUTCString() : '', // use expires attribute, max-age is not supported by IE @@ -362,7 +362,7 @@ ].join('')); }, get_cookie: function(cookie_name, result){ // from jquery.cookie.js - return (result = new RegExp('(?:^|; )' + encodeURIComponent(cookie_name) + '=([^;]*)').exec(document.cookie)) ? decodeURIComponent(result[1]) : null; + return (result = new RegExp('(?:^|; )' + encodeURIComponent(cookie_name) + '=([^;]*)').exec(doc.cookie)) ? decodeURIComponent(result[1]) : null; }, embed_script: function(url){ var element = doc.createElement("script"); @@ -411,4 +411,10 @@ // Initialize SessionRunner SessionRunner(); -})(window, document, navigator); +}); + +if (typeof(exports) === 'undefined'){ + session_fetch(window, document, navigator); +} else { + exports.session = session_fetch; +} diff --git a/test/SpecRunner.html b/test/SpecRunner.html new file mode 100644 index 0000000..dcb962c --- /dev/null +++ b/test/SpecRunner.html @@ -0,0 +1,67 @@ + + + + Jasmine Spec Runner for Session.js + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Session.js test runner

+ + diff --git a/test/spec/helpers/cookiejar.js b/test/spec/helpers/cookiejar.js new file mode 100644 index 0000000..71181d5 --- /dev/null +++ b/test/spec/helpers/cookiejar.js @@ -0,0 +1,216 @@ +// Code modified for client-side use by CodeJoust +// originally from: +// bmeck/node-cookiejar - GitHub +// https://github.com/bmeck/node-cookiejar + + +var cookiejar = {} + +var CookieAccessInfo = cookiejar.CookieAccessInfo = function CookieAccessInfo(domain, path, secure, script) { + if (this instanceof CookieAccessInfo) { + this.domain = domain || undefined; + this.path = path || "/"; + this.secure = !! secure; + this.script = !! script; + return this; + } else { + return new CookieAccessInfo(domain, path, secure, script) + } +} + +var Cookie = cookiejar.Cookie = function Cookie(cookiestr) { + if (cookiestr instanceof Cookie) { + return cookiestr; + } else { + if (this instanceof Cookie) { + this.name = null; + this.value = null; + this.expiration_date = Infinity; + this.path = "/"; + this.domain = null; + this.secure = false; //how to define? + this.noscript = false; //httponly + if (cookiestr) { + this.parse(cookiestr) + } + return this; + } + return new Cookie(cookiestr) + } +} + +Cookie.prototype.toString = function toString() { + var str = [this.name + "=" + this.value]; + if (this.expiration_date !== Infinity) { + str.push("expires=" + (new Date(this.expiration_date)).toGMTString()); + } + if (this.domain) { + str.push("domain=" + this.domain); + } + if (this.path) { + str.push("path=" + this.path); + } + if (this.secure) { + str.push("secure"); + } + if (this.noscript) { + str.push("httponly"); + } + return str.join("; "); +} + +Cookie.prototype.toValueString = function toValueString() { + return this.name + "=" + this.value; +} + +var cookie_str_splitter = /[:](?=\s*[a-zA-Z0-9_\-]+\s*[=])/g +Cookie.prototype.parse = function parse(str){ + if (this instanceof Cookie) { + var parts = str.split(";"), + pair = parts[0].match(/([^=]+)=((?:.|\n)*)/), + key = pair[1], + value = pair[2]; + this.name = key; + this.value = value; + for (var i = 1; i < parts.length; i++) { + pair = parts[i].match(/([^=]+)(?:=((?:.|\n)*))?/), key = pair[1].trim().toLowerCase(), value = pair[2]; + switch (key) { + case "httponly": + this.noscript = true; + break; + case "expires": + this.expiration_date = value ? Number(Date.parse(value)) : Infinity; + break; + case "path": + this.path = value ? value.trim() : ""; + break; + case "domain": + this.domain = value ? value.trim() : ""; + break; + case "secure": + this.secure = true; + break + } + } + + return this; + } + return new Cookie().parse(str) +} + +Cookie.prototype.matches = function matches(access_info) { + if (this.noscript && access_info.script || this.secure && !access_info.secure || !this.collidesWith(access_info)) { + return false + } + return true; +} + +Cookie.prototype.collidesWith = function collidesWith(access_info) { + if ((this.path && !access_info.path) || (this.domain && !access_info.domain)) { + return false + } + if (this.path && access_info.path.indexOf(this.path) !== 0) { + return false; + } + if (this.domain === access_info.domain) { + return true; + } else if (this.domain && this.domain.charAt(0) === ".") { + var wildcard = access_info.domain.indexOf(this.domain.slice(1)) + if (wildcard === -1 || wildcard !== access_info.domain.length - this.domain.length + 1) { + return false; + } + } else if (this.domain) { + return false + } + return true; +} + +var CookieJar = cookiejar.CookieJar = function CookieJar() { + if (this instanceof CookieJar) { + var cookies = {} //name: [Cookie] + this.setCookie = function setCookie(cookie){ + cookie = Cookie(cookie); + //Delete the cookie if the set is past the current time + var remove = cookie.expiration_date <= Date.now(); + if (cookie.name in cookies) { + var cookies_list = cookies[cookie.name]; + for (var i = 0; i < cookies_list.length; i++) { + var collidable_cookie = cookies_list[i]; + if (collidable_cookie.collidesWith(cookie)) { + if (remove) { + cookies_list.splice(i, 1); + if (cookies_list.length === 0) { + delete cookies[cookie.name] + } + return false; + } else { + return cookies_list[i] = cookie; + } + } + } + if (remove) { + return false; + } + cookies_list.push(cookie); + return cookie; + } else if (remove) { + return false; + } else { + return cookies[cookie.name] = [cookie]; + } + } + //returns a cookie + this.getCookie = function getCookie(cookie_name, access_info) { + var cookies_list = cookies[cookie_name]; + for (var i = 0; i < cookies_list.length; i++) { + var cookie = cookies_list[i]; + if (cookie.expiration_date <= Date.now()) { + if (cookies_list.length === 0) { + delete cookies[cookie.name] + } + continue; + } + if (cookie.matches(access_info)) { + return cookie; + } + } + } + //returns a list of cookies + this.getCookies = function getCookies(access_info) { + var matches = []; + for (var cookie_name in cookies) { + var cookie = this.getCookie(cookie_name, access_info); + if (cookie) { + matches.push(cookie); + } + } + matches.toString = function toString() { + return matches.join(":"); + } + matches.toValueString = function() { + return matches.map(function(c) { + return c.toValueString(); + }).join(';'); + } + return matches; + } + + return this; + } + + return new CookieJar() +} + + +//returns list of cookies that were set correctly. Cookies that are expired and removed are not returned. +CookieJar.prototype.setCookies = function setCookies(cookies) { + cookies = Array.isArray(cookies) ? cookies : cookies.split(cookie_str_splitter); + var successful = [] + for (var i = 0; i < cookies.length; i++) { + var cookie = Cookie(cookies[i]); + if (this.setCookie(cookie)) { + successful.push(cookie); + } + } + return successful; +} \ No newline at end of file diff --git a/test/spec/helpers/date.format.js b/test/spec/helpers/date.format.js new file mode 100644 index 0000000..3992c50 --- /dev/null +++ b/test/spec/helpers/date.format.js @@ -0,0 +1,126 @@ +/* + * Date Format 1.2.3 + * (c) 2007-2009 Steven Levithan + * MIT license + * + * Includes enhancements by Scott Trenda + * and Kris Kowal + * + * Accepts a date, a mask, or a date and a mask. + * Returns a formatted version of the given date. + * The date defaults to the current date/time. + * The mask defaults to dateFormat.masks.default. + */ + +var dateFormat = function () { + var token = /d{1,4}|m{1,4}|yy(?:yy)?|([HhMsTt])\1?|[LloSZ]|"[^"]*"|'[^']*'/g, + timezone = /\b(?:[PMCEA][SDP]T|(?:Pacific|Mountain|Central|Eastern|Atlantic) (?:Standard|Daylight|Prevailing) Time|(?:GMT|UTC)(?:[-+]\d{4})?)\b/g, + timezoneClip = /[^-+\dA-Z]/g, + pad = function (val, len) { + val = String(val); + len = len || 2; + while (val.length < len) val = "0" + val; + return val; + }; + + // Regexes and supporting functions are cached through closure + return function (date, mask, utc) { + var dF = dateFormat; + + // You can't provide utc if you skip other args (use the "UTC:" mask prefix) + if (arguments.length == 1 && Object.prototype.toString.call(date) == "[object String]" && !/\d/.test(date)) { + mask = date; + date = undefined; + } + + // Passing date through Date applies Date.parse, if necessary + date = date ? new Date(date) : new Date; + if (isNaN(date)) throw SyntaxError("invalid date"); + + mask = String(dF.masks[mask] || mask || dF.masks["default"]); + + // Allow setting the utc argument via the mask + if (mask.slice(0, 4) == "UTC:") { + mask = mask.slice(4); + utc = true; + } + + var _ = utc ? "getUTC" : "get", + d = date[_ + "Date"](), + D = date[_ + "Day"](), + m = date[_ + "Month"](), + y = date[_ + "FullYear"](), + H = date[_ + "Hours"](), + M = date[_ + "Minutes"](), + s = date[_ + "Seconds"](), + L = date[_ + "Milliseconds"](), + o = utc ? 0 : date.getTimezoneOffset(), + flags = { + d: d, + dd: pad(d), + ddd: dF.i18n.dayNames[D], + dddd: dF.i18n.dayNames[D + 7], + m: m + 1, + mm: pad(m + 1), + mmm: dF.i18n.monthNames[m], + mmmm: dF.i18n.monthNames[m + 12], + yy: String(y).slice(2), + yyyy: y, + h: H % 12 || 12, + hh: pad(H % 12 || 12), + H: H, + HH: pad(H), + M: M, + MM: pad(M), + s: s, + ss: pad(s), + l: pad(L, 3), + L: pad(L > 99 ? Math.round(L / 10) : L), + t: H < 12 ? "a" : "p", + tt: H < 12 ? "am" : "pm", + T: H < 12 ? "A" : "P", + TT: H < 12 ? "AM" : "PM", + Z: utc ? "UTC" : (String(date).match(timezone) || [""]).pop().replace(timezoneClip, ""), + o: (o > 0 ? "-" : "+") + pad(Math.floor(Math.abs(o) / 60) * 100 + Math.abs(o) % 60, 4), + S: ["th", "st", "nd", "rd"][d % 10 > 3 ? 0 : (d % 100 - d % 10 != 10) * d % 10] + }; + + return mask.replace(token, function ($0) { + return $0 in flags ? flags[$0] : $0.slice(1, $0.length - 1); + }); + }; +}(); + +// Some common format strings +dateFormat.masks = { + "default": "ddd mmm dd yyyy HH:MM:ss", + shortDate: "m/d/yy", + mediumDate: "mmm d, yyyy", + longDate: "mmmm d, yyyy", + fullDate: "dddd, mmmm d, yyyy", + shortTime: "h:MM TT", + mediumTime: "h:MM:ss TT", + longTime: "h:MM:ss TT Z", + isoDate: "yyyy-mm-dd", + isoTime: "HH:MM:ss", + isoDateTime: "yyyy-mm-dd'T'HH:MM:ss", + isoUtcDateTime: "UTC:yyyy-mm-dd'T'HH:MM:ss'Z'" +}; + +// Internationalization strings +dateFormat.i18n = { + dayNames: [ + "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", + "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" + ], + monthNames: [ + "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", + "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" + ] +}; + +// For convenience... +Date.prototype.format = function (mask, utc) { + return dateFormat(this, mask, utc); +}; + diff --git a/test/spec/helpers/session-mocker.js b/test/spec/helpers/session-mocker.js new file mode 100644 index 0000000..88fffa2 --- /dev/null +++ b/test/spec/helpers/session-mocker.js @@ -0,0 +1,43 @@ +/*** +* Test Mocker for Session.js +* Author: Iain, CodeJoust 2012. MIT License +***/ + +function parse_url(url){ + var d = document.createElement('a'); + d.href = url; d.raw = url; + return d; +} + +function create_mock_doc(){ + var doc = { + get cookie(){ return this.cookiejar.getCookies(cookiejar.CookieAccessInfo()).toString(); }, + set cookie(cookie_in){ this.cookiejar.setCookie(cookie_in); return cookie_in; }, + reset_jar: function(){ this.cookiejar = new cookiejar.CookieJar(); }, + cookiejar: new cookiejar.CookieJar(), + referrer: 'http://codejoust.com/asdf', + cleanup_cookies: function(){ this.cookiejar = new cookiejar.CookieJar(); } + }; + doc.createElement = function(a){ return document.createElement(a) }; + doc.getElementsByTagName = function(nm){ return document.getElementsByTagName(nm) }; + return doc; +} + +function create_mock(sess_obj){ + if (!sess_obj){ + var sess_obj = {options: { gapi_location: false }}; + } + var mock = { + sess: null // sets initial state + , win: {innerWidth: 200, innerHeight: 200, location: window.location, session: sess_obj} + , nav: navigator + , get_cookie_obj: function(cookie_name){ + return JSON.parse(unescape(this.doc.cookiejar.getCookie(cookie_name, cookiejar.CookieAccessInfo()).value)); + }, doc: create_mock_doc() + , run_sess: function(){ + mock.win.session = sess_obj; + sess = exports.session(mock.win, mock.doc, mock.nav) + return sess; + }}; + return mock; +} diff --git a/test/spec/jasmine/MIT.LICENSE b/test/spec/jasmine/MIT.LICENSE new file mode 100644 index 0000000..7c435ba --- /dev/null +++ b/test/spec/jasmine/MIT.LICENSE @@ -0,0 +1,20 @@ +Copyright (c) 2008-2011 Pivotal Labs + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/test/spec/jasmine/jasmine-html.js b/test/spec/jasmine/jasmine-html.js new file mode 100644 index 0000000..7383401 --- /dev/null +++ b/test/spec/jasmine/jasmine-html.js @@ -0,0 +1,190 @@ +jasmine.TrivialReporter = function(doc) { + this.document = doc || document; + this.suiteDivs = {}; + this.logRunningSpecs = false; +}; + +jasmine.TrivialReporter.prototype.createDom = function(type, attrs, childrenVarArgs) { + var el = document.createElement(type); + + for (var i = 2; i < arguments.length; i++) { + var child = arguments[i]; + + if (typeof child === 'string') { + el.appendChild(document.createTextNode(child)); + } else { + if (child) { el.appendChild(child); } + } + } + + for (var attr in attrs) { + if (attr == "className") { + el[attr] = attrs[attr]; + } else { + el.setAttribute(attr, attrs[attr]); + } + } + + return el; +}; + +jasmine.TrivialReporter.prototype.reportRunnerStarting = function(runner) { + var showPassed, showSkipped; + + this.outerDiv = this.createDom('div', { className: 'jasmine_reporter' }, + this.createDom('div', { className: 'banner' }, + this.createDom('div', { className: 'logo' }, + this.createDom('span', { className: 'title' }, "Jasmine"), + this.createDom('span', { className: 'version' }, runner.env.versionString())), + this.createDom('div', { className: 'options' }, + "Show ", + showPassed = this.createDom('input', { id: "__jasmine_TrivialReporter_showPassed__", type: 'checkbox' }), + this.createDom('label', { "for": "__jasmine_TrivialReporter_showPassed__" }, " passed "), + showSkipped = this.createDom('input', { id: "__jasmine_TrivialReporter_showSkipped__", type: 'checkbox' }), + this.createDom('label', { "for": "__jasmine_TrivialReporter_showSkipped__" }, " skipped") + ) + ), + + this.runnerDiv = this.createDom('div', { className: 'runner running' }, + this.createDom('a', { className: 'run_spec', href: '?' }, "run all"), + this.runnerMessageSpan = this.createDom('span', {}, "Running..."), + this.finishedAtSpan = this.createDom('span', { className: 'finished-at' }, "")) + ); + + this.document.body.appendChild(this.outerDiv); + + var suites = runner.suites(); + for (var i = 0; i < suites.length; i++) { + var suite = suites[i]; + var suiteDiv = this.createDom('div', { className: 'suite' }, + this.createDom('a', { className: 'run_spec', href: '?spec=' + encodeURIComponent(suite.getFullName()) }, "run"), + this.createDom('a', { className: 'description', href: '?spec=' + encodeURIComponent(suite.getFullName()) }, suite.description)); + this.suiteDivs[suite.id] = suiteDiv; + var parentDiv = this.outerDiv; + if (suite.parentSuite) { + parentDiv = this.suiteDivs[suite.parentSuite.id]; + } + parentDiv.appendChild(suiteDiv); + } + + this.startedAt = new Date(); + + var self = this; + showPassed.onclick = function(evt) { + if (showPassed.checked) { + self.outerDiv.className += ' show-passed'; + } else { + self.outerDiv.className = self.outerDiv.className.replace(/ show-passed/, ''); + } + }; + + showSkipped.onclick = function(evt) { + if (showSkipped.checked) { + self.outerDiv.className += ' show-skipped'; + } else { + self.outerDiv.className = self.outerDiv.className.replace(/ show-skipped/, ''); + } + }; +}; + +jasmine.TrivialReporter.prototype.reportRunnerResults = function(runner) { + var results = runner.results(); + var className = (results.failedCount > 0) ? "runner failed" : "runner passed"; + this.runnerDiv.setAttribute("class", className); + //do it twice for IE + this.runnerDiv.setAttribute("className", className); + var specs = runner.specs(); + var specCount = 0; + for (var i = 0; i < specs.length; i++) { + if (this.specFilter(specs[i])) { + specCount++; + } + } + var message = "" + specCount + " spec" + (specCount == 1 ? "" : "s" ) + ", " + results.failedCount + " failure" + ((results.failedCount == 1) ? "" : "s"); + message += " in " + ((new Date().getTime() - this.startedAt.getTime()) / 1000) + "s"; + this.runnerMessageSpan.replaceChild(this.createDom('a', { className: 'description', href: '?'}, message), this.runnerMessageSpan.firstChild); + + this.finishedAtSpan.appendChild(document.createTextNode("Finished at " + new Date().toString())); +}; + +jasmine.TrivialReporter.prototype.reportSuiteResults = function(suite) { + var results = suite.results(); + var status = results.passed() ? 'passed' : 'failed'; + if (results.totalCount === 0) { // todo: change this to check results.skipped + status = 'skipped'; + } + this.suiteDivs[suite.id].className += " " + status; +}; + +jasmine.TrivialReporter.prototype.reportSpecStarting = function(spec) { + if (this.logRunningSpecs) { + this.log('>> Jasmine Running ' + spec.suite.description + ' ' + spec.description + '...'); + } +}; + +jasmine.TrivialReporter.prototype.reportSpecResults = function(spec) { + var results = spec.results(); + var status = results.passed() ? 'passed' : 'failed'; + if (results.skipped) { + status = 'skipped'; + } + var specDiv = this.createDom('div', { className: 'spec ' + status }, + this.createDom('a', { className: 'run_spec', href: '?spec=' + encodeURIComponent(spec.getFullName()) }, "run"), + this.createDom('a', { + className: 'description', + href: '?spec=' + encodeURIComponent(spec.getFullName()), + title: spec.getFullName() + }, spec.description)); + + + var resultItems = results.getItems(); + var messagesDiv = this.createDom('div', { className: 'messages' }); + for (var i = 0; i < resultItems.length; i++) { + var result = resultItems[i]; + + if (result.type == 'log') { + messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage log'}, result.toString())); + } else if (result.type == 'expect' && result.passed && !result.passed()) { + messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage fail'}, result.message)); + + if (result.trace.stack) { + messagesDiv.appendChild(this.createDom('div', {className: 'stackTrace'}, result.trace.stack)); + } + } + } + + if (messagesDiv.childNodes.length > 0) { + specDiv.appendChild(messagesDiv); + } + + this.suiteDivs[spec.suite.id].appendChild(specDiv); +}; + +jasmine.TrivialReporter.prototype.log = function() { + var console = jasmine.getGlobal().console; + if (console && console.log) { + if (console.log.apply) { + console.log.apply(console, arguments); + } else { + console.log(arguments); // ie fix: console.log.apply doesn't exist on ie + } + } +}; + +jasmine.TrivialReporter.prototype.getLocation = function() { + return this.document.location; +}; + +jasmine.TrivialReporter.prototype.specFilter = function(spec) { + var paramMap = {}; + var params = this.getLocation().search.substring(1).split('&'); + for (var i = 0; i < params.length; i++) { + var p = params[i].split('='); + paramMap[decodeURIComponent(p[0])] = decodeURIComponent(p[1]); + } + + if (!paramMap.spec) { + return true; + } + return spec.getFullName().indexOf(paramMap.spec) === 0; +}; diff --git a/test/spec/jasmine/jasmine.css b/test/spec/jasmine/jasmine.css new file mode 100644 index 0000000..6583fe7 --- /dev/null +++ b/test/spec/jasmine/jasmine.css @@ -0,0 +1,166 @@ +body { + font-family: "Helvetica Neue Light", "Lucida Grande", "Calibri", "Arial", sans-serif; +} + + +.jasmine_reporter a:visited, .jasmine_reporter a { + color: #303; +} + +.jasmine_reporter a:hover, .jasmine_reporter a:active { + color: blue; +} + +.run_spec { + float:right; + padding-right: 5px; + font-size: .8em; + text-decoration: none; +} + +.jasmine_reporter { + margin: 0 5px; +} + +.banner { + color: #303; + background-color: #fef; + padding: 5px; +} + +.logo { + float: left; + font-size: 1.1em; + padding-left: 5px; +} + +.logo .version { + font-size: .6em; + padding-left: 1em; +} + +.runner.running { + background-color: yellow; +} + + +.options { + text-align: right; + font-size: .8em; +} + + + + +.suite { + border: 1px outset gray; + margin: 5px 0; + padding-left: 1em; +} + +.suite .suite { + margin: 5px; +} + +.suite.passed { + background-color: #dfd; +} + +.suite.failed { + background-color: #fdd; +} + +.spec { + margin: 5px; + padding-left: 1em; + clear: both; +} + +.spec.failed, .spec.passed, .spec.skipped { + padding-bottom: 5px; + border: 1px solid gray; +} + +.spec.failed { + background-color: #fbb; + border-color: red; +} + +.spec.passed { + background-color: #bfb; + border-color: green; +} + +.spec.skipped { + background-color: #bbb; +} + +.messages { + border-left: 1px dashed gray; + padding-left: 1em; + padding-right: 1em; +} + +.passed { + background-color: #cfc; + display: none; +} + +.failed { + background-color: #fbb; +} + +.skipped { + color: #777; + background-color: #eee; + display: none; +} + + +/*.resultMessage {*/ + /*white-space: pre;*/ +/*}*/ + +.resultMessage span.result { + display: block; + line-height: 2em; + color: black; +} + +.resultMessage .mismatch { + color: black; +} + +.stackTrace { + white-space: pre; + font-size: .8em; + margin-left: 10px; + max-height: 5em; + overflow: auto; + border: 1px inset red; + padding: 1em; + background: #eef; +} + +.finished-at { + padding-left: 1em; + font-size: .6em; +} + +.show-passed .passed, +.show-skipped .skipped { + display: block; +} + + +#jasmine_content { + position:fixed; + right: 100%; +} + +.runner { + border: 1px solid gray; + display: block; + margin: 5px 0; + padding: 2px 0 2px 10px; +} diff --git a/test/spec/jasmine/jasmine.js b/test/spec/jasmine/jasmine.js new file mode 100644 index 0000000..b058ebd --- /dev/null +++ b/test/spec/jasmine/jasmine.js @@ -0,0 +1,2471 @@ +var isCommonJS = typeof window == "undefined"; + +/** + * Top level namespace for Jasmine, a lightweight JavaScript BDD/spec/testing framework. + * + * @namespace + */ +var jasmine = {}; +if (isCommonJS) exports.jasmine = jasmine; +/** + * @private + */ +jasmine.unimplementedMethod_ = function() { + throw new Error("unimplemented method"); +}; + +/** + * Use jasmine.undefined instead of undefined, since undefined is just + * a plain old variable and may be redefined by somebody else. + * + * @private + */ +jasmine.undefined = jasmine.___undefined___; + +/** + * Show diagnostic messages in the console if set to true + * + */ +jasmine.VERBOSE = false; + +/** + * Default interval in milliseconds for event loop yields (e.g. to allow network activity or to refresh the screen with the HTML-based runner). Small values here may result in slow test running. Zero means no updates until all tests have completed. + * + */ +jasmine.DEFAULT_UPDATE_INTERVAL = 250; + +/** + * Default timeout interval in milliseconds for waitsFor() blocks. + */ +jasmine.DEFAULT_TIMEOUT_INTERVAL = 5000; + +jasmine.getGlobal = function() { + function getGlobal() { + return this; + } + + return getGlobal(); +}; + +/** + * Allows for bound functions to be compared. Internal use only. + * + * @ignore + * @private + * @param base {Object} bound 'this' for the function + * @param name {Function} function to find + */ +jasmine.bindOriginal_ = function(base, name) { + var original = base[name]; + if (original.apply) { + return function() { + return original.apply(base, arguments); + }; + } else { + // IE support + return jasmine.getGlobal()[name]; + } +}; + +jasmine.setTimeout = jasmine.bindOriginal_(jasmine.getGlobal(), 'setTimeout'); +jasmine.clearTimeout = jasmine.bindOriginal_(jasmine.getGlobal(), 'clearTimeout'); +jasmine.setInterval = jasmine.bindOriginal_(jasmine.getGlobal(), 'setInterval'); +jasmine.clearInterval = jasmine.bindOriginal_(jasmine.getGlobal(), 'clearInterval'); + +jasmine.MessageResult = function(values) { + this.type = 'log'; + this.values = values; + this.trace = new Error(); // todo: test better +}; + +jasmine.MessageResult.prototype.toString = function() { + var text = ""; + for (var i = 0; i < this.values.length; i++) { + if (i > 0) text += " "; + if (jasmine.isString_(this.values[i])) { + text += this.values[i]; + } else { + text += jasmine.pp(this.values[i]); + } + } + return text; +}; + +jasmine.ExpectationResult = function(params) { + this.type = 'expect'; + this.matcherName = params.matcherName; + this.passed_ = params.passed; + this.expected = params.expected; + this.actual = params.actual; + this.message = this.passed_ ? 'Passed.' : params.message; + + var trace = (params.trace || new Error(this.message)); + this.trace = this.passed_ ? '' : trace; +}; + +jasmine.ExpectationResult.prototype.toString = function () { + return this.message; +}; + +jasmine.ExpectationResult.prototype.passed = function () { + return this.passed_; +}; + +/** + * Getter for the Jasmine environment. Ensures one gets created + */ +jasmine.getEnv = function() { + var env = jasmine.currentEnv_ = jasmine.currentEnv_ || new jasmine.Env(); + return env; +}; + +/** + * @ignore + * @private + * @param value + * @returns {Boolean} + */ +jasmine.isArray_ = function(value) { + return jasmine.isA_("Array", value); +}; + +/** + * @ignore + * @private + * @param value + * @returns {Boolean} + */ +jasmine.isString_ = function(value) { + return jasmine.isA_("String", value); +}; + +/** + * @ignore + * @private + * @param value + * @returns {Boolean} + */ +jasmine.isNumber_ = function(value) { + return jasmine.isA_("Number", value); +}; + +/** + * @ignore + * @private + * @param {String} typeName + * @param value + * @returns {Boolean} + */ +jasmine.isA_ = function(typeName, value) { + return Object.prototype.toString.apply(value) === '[object ' + typeName + ']'; +}; + +/** + * Pretty printer for expecations. Takes any object and turns it into a human-readable string. + * + * @param value {Object} an object to be outputted + * @returns {String} + */ +jasmine.pp = function(value) { + var stringPrettyPrinter = new jasmine.StringPrettyPrinter(); + stringPrettyPrinter.format(value); + return stringPrettyPrinter.string; +}; + +/** + * Returns true if the object is a DOM Node. + * + * @param {Object} obj object to check + * @returns {Boolean} + */ +jasmine.isDomNode = function(obj) { + return obj.nodeType > 0; +}; + +/** + * Returns a matchable 'generic' object of the class type. For use in expecations of type when values don't matter. + * + * @example + * // don't care about which function is passed in, as long as it's a function + * expect(mySpy).toHaveBeenCalledWith(jasmine.any(Function)); + * + * @param {Class} clazz + * @returns matchable object of the type clazz + */ +jasmine.any = function(clazz) { + return new jasmine.Matchers.Any(clazz); +}; + +/** + * Jasmine Spies are test doubles that can act as stubs, spies, fakes or when used in an expecation, mocks. + * + * Spies should be created in test setup, before expectations. They can then be checked, using the standard Jasmine + * expectation syntax. Spies can be checked if they were called or not and what the calling params were. + * + * A Spy has the following fields: wasCalled, callCount, mostRecentCall, and argsForCall (see docs). + * + * Spies are torn down at the end of every spec. + * + * Note: Do not call new jasmine.Spy() directly - a spy must be created using spyOn, jasmine.createSpy or jasmine.createSpyObj. + * + * @example + * // a stub + * var myStub = jasmine.createSpy('myStub'); // can be used anywhere + * + * // spy example + * var foo = { + * not: function(bool) { return !bool; } + * } + * + * // actual foo.not will not be called, execution stops + * spyOn(foo, 'not'); + + // foo.not spied upon, execution will continue to implementation + * spyOn(foo, 'not').andCallThrough(); + * + * // fake example + * var foo = { + * not: function(bool) { return !bool; } + * } + * + * // foo.not(val) will return val + * spyOn(foo, 'not').andCallFake(function(value) {return value;}); + * + * // mock example + * foo.not(7 == 7); + * expect(foo.not).toHaveBeenCalled(); + * expect(foo.not).toHaveBeenCalledWith(true); + * + * @constructor + * @see spyOn, jasmine.createSpy, jasmine.createSpyObj + * @param {String} name + */ +jasmine.Spy = function(name) { + /** + * The name of the spy, if provided. + */ + this.identity = name || 'unknown'; + /** + * Is this Object a spy? + */ + this.isSpy = true; + /** + * The actual function this spy stubs. + */ + this.plan = function() { + }; + /** + * Tracking of the most recent call to the spy. + * @example + * var mySpy = jasmine.createSpy('foo'); + * mySpy(1, 2); + * mySpy.mostRecentCall.args = [1, 2]; + */ + this.mostRecentCall = {}; + + /** + * Holds arguments for each call to the spy, indexed by call count + * @example + * var mySpy = jasmine.createSpy('foo'); + * mySpy(1, 2); + * mySpy(7, 8); + * mySpy.mostRecentCall.args = [7, 8]; + * mySpy.argsForCall[0] = [1, 2]; + * mySpy.argsForCall[1] = [7, 8]; + */ + this.argsForCall = []; + this.calls = []; +}; + +/** + * Tells a spy to call through to the actual implemenatation. + * + * @example + * var foo = { + * bar: function() { // do some stuff } + * } + * + * // defining a spy on an existing property: foo.bar + * spyOn(foo, 'bar').andCallThrough(); + */ +jasmine.Spy.prototype.andCallThrough = function() { + this.plan = this.originalValue; + return this; +}; + +/** + * For setting the return value of a spy. + * + * @example + * // defining a spy from scratch: foo() returns 'baz' + * var foo = jasmine.createSpy('spy on foo').andReturn('baz'); + * + * // defining a spy on an existing property: foo.bar() returns 'baz' + * spyOn(foo, 'bar').andReturn('baz'); + * + * @param {Object} value + */ +jasmine.Spy.prototype.andReturn = function(value) { + this.plan = function() { + return value; + }; + return this; +}; + +/** + * For throwing an exception when a spy is called. + * + * @example + * // defining a spy from scratch: foo() throws an exception w/ message 'ouch' + * var foo = jasmine.createSpy('spy on foo').andThrow('baz'); + * + * // defining a spy on an existing property: foo.bar() throws an exception w/ message 'ouch' + * spyOn(foo, 'bar').andThrow('baz'); + * + * @param {String} exceptionMsg + */ +jasmine.Spy.prototype.andThrow = function(exceptionMsg) { + this.plan = function() { + throw exceptionMsg; + }; + return this; +}; + +/** + * Calls an alternate implementation when a spy is called. + * + * @example + * var baz = function() { + * // do some stuff, return something + * } + * // defining a spy from scratch: foo() calls the function baz + * var foo = jasmine.createSpy('spy on foo').andCall(baz); + * + * // defining a spy on an existing property: foo.bar() calls an anonymnous function + * spyOn(foo, 'bar').andCall(function() { return 'baz';} ); + * + * @param {Function} fakeFunc + */ +jasmine.Spy.prototype.andCallFake = function(fakeFunc) { + this.plan = fakeFunc; + return this; +}; + +/** + * Resets all of a spy's the tracking variables so that it can be used again. + * + * @example + * spyOn(foo, 'bar'); + * + * foo.bar(); + * + * expect(foo.bar.callCount).toEqual(1); + * + * foo.bar.reset(); + * + * expect(foo.bar.callCount).toEqual(0); + */ +jasmine.Spy.prototype.reset = function() { + this.wasCalled = false; + this.callCount = 0; + this.argsForCall = []; + this.calls = []; + this.mostRecentCall = {}; +}; + +jasmine.createSpy = function(name) { + + var spyObj = function() { + spyObj.wasCalled = true; + spyObj.callCount++; + var args = jasmine.util.argsToArray(arguments); + spyObj.mostRecentCall.object = this; + spyObj.mostRecentCall.args = args; + spyObj.argsForCall.push(args); + spyObj.calls.push({object: this, args: args}); + return spyObj.plan.apply(this, arguments); + }; + + var spy = new jasmine.Spy(name); + + for (var prop in spy) { + spyObj[prop] = spy[prop]; + } + + spyObj.reset(); + + return spyObj; +}; + +/** + * Determines whether an object is a spy. + * + * @param {jasmine.Spy|Object} putativeSpy + * @returns {Boolean} + */ +jasmine.isSpy = function(putativeSpy) { + return putativeSpy && putativeSpy.isSpy; +}; + +/** + * Creates a more complicated spy: an Object that has every property a function that is a spy. Used for stubbing something + * large in one call. + * + * @param {String} baseName name of spy class + * @param {Array} methodNames array of names of methods to make spies + */ +jasmine.createSpyObj = function(baseName, methodNames) { + if (!jasmine.isArray_(methodNames) || methodNames.length === 0) { + throw new Error('createSpyObj requires a non-empty array of method names to create spies for'); + } + var obj = {}; + for (var i = 0; i < methodNames.length; i++) { + obj[methodNames[i]] = jasmine.createSpy(baseName + '.' + methodNames[i]); + } + return obj; +}; + +/** + * All parameters are pretty-printed and concatenated together, then written to the current spec's output. + * + * Be careful not to leave calls to jasmine.log in production code. + */ +jasmine.log = function() { + var spec = jasmine.getEnv().currentSpec; + spec.log.apply(spec, arguments); +}; + +/** + * Function that installs a spy on an existing object's method name. Used within a Spec to create a spy. + * + * @example + * // spy example + * var foo = { + * not: function(bool) { return !bool; } + * } + * spyOn(foo, 'not'); // actual foo.not will not be called, execution stops + * + * @see jasmine.createSpy + * @param obj + * @param methodName + * @returns a Jasmine spy that can be chained with all spy methods + */ +var spyOn = function(obj, methodName) { + return jasmine.getEnv().currentSpec.spyOn(obj, methodName); +}; +if (isCommonJS) exports.spyOn = spyOn; + +/** + * Creates a Jasmine spec that will be added to the current suite. + * + * // TODO: pending tests + * + * @example + * it('should be true', function() { + * expect(true).toEqual(true); + * }); + * + * @param {String} desc description of this specification + * @param {Function} func defines the preconditions and expectations of the spec + */ +var it = function(desc, func) { + return jasmine.getEnv().it(desc, func); +}; +if (isCommonJS) exports.it = it; + +/** + * Creates a disabled Jasmine spec. + * + * A convenience method that allows existing specs to be disabled temporarily during development. + * + * @param {String} desc description of this specification + * @param {Function} func defines the preconditions and expectations of the spec + */ +var xit = function(desc, func) { + return jasmine.getEnv().xit(desc, func); +}; +if (isCommonJS) exports.xit = xit; + +/** + * Starts a chain for a Jasmine expectation. + * + * It is passed an Object that is the actual value and should chain to one of the many + * jasmine.Matchers functions. + * + * @param {Object} actual Actual value to test against and expected value + */ +var expect = function(actual) { + return jasmine.getEnv().currentSpec.expect(actual); +}; +if (isCommonJS) exports.expect = expect; + +/** + * Defines part of a jasmine spec. Used in cominbination with waits or waitsFor in asynchrnous specs. + * + * @param {Function} func Function that defines part of a jasmine spec. + */ +var runs = function(func) { + jasmine.getEnv().currentSpec.runs(func); +}; +if (isCommonJS) exports.runs = runs; + +/** + * Waits a fixed time period before moving to the next block. + * + * @deprecated Use waitsFor() instead + * @param {Number} timeout milliseconds to wait + */ +var waits = function(timeout) { + jasmine.getEnv().currentSpec.waits(timeout); +}; +if (isCommonJS) exports.waits = waits; + +/** + * Waits for the latchFunction to return true before proceeding to the next block. + * + * @param {Function} latchFunction + * @param {String} optional_timeoutMessage + * @param {Number} optional_timeout + */ +var waitsFor = function(latchFunction, optional_timeoutMessage, optional_timeout) { + jasmine.getEnv().currentSpec.waitsFor.apply(jasmine.getEnv().currentSpec, arguments); +}; +if (isCommonJS) exports.waitsFor = waitsFor; + +/** + * A function that is called before each spec in a suite. + * + * Used for spec setup, including validating assumptions. + * + * @param {Function} beforeEachFunction + */ +var beforeEach = function(beforeEachFunction) { + jasmine.getEnv().beforeEach(beforeEachFunction); +}; +if (isCommonJS) exports.beforeEach = beforeEach; + +/** + * A function that is called after each spec in a suite. + * + * Used for restoring any state that is hijacked during spec execution. + * + * @param {Function} afterEachFunction + */ +var afterEach = function(afterEachFunction) { + jasmine.getEnv().afterEach(afterEachFunction); +}; +if (isCommonJS) exports.afterEach = afterEach; + +/** + * Defines a suite of specifications. + * + * Stores the description and all defined specs in the Jasmine environment as one suite of specs. Variables declared + * are accessible by calls to beforeEach, it, and afterEach. Describe blocks can be nested, allowing for specialization + * of setup in some tests. + * + * @example + * // TODO: a simple suite + * + * // TODO: a simple suite with a nested describe block + * + * @param {String} description A string, usually the class under test. + * @param {Function} specDefinitions function that defines several specs. + */ +var describe = function(description, specDefinitions) { + return jasmine.getEnv().describe(description, specDefinitions); +}; +if (isCommonJS) exports.describe = describe; + +/** + * Disables a suite of specifications. Used to disable some suites in a file, or files, temporarily during development. + * + * @param {String} description A string, usually the class under test. + * @param {Function} specDefinitions function that defines several specs. + */ +var xdescribe = function(description, specDefinitions) { + return jasmine.getEnv().xdescribe(description, specDefinitions); +}; +if (isCommonJS) exports.xdescribe = xdescribe; + + +// Provide the XMLHttpRequest class for IE 5.x-6.x: +jasmine.XmlHttpRequest = (typeof XMLHttpRequest == "undefined") ? function() { + function tryIt(f) { + try { + return f(); + } catch(e) { + } + return null; + } + + var xhr = tryIt(function() { + return new ActiveXObject("Msxml2.XMLHTTP.6.0"); + }) || + tryIt(function() { + return new ActiveXObject("Msxml2.XMLHTTP.3.0"); + }) || + tryIt(function() { + return new ActiveXObject("Msxml2.XMLHTTP"); + }) || + tryIt(function() { + return new ActiveXObject("Microsoft.XMLHTTP"); + }); + + if (!xhr) throw new Error("This browser does not support XMLHttpRequest."); + + return xhr; +} : XMLHttpRequest; +/** + * @namespace + */ +jasmine.util = {}; + +/** + * Declare that a child class inherit it's prototype from the parent class. + * + * @private + * @param {Function} childClass + * @param {Function} parentClass + */ +jasmine.util.inherit = function(childClass, parentClass) { + /** + * @private + */ + var subclass = function() { + }; + subclass.prototype = parentClass.prototype; + childClass.prototype = new subclass(); +}; + +jasmine.util.formatException = function(e) { + var lineNumber; + if (e.line) { + lineNumber = e.line; + } + else if (e.lineNumber) { + lineNumber = e.lineNumber; + } + + var file; + + if (e.sourceURL) { + file = e.sourceURL; + } + else if (e.fileName) { + file = e.fileName; + } + + var message = (e.name && e.message) ? (e.name + ': ' + e.message) : e.toString(); + + if (file && lineNumber) { + message += ' in ' + file + ' (line ' + lineNumber + ')'; + } + + return message; +}; + +jasmine.util.htmlEscape = function(str) { + if (!str) return str; + return str.replace(/&/g, '&') + .replace(//g, '>'); +}; + +jasmine.util.argsToArray = function(args) { + var arrayOfArgs = []; + for (var i = 0; i < args.length; i++) arrayOfArgs.push(args[i]); + return arrayOfArgs; +}; + +jasmine.util.extend = function(destination, source) { + for (var property in source) destination[property] = source[property]; + return destination; +}; + +/** + * Environment for Jasmine + * + * @constructor + */ +jasmine.Env = function() { + this.currentSpec = null; + this.currentSuite = null; + this.currentRunner_ = new jasmine.Runner(this); + + this.reporter = new jasmine.MultiReporter(); + + this.updateInterval = jasmine.DEFAULT_UPDATE_INTERVAL; + this.defaultTimeoutInterval = jasmine.DEFAULT_TIMEOUT_INTERVAL; + this.lastUpdate = 0; + this.specFilter = function() { + return true; + }; + + this.nextSpecId_ = 0; + this.nextSuiteId_ = 0; + this.equalityTesters_ = []; + + // wrap matchers + this.matchersClass = function() { + jasmine.Matchers.apply(this, arguments); + }; + jasmine.util.inherit(this.matchersClass, jasmine.Matchers); + + jasmine.Matchers.wrapInto_(jasmine.Matchers.prototype, this.matchersClass); +}; + + +jasmine.Env.prototype.setTimeout = jasmine.setTimeout; +jasmine.Env.prototype.clearTimeout = jasmine.clearTimeout; +jasmine.Env.prototype.setInterval = jasmine.setInterval; +jasmine.Env.prototype.clearInterval = jasmine.clearInterval; + +/** + * @returns an object containing jasmine version build info, if set. + */ +jasmine.Env.prototype.version = function () { + if (jasmine.version_) { + return jasmine.version_; + } else { + throw new Error('Version not set'); + } +}; + +/** + * @returns string containing jasmine version build info, if set. + */ +jasmine.Env.prototype.versionString = function() { + if (jasmine.version_) { + var version = this.version(); + return version.major + "." + version.minor + "." + version.build + " revision " + version.revision; + } else { + return "version unknown"; + } +}; + +/** + * @returns a sequential integer starting at 0 + */ +jasmine.Env.prototype.nextSpecId = function () { + return this.nextSpecId_++; +}; + +/** + * @returns a sequential integer starting at 0 + */ +jasmine.Env.prototype.nextSuiteId = function () { + return this.nextSuiteId_++; +}; + +/** + * Register a reporter to receive status updates from Jasmine. + * @param {jasmine.Reporter} reporter An object which will receive status updates. + */ +jasmine.Env.prototype.addReporter = function(reporter) { + this.reporter.addReporter(reporter); +}; + +jasmine.Env.prototype.execute = function() { + this.currentRunner_.execute(); +}; + +jasmine.Env.prototype.describe = function(description, specDefinitions) { + var suite = new jasmine.Suite(this, description, specDefinitions, this.currentSuite); + + var parentSuite = this.currentSuite; + if (parentSuite) { + parentSuite.add(suite); + } else { + this.currentRunner_.add(suite); + } + + this.currentSuite = suite; + + var declarationError = null; + try { + specDefinitions.call(suite); + } catch(e) { + declarationError = e; + } + + if (declarationError) { + this.it("encountered a declaration exception", function() { + throw declarationError; + }); + } + + this.currentSuite = parentSuite; + + return suite; +}; + +jasmine.Env.prototype.beforeEach = function(beforeEachFunction) { + if (this.currentSuite) { + this.currentSuite.beforeEach(beforeEachFunction); + } else { + this.currentRunner_.beforeEach(beforeEachFunction); + } +}; + +jasmine.Env.prototype.currentRunner = function () { + return this.currentRunner_; +}; + +jasmine.Env.prototype.afterEach = function(afterEachFunction) { + if (this.currentSuite) { + this.currentSuite.afterEach(afterEachFunction); + } else { + this.currentRunner_.afterEach(afterEachFunction); + } + +}; + +jasmine.Env.prototype.xdescribe = function(desc, specDefinitions) { + return { + execute: function() { + } + }; +}; + +jasmine.Env.prototype.it = function(description, func) { + var spec = new jasmine.Spec(this, this.currentSuite, description); + this.currentSuite.add(spec); + this.currentSpec = spec; + + if (func) { + spec.runs(func); + } + + return spec; +}; + +jasmine.Env.prototype.xit = function(desc, func) { + return { + id: this.nextSpecId(), + runs: function() { + } + }; +}; + +jasmine.Env.prototype.compareObjects_ = function(a, b, mismatchKeys, mismatchValues) { + if (a.__Jasmine_been_here_before__ === b && b.__Jasmine_been_here_before__ === a) { + return true; + } + + a.__Jasmine_been_here_before__ = b; + b.__Jasmine_been_here_before__ = a; + + var hasKey = function(obj, keyName) { + return obj !== null && obj[keyName] !== jasmine.undefined; + }; + + for (var property in b) { + if (!hasKey(a, property) && hasKey(b, property)) { + mismatchKeys.push("expected has key '" + property + "', but missing from actual."); + } + } + for (property in a) { + if (!hasKey(b, property) && hasKey(a, property)) { + mismatchKeys.push("expected missing key '" + property + "', but present in actual."); + } + } + for (property in b) { + if (property == '__Jasmine_been_here_before__') continue; + if (!this.equals_(a[property], b[property], mismatchKeys, mismatchValues)) { + mismatchValues.push("'" + property + "' was '" + (b[property] ? jasmine.util.htmlEscape(b[property].toString()) : b[property]) + "' in expected, but was '" + (a[property] ? jasmine.util.htmlEscape(a[property].toString()) : a[property]) + "' in actual."); + } + } + + if (jasmine.isArray_(a) && jasmine.isArray_(b) && a.length != b.length) { + mismatchValues.push("arrays were not the same length"); + } + + delete a.__Jasmine_been_here_before__; + delete b.__Jasmine_been_here_before__; + return (mismatchKeys.length === 0 && mismatchValues.length === 0); +}; + +jasmine.Env.prototype.equals_ = function(a, b, mismatchKeys, mismatchValues) { + mismatchKeys = mismatchKeys || []; + mismatchValues = mismatchValues || []; + + for (var i = 0; i < this.equalityTesters_.length; i++) { + var equalityTester = this.equalityTesters_[i]; + var result = equalityTester(a, b, this, mismatchKeys, mismatchValues); + if (result !== jasmine.undefined) return result; + } + + if (a === b) return true; + + if (a === jasmine.undefined || a === null || b === jasmine.undefined || b === null) { + return (a == jasmine.undefined && b == jasmine.undefined); + } + + if (jasmine.isDomNode(a) && jasmine.isDomNode(b)) { + return a === b; + } + + if (a instanceof Date && b instanceof Date) { + return a.getTime() == b.getTime(); + } + + if (a instanceof jasmine.Matchers.Any) { + return a.matches(b); + } + + if (b instanceof jasmine.Matchers.Any) { + return b.matches(a); + } + + if (jasmine.isString_(a) && jasmine.isString_(b)) { + return (a == b); + } + + if (jasmine.isNumber_(a) && jasmine.isNumber_(b)) { + return (a == b); + } + + if (typeof a === "object" && typeof b === "object") { + return this.compareObjects_(a, b, mismatchKeys, mismatchValues); + } + + //Straight check + return (a === b); +}; + +jasmine.Env.prototype.contains_ = function(haystack, needle) { + if (jasmine.isArray_(haystack)) { + for (var i = 0; i < haystack.length; i++) { + if (this.equals_(haystack[i], needle)) return true; + } + return false; + } + return haystack.indexOf(needle) >= 0; +}; + +jasmine.Env.prototype.addEqualityTester = function(equalityTester) { + this.equalityTesters_.push(equalityTester); +}; +/** No-op base class for Jasmine reporters. + * + * @constructor + */ +jasmine.Reporter = function() { +}; + +//noinspection JSUnusedLocalSymbols +jasmine.Reporter.prototype.reportRunnerStarting = function(runner) { +}; + +//noinspection JSUnusedLocalSymbols +jasmine.Reporter.prototype.reportRunnerResults = function(runner) { +}; + +//noinspection JSUnusedLocalSymbols +jasmine.Reporter.prototype.reportSuiteResults = function(suite) { +}; + +//noinspection JSUnusedLocalSymbols +jasmine.Reporter.prototype.reportSpecStarting = function(spec) { +}; + +//noinspection JSUnusedLocalSymbols +jasmine.Reporter.prototype.reportSpecResults = function(spec) { +}; + +//noinspection JSUnusedLocalSymbols +jasmine.Reporter.prototype.log = function(str) { +}; + +/** + * Blocks are functions with executable code that make up a spec. + * + * @constructor + * @param {jasmine.Env} env + * @param {Function} func + * @param {jasmine.Spec} spec + */ +jasmine.Block = function(env, func, spec) { + this.env = env; + this.func = func; + this.spec = spec; +}; + +jasmine.Block.prototype.execute = function(onComplete) { + try { + this.func.apply(this.spec); + } catch (e) { + this.spec.fail(e); + } + onComplete(); +}; +/** JavaScript API reporter. + * + * @constructor + */ +jasmine.JsApiReporter = function() { + this.started = false; + this.finished = false; + this.suites_ = []; + this.results_ = {}; +}; + +jasmine.JsApiReporter.prototype.reportRunnerStarting = function(runner) { + this.started = true; + var suites = runner.topLevelSuites(); + for (var i = 0; i < suites.length; i++) { + var suite = suites[i]; + this.suites_.push(this.summarize_(suite)); + } +}; + +jasmine.JsApiReporter.prototype.suites = function() { + return this.suites_; +}; + +jasmine.JsApiReporter.prototype.summarize_ = function(suiteOrSpec) { + var isSuite = suiteOrSpec instanceof jasmine.Suite; + var summary = { + id: suiteOrSpec.id, + name: suiteOrSpec.description, + type: isSuite ? 'suite' : 'spec', + children: [] + }; + + if (isSuite) { + var children = suiteOrSpec.children(); + for (var i = 0; i < children.length; i++) { + summary.children.push(this.summarize_(children[i])); + } + } + return summary; +}; + +jasmine.JsApiReporter.prototype.results = function() { + return this.results_; +}; + +jasmine.JsApiReporter.prototype.resultsForSpec = function(specId) { + return this.results_[specId]; +}; + +//noinspection JSUnusedLocalSymbols +jasmine.JsApiReporter.prototype.reportRunnerResults = function(runner) { + this.finished = true; +}; + +//noinspection JSUnusedLocalSymbols +jasmine.JsApiReporter.prototype.reportSuiteResults = function(suite) { +}; + +//noinspection JSUnusedLocalSymbols +jasmine.JsApiReporter.prototype.reportSpecResults = function(spec) { + this.results_[spec.id] = { + messages: spec.results().getItems(), + result: spec.results().failedCount > 0 ? "failed" : "passed" + }; +}; + +//noinspection JSUnusedLocalSymbols +jasmine.JsApiReporter.prototype.log = function(str) { +}; + +jasmine.JsApiReporter.prototype.resultsForSpecs = function(specIds){ + var results = {}; + for (var i = 0; i < specIds.length; i++) { + var specId = specIds[i]; + results[specId] = this.summarizeResult_(this.results_[specId]); + } + return results; +}; + +jasmine.JsApiReporter.prototype.summarizeResult_ = function(result){ + var summaryMessages = []; + var messagesLength = result.messages.length; + for (var messageIndex = 0; messageIndex < messagesLength; messageIndex++) { + var resultMessage = result.messages[messageIndex]; + summaryMessages.push({ + text: resultMessage.type == 'log' ? resultMessage.toString() : jasmine.undefined, + passed: resultMessage.passed ? resultMessage.passed() : true, + type: resultMessage.type, + message: resultMessage.message, + trace: { + stack: resultMessage.passed && !resultMessage.passed() ? resultMessage.trace.stack : jasmine.undefined + } + }); + } + + return { + result : result.result, + messages : summaryMessages + }; +}; + +/** + * @constructor + * @param {jasmine.Env} env + * @param actual + * @param {jasmine.Spec} spec + */ +jasmine.Matchers = function(env, actual, spec, opt_isNot) { + this.env = env; + this.actual = actual; + this.spec = spec; + this.isNot = opt_isNot || false; + this.reportWasCalled_ = false; +}; + +// todo: @deprecated as of Jasmine 0.11, remove soon [xw] +jasmine.Matchers.pp = function(str) { + throw new Error("jasmine.Matchers.pp() is no longer supported, please use jasmine.pp() instead!"); +}; + +// todo: @deprecated Deprecated as of Jasmine 0.10. Rewrite your custom matchers to return true or false. [xw] +jasmine.Matchers.prototype.report = function(result, failing_message, details) { + throw new Error("As of jasmine 0.11, custom matchers must be implemented differently -- please see jasmine docs"); +}; + +jasmine.Matchers.wrapInto_ = function(prototype, matchersClass) { + for (var methodName in prototype) { + if (methodName == 'report') continue; + var orig = prototype[methodName]; + matchersClass.prototype[methodName] = jasmine.Matchers.matcherFn_(methodName, orig); + } +}; + +jasmine.Matchers.matcherFn_ = function(matcherName, matcherFunction) { + return function() { + var matcherArgs = jasmine.util.argsToArray(arguments); + var result = matcherFunction.apply(this, arguments); + + if (this.isNot) { + result = !result; + } + + if (this.reportWasCalled_) return result; + + var message; + if (!result) { + if (this.message) { + message = this.message.apply(this, arguments); + if (jasmine.isArray_(message)) { + message = message[this.isNot ? 1 : 0]; + } + } else { + var englishyPredicate = matcherName.replace(/[A-Z]/g, function(s) { return ' ' + s.toLowerCase(); }); + message = "Expected " + jasmine.pp(this.actual) + (this.isNot ? " not " : " ") + englishyPredicate; + if (matcherArgs.length > 0) { + for (var i = 0; i < matcherArgs.length; i++) { + if (i > 0) message += ","; + message += " " + jasmine.pp(matcherArgs[i]); + } + } + message += "."; + } + } + var expectationResult = new jasmine.ExpectationResult({ + matcherName: matcherName, + passed: result, + expected: matcherArgs.length > 1 ? matcherArgs : matcherArgs[0], + actual: this.actual, + message: message + }); + this.spec.addMatcherResult(expectationResult); + return jasmine.undefined; + }; +}; + + + + +/** + * toBe: compares the actual to the expected using === + * @param expected + */ +jasmine.Matchers.prototype.toBe = function(expected) { + return this.actual === expected; +}; + +/** + * toNotBe: compares the actual to the expected using !== + * @param expected + * @deprecated as of 1.0. Use not.toBe() instead. + */ +jasmine.Matchers.prototype.toNotBe = function(expected) { + return this.actual !== expected; +}; + +/** + * toEqual: compares the actual to the expected using common sense equality. Handles Objects, Arrays, etc. + * + * @param expected + */ +jasmine.Matchers.prototype.toEqual = function(expected) { + return this.env.equals_(this.actual, expected); +}; + +/** + * toNotEqual: compares the actual to the expected using the ! of jasmine.Matchers.toEqual + * @param expected + * @deprecated as of 1.0. Use not.toNotEqual() instead. + */ +jasmine.Matchers.prototype.toNotEqual = function(expected) { + return !this.env.equals_(this.actual, expected); +}; + +/** + * Matcher that compares the actual to the expected using a regular expression. Constructs a RegExp, so takes + * a pattern or a String. + * + * @param expected + */ +jasmine.Matchers.prototype.toMatch = function(expected) { + return new RegExp(expected).test(this.actual); +}; + +/** + * Matcher that compares the actual to the expected using the boolean inverse of jasmine.Matchers.toMatch + * @param expected + * @deprecated as of 1.0. Use not.toMatch() instead. + */ +jasmine.Matchers.prototype.toNotMatch = function(expected) { + return !(new RegExp(expected).test(this.actual)); +}; + +/** + * Matcher that compares the actual to jasmine.undefined. + */ +jasmine.Matchers.prototype.toBeDefined = function() { + return (this.actual !== jasmine.undefined); +}; + +/** + * Matcher that compares the actual to jasmine.undefined. + */ +jasmine.Matchers.prototype.toBeUndefined = function() { + return (this.actual === jasmine.undefined); +}; + +/** + * Matcher that compares the actual to null. + */ +jasmine.Matchers.prototype.toBeNull = function() { + return (this.actual === null); +}; + +/** + * Matcher that boolean not-nots the actual. + */ +jasmine.Matchers.prototype.toBeTruthy = function() { + return !!this.actual; +}; + + +/** + * Matcher that boolean nots the actual. + */ +jasmine.Matchers.prototype.toBeFalsy = function() { + return !this.actual; +}; + + +/** + * Matcher that checks to see if the actual, a Jasmine spy, was called. + */ +jasmine.Matchers.prototype.toHaveBeenCalled = function() { + if (arguments.length > 0) { + throw new Error('toHaveBeenCalled does not take arguments, use toHaveBeenCalledWith'); + } + + if (!jasmine.isSpy(this.actual)) { + throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.'); + } + + this.message = function() { + return [ + "Expected spy " + this.actual.identity + " to have been called.", + "Expected spy " + this.actual.identity + " not to have been called." + ]; + }; + + return this.actual.wasCalled; +}; + +/** @deprecated Use expect(xxx).toHaveBeenCalled() instead */ +jasmine.Matchers.prototype.wasCalled = jasmine.Matchers.prototype.toHaveBeenCalled; + +/** + * Matcher that checks to see if the actual, a Jasmine spy, was not called. + * + * @deprecated Use expect(xxx).not.toHaveBeenCalled() instead + */ +jasmine.Matchers.prototype.wasNotCalled = function() { + if (arguments.length > 0) { + throw new Error('wasNotCalled does not take arguments'); + } + + if (!jasmine.isSpy(this.actual)) { + throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.'); + } + + this.message = function() { + return [ + "Expected spy " + this.actual.identity + " to not have been called.", + "Expected spy " + this.actual.identity + " to have been called." + ]; + }; + + return !this.actual.wasCalled; +}; + +/** + * Matcher that checks to see if the actual, a Jasmine spy, was called with a set of parameters. + * + * @example + * + */ +jasmine.Matchers.prototype.toHaveBeenCalledWith = function() { + var expectedArgs = jasmine.util.argsToArray(arguments); + if (!jasmine.isSpy(this.actual)) { + throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.'); + } + this.message = function() { + if (this.actual.callCount === 0) { + // todo: what should the failure message for .not.toHaveBeenCalledWith() be? is this right? test better. [xw] + return [ + "Expected spy " + this.actual.identity + " to have been called with " + jasmine.pp(expectedArgs) + " but it was never called.", + "Expected spy " + this.actual.identity + " not to have been called with " + jasmine.pp(expectedArgs) + " but it was." + ]; + } else { + return [ + "Expected spy " + this.actual.identity + " to have been called with " + jasmine.pp(expectedArgs) + " but was called with " + jasmine.pp(this.actual.argsForCall), + "Expected spy " + this.actual.identity + " not to have been called with " + jasmine.pp(expectedArgs) + " but was called with " + jasmine.pp(this.actual.argsForCall) + ]; + } + }; + + return this.env.contains_(this.actual.argsForCall, expectedArgs); +}; + +/** @deprecated Use expect(xxx).toHaveBeenCalledWith() instead */ +jasmine.Matchers.prototype.wasCalledWith = jasmine.Matchers.prototype.toHaveBeenCalledWith; + +/** @deprecated Use expect(xxx).not.toHaveBeenCalledWith() instead */ +jasmine.Matchers.prototype.wasNotCalledWith = function() { + var expectedArgs = jasmine.util.argsToArray(arguments); + if (!jasmine.isSpy(this.actual)) { + throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.'); + } + + this.message = function() { + return [ + "Expected spy not to have been called with " + jasmine.pp(expectedArgs) + " but it was", + "Expected spy to have been called with " + jasmine.pp(expectedArgs) + " but it was" + ]; + }; + + return !this.env.contains_(this.actual.argsForCall, expectedArgs); +}; + +/** + * Matcher that checks that the expected item is an element in the actual Array. + * + * @param {Object} expected + */ +jasmine.Matchers.prototype.toContain = function(expected) { + return this.env.contains_(this.actual, expected); +}; + +/** + * Matcher that checks that the expected item is NOT an element in the actual Array. + * + * @param {Object} expected + * @deprecated as of 1.0. Use not.toNotContain() instead. + */ +jasmine.Matchers.prototype.toNotContain = function(expected) { + return !this.env.contains_(this.actual, expected); +}; + +jasmine.Matchers.prototype.toBeLessThan = function(expected) { + return this.actual < expected; +}; + +jasmine.Matchers.prototype.toBeGreaterThan = function(expected) { + return this.actual > expected; +}; + +/** + * Matcher that checks that the expected item is equal to the actual item + * up to a given level of decimal precision (default 2). + * + * @param {Number} expected + * @param {Number} precision + */ +jasmine.Matchers.prototype.toBeCloseTo = function(expected, precision) { + if (!(precision === 0)) { + precision = precision || 2; + } + var multiplier = Math.pow(10, precision); + var actual = Math.round(this.actual * multiplier); + expected = Math.round(expected * multiplier); + return expected == actual; +}; + +/** + * Matcher that checks that the expected exception was thrown by the actual. + * + * @param {String} expected + */ +jasmine.Matchers.prototype.toThrow = function(expected) { + var result = false; + var exception; + if (typeof this.actual != 'function') { + throw new Error('Actual is not a function'); + } + try { + this.actual(); + } catch (e) { + exception = e; + } + if (exception) { + result = (expected === jasmine.undefined || this.env.equals_(exception.message || exception, expected.message || expected)); + } + + var not = this.isNot ? "not " : ""; + + this.message = function() { + if (exception && (expected === jasmine.undefined || !this.env.equals_(exception.message || exception, expected.message || expected))) { + return ["Expected function " + not + "to throw", expected ? expected.message || expected : "an exception", ", but it threw", exception.message || exception].join(' '); + } else { + return "Expected function to throw an exception."; + } + }; + + return result; +}; + +jasmine.Matchers.Any = function(expectedClass) { + this.expectedClass = expectedClass; +}; + +jasmine.Matchers.Any.prototype.matches = function(other) { + if (this.expectedClass == String) { + return typeof other == 'string' || other instanceof String; + } + + if (this.expectedClass == Number) { + return typeof other == 'number' || other instanceof Number; + } + + if (this.expectedClass == Function) { + return typeof other == 'function' || other instanceof Function; + } + + if (this.expectedClass == Object) { + return typeof other == 'object'; + } + + return other instanceof this.expectedClass; +}; + +jasmine.Matchers.Any.prototype.toString = function() { + return ''; +}; + +/** + * @constructor + */ +jasmine.MultiReporter = function() { + this.subReporters_ = []; +}; +jasmine.util.inherit(jasmine.MultiReporter, jasmine.Reporter); + +jasmine.MultiReporter.prototype.addReporter = function(reporter) { + this.subReporters_.push(reporter); +}; + +(function() { + var functionNames = [ + "reportRunnerStarting", + "reportRunnerResults", + "reportSuiteResults", + "reportSpecStarting", + "reportSpecResults", + "log" + ]; + for (var i = 0; i < functionNames.length; i++) { + var functionName = functionNames[i]; + jasmine.MultiReporter.prototype[functionName] = (function(functionName) { + return function() { + for (var j = 0; j < this.subReporters_.length; j++) { + var subReporter = this.subReporters_[j]; + if (subReporter[functionName]) { + subReporter[functionName].apply(subReporter, arguments); + } + } + }; + })(functionName); + } +})(); +/** + * Holds results for a set of Jasmine spec. Allows for the results array to hold another jasmine.NestedResults + * + * @constructor + */ +jasmine.NestedResults = function() { + /** + * The total count of results + */ + this.totalCount = 0; + /** + * Number of passed results + */ + this.passedCount = 0; + /** + * Number of failed results + */ + this.failedCount = 0; + /** + * Was this suite/spec skipped? + */ + this.skipped = false; + /** + * @ignore + */ + this.items_ = []; +}; + +/** + * Roll up the result counts. + * + * @param result + */ +jasmine.NestedResults.prototype.rollupCounts = function(result) { + this.totalCount += result.totalCount; + this.passedCount += result.passedCount; + this.failedCount += result.failedCount; +}; + +/** + * Adds a log message. + * @param values Array of message parts which will be concatenated later. + */ +jasmine.NestedResults.prototype.log = function(values) { + this.items_.push(new jasmine.MessageResult(values)); +}; + +/** + * Getter for the results: message & results. + */ +jasmine.NestedResults.prototype.getItems = function() { + return this.items_; +}; + +/** + * Adds a result, tracking counts (total, passed, & failed) + * @param {jasmine.ExpectationResult|jasmine.NestedResults} result + */ +jasmine.NestedResults.prototype.addResult = function(result) { + if (result.type != 'log') { + if (result.items_) { + this.rollupCounts(result); + } else { + this.totalCount++; + if (result.passed()) { + this.passedCount++; + } else { + this.failedCount++; + } + } + } + this.items_.push(result); +}; + +/** + * @returns {Boolean} True if everything below passed + */ +jasmine.NestedResults.prototype.passed = function() { + return this.passedCount === this.totalCount; +}; +/** + * Base class for pretty printing for expectation results. + */ +jasmine.PrettyPrinter = function() { + this.ppNestLevel_ = 0; +}; + +/** + * Formats a value in a nice, human-readable string. + * + * @param value + */ +jasmine.PrettyPrinter.prototype.format = function(value) { + if (this.ppNestLevel_ > 40) { + throw new Error('jasmine.PrettyPrinter: format() nested too deeply!'); + } + + this.ppNestLevel_++; + try { + if (value === jasmine.undefined) { + this.emitScalar('undefined'); + } else if (value === null) { + this.emitScalar('null'); + } else if (value === jasmine.getGlobal()) { + this.emitScalar(''); + } else if (value instanceof jasmine.Matchers.Any) { + this.emitScalar(value.toString()); + } else if (typeof value === 'string') { + this.emitString(value); + } else if (jasmine.isSpy(value)) { + this.emitScalar("spy on " + value.identity); + } else if (value instanceof RegExp) { + this.emitScalar(value.toString()); + } else if (typeof value === 'function') { + this.emitScalar('Function'); + } else if (typeof value.nodeType === 'number') { + this.emitScalar('HTMLNode'); + } else if (value instanceof Date) { + this.emitScalar('Date(' + value + ')'); + } else if (value.__Jasmine_been_here_before__) { + this.emitScalar(''); + } else if (jasmine.isArray_(value) || typeof value == 'object') { + value.__Jasmine_been_here_before__ = true; + if (jasmine.isArray_(value)) { + this.emitArray(value); + } else { + this.emitObject(value); + } + delete value.__Jasmine_been_here_before__; + } else { + this.emitScalar(value.toString()); + } + } finally { + this.ppNestLevel_--; + } +}; + +jasmine.PrettyPrinter.prototype.iterateObject = function(obj, fn) { + for (var property in obj) { + if (property == '__Jasmine_been_here_before__') continue; + fn(property, obj.__lookupGetter__ ? (obj.__lookupGetter__(property) !== jasmine.undefined && + obj.__lookupGetter__(property) !== null) : false); + } +}; + +jasmine.PrettyPrinter.prototype.emitArray = jasmine.unimplementedMethod_; +jasmine.PrettyPrinter.prototype.emitObject = jasmine.unimplementedMethod_; +jasmine.PrettyPrinter.prototype.emitScalar = jasmine.unimplementedMethod_; +jasmine.PrettyPrinter.prototype.emitString = jasmine.unimplementedMethod_; + +jasmine.StringPrettyPrinter = function() { + jasmine.PrettyPrinter.call(this); + + this.string = ''; +}; +jasmine.util.inherit(jasmine.StringPrettyPrinter, jasmine.PrettyPrinter); + +jasmine.StringPrettyPrinter.prototype.emitScalar = function(value) { + this.append(value); +}; + +jasmine.StringPrettyPrinter.prototype.emitString = function(value) { + this.append("'" + value + "'"); +}; + +jasmine.StringPrettyPrinter.prototype.emitArray = function(array) { + this.append('[ '); + for (var i = 0; i < array.length; i++) { + if (i > 0) { + this.append(', '); + } + this.format(array[i]); + } + this.append(' ]'); +}; + +jasmine.StringPrettyPrinter.prototype.emitObject = function(obj) { + var self = this; + this.append('{ '); + var first = true; + + this.iterateObject(obj, function(property, isGetter) { + if (first) { + first = false; + } else { + self.append(', '); + } + + self.append(property); + self.append(' : '); + if (isGetter) { + self.append(''); + } else { + self.format(obj[property]); + } + }); + + this.append(' }'); +}; + +jasmine.StringPrettyPrinter.prototype.append = function(value) { + this.string += value; +}; +jasmine.Queue = function(env) { + this.env = env; + this.blocks = []; + this.running = false; + this.index = 0; + this.offset = 0; + this.abort = false; +}; + +jasmine.Queue.prototype.addBefore = function(block) { + this.blocks.unshift(block); +}; + +jasmine.Queue.prototype.add = function(block) { + this.blocks.push(block); +}; + +jasmine.Queue.prototype.insertNext = function(block) { + this.blocks.splice((this.index + this.offset + 1), 0, block); + this.offset++; +}; + +jasmine.Queue.prototype.start = function(onComplete) { + this.running = true; + this.onComplete = onComplete; + this.next_(); +}; + +jasmine.Queue.prototype.isRunning = function() { + return this.running; +}; + +jasmine.Queue.LOOP_DONT_RECURSE = true; + +jasmine.Queue.prototype.next_ = function() { + var self = this; + var goAgain = true; + + while (goAgain) { + goAgain = false; + + if (self.index < self.blocks.length && !this.abort) { + var calledSynchronously = true; + var completedSynchronously = false; + + var onComplete = function () { + if (jasmine.Queue.LOOP_DONT_RECURSE && calledSynchronously) { + completedSynchronously = true; + return; + } + + if (self.blocks[self.index].abort) { + self.abort = true; + } + + self.offset = 0; + self.index++; + + var now = new Date().getTime(); + if (self.env.updateInterval && now - self.env.lastUpdate > self.env.updateInterval) { + self.env.lastUpdate = now; + self.env.setTimeout(function() { + self.next_(); + }, 0); + } else { + if (jasmine.Queue.LOOP_DONT_RECURSE && completedSynchronously) { + goAgain = true; + } else { + self.next_(); + } + } + }; + self.blocks[self.index].execute(onComplete); + + calledSynchronously = false; + if (completedSynchronously) { + onComplete(); + } + + } else { + self.running = false; + if (self.onComplete) { + self.onComplete(); + } + } + } +}; + +jasmine.Queue.prototype.results = function() { + var results = new jasmine.NestedResults(); + for (var i = 0; i < this.blocks.length; i++) { + if (this.blocks[i].results) { + results.addResult(this.blocks[i].results()); + } + } + return results; +}; + + +/** + * Runner + * + * @constructor + * @param {jasmine.Env} env + */ +jasmine.Runner = function(env) { + var self = this; + self.env = env; + self.queue = new jasmine.Queue(env); + self.before_ = []; + self.after_ = []; + self.suites_ = []; +}; + +jasmine.Runner.prototype.execute = function() { + var self = this; + if (self.env.reporter.reportRunnerStarting) { + self.env.reporter.reportRunnerStarting(this); + } + self.queue.start(function () { + self.finishCallback(); + }); +}; + +jasmine.Runner.prototype.beforeEach = function(beforeEachFunction) { + beforeEachFunction.typeName = 'beforeEach'; + this.before_.splice(0,0,beforeEachFunction); +}; + +jasmine.Runner.prototype.afterEach = function(afterEachFunction) { + afterEachFunction.typeName = 'afterEach'; + this.after_.splice(0,0,afterEachFunction); +}; + + +jasmine.Runner.prototype.finishCallback = function() { + this.env.reporter.reportRunnerResults(this); +}; + +jasmine.Runner.prototype.addSuite = function(suite) { + this.suites_.push(suite); +}; + +jasmine.Runner.prototype.add = function(block) { + if (block instanceof jasmine.Suite) { + this.addSuite(block); + } + this.queue.add(block); +}; + +jasmine.Runner.prototype.specs = function () { + var suites = this.suites(); + var specs = []; + for (var i = 0; i < suites.length; i++) { + specs = specs.concat(suites[i].specs()); + } + return specs; +}; + +jasmine.Runner.prototype.suites = function() { + return this.suites_; +}; + +jasmine.Runner.prototype.topLevelSuites = function() { + var topLevelSuites = []; + for (var i = 0; i < this.suites_.length; i++) { + if (!this.suites_[i].parentSuite) { + topLevelSuites.push(this.suites_[i]); + } + } + return topLevelSuites; +}; + +jasmine.Runner.prototype.results = function() { + return this.queue.results(); +}; +/** + * Internal representation of a Jasmine specification, or test. + * + * @constructor + * @param {jasmine.Env} env + * @param {jasmine.Suite} suite + * @param {String} description + */ +jasmine.Spec = function(env, suite, description) { + if (!env) { + throw new Error('jasmine.Env() required'); + } + if (!suite) { + throw new Error('jasmine.Suite() required'); + } + var spec = this; + spec.id = env.nextSpecId ? env.nextSpecId() : null; + spec.env = env; + spec.suite = suite; + spec.description = description; + spec.queue = new jasmine.Queue(env); + + spec.afterCallbacks = []; + spec.spies_ = []; + + spec.results_ = new jasmine.NestedResults(); + spec.results_.description = description; + spec.matchersClass = null; +}; + +jasmine.Spec.prototype.getFullName = function() { + return this.suite.getFullName() + ' ' + this.description + '.'; +}; + + +jasmine.Spec.prototype.results = function() { + return this.results_; +}; + +/** + * All parameters are pretty-printed and concatenated together, then written to the spec's output. + * + * Be careful not to leave calls to jasmine.log in production code. + */ +jasmine.Spec.prototype.log = function() { + return this.results_.log(arguments); +}; + +jasmine.Spec.prototype.runs = function (func) { + var block = new jasmine.Block(this.env, func, this); + this.addToQueue(block); + return this; +}; + +jasmine.Spec.prototype.addToQueue = function (block) { + if (this.queue.isRunning()) { + this.queue.insertNext(block); + } else { + this.queue.add(block); + } +}; + +/** + * @param {jasmine.ExpectationResult} result + */ +jasmine.Spec.prototype.addMatcherResult = function(result) { + this.results_.addResult(result); +}; + +jasmine.Spec.prototype.expect = function(actual) { + var positive = new (this.getMatchersClass_())(this.env, actual, this); + positive.not = new (this.getMatchersClass_())(this.env, actual, this, true); + return positive; +}; + +/** + * Waits a fixed time period before moving to the next block. + * + * @deprecated Use waitsFor() instead + * @param {Number} timeout milliseconds to wait + */ +jasmine.Spec.prototype.waits = function(timeout) { + var waitsFunc = new jasmine.WaitsBlock(this.env, timeout, this); + this.addToQueue(waitsFunc); + return this; +}; + +/** + * Waits for the latchFunction to return true before proceeding to the next block. + * + * @param {Function} latchFunction + * @param {String} optional_timeoutMessage + * @param {Number} optional_timeout + */ +jasmine.Spec.prototype.waitsFor = function(latchFunction, optional_timeoutMessage, optional_timeout) { + var latchFunction_ = null; + var optional_timeoutMessage_ = null; + var optional_timeout_ = null; + + for (var i = 0; i < arguments.length; i++) { + var arg = arguments[i]; + switch (typeof arg) { + case 'function': + latchFunction_ = arg; + break; + case 'string': + optional_timeoutMessage_ = arg; + break; + case 'number': + optional_timeout_ = arg; + break; + } + } + + var waitsForFunc = new jasmine.WaitsForBlock(this.env, optional_timeout_, latchFunction_, optional_timeoutMessage_, this); + this.addToQueue(waitsForFunc); + return this; +}; + +jasmine.Spec.prototype.fail = function (e) { + var expectationResult = new jasmine.ExpectationResult({ + passed: false, + message: e ? jasmine.util.formatException(e) : 'Exception', + trace: { stack: e.stack } + }); + this.results_.addResult(expectationResult); +}; + +jasmine.Spec.prototype.getMatchersClass_ = function() { + return this.matchersClass || this.env.matchersClass; +}; + +jasmine.Spec.prototype.addMatchers = function(matchersPrototype) { + var parent = this.getMatchersClass_(); + var newMatchersClass = function() { + parent.apply(this, arguments); + }; + jasmine.util.inherit(newMatchersClass, parent); + jasmine.Matchers.wrapInto_(matchersPrototype, newMatchersClass); + this.matchersClass = newMatchersClass; +}; + +jasmine.Spec.prototype.finishCallback = function() { + this.env.reporter.reportSpecResults(this); +}; + +jasmine.Spec.prototype.finish = function(onComplete) { + this.removeAllSpies(); + this.finishCallback(); + if (onComplete) { + onComplete(); + } +}; + +jasmine.Spec.prototype.after = function(doAfter) { + if (this.queue.isRunning()) { + this.queue.add(new jasmine.Block(this.env, doAfter, this)); + } else { + this.afterCallbacks.unshift(doAfter); + } +}; + +jasmine.Spec.prototype.execute = function(onComplete) { + var spec = this; + if (!spec.env.specFilter(spec)) { + spec.results_.skipped = true; + spec.finish(onComplete); + return; + } + + this.env.reporter.reportSpecStarting(this); + + spec.env.currentSpec = spec; + + spec.addBeforesAndAftersToQueue(); + + spec.queue.start(function () { + spec.finish(onComplete); + }); +}; + +jasmine.Spec.prototype.addBeforesAndAftersToQueue = function() { + var runner = this.env.currentRunner(); + var i; + + for (var suite = this.suite; suite; suite = suite.parentSuite) { + for (i = 0; i < suite.before_.length; i++) { + this.queue.addBefore(new jasmine.Block(this.env, suite.before_[i], this)); + } + } + for (i = 0; i < runner.before_.length; i++) { + this.queue.addBefore(new jasmine.Block(this.env, runner.before_[i], this)); + } + for (i = 0; i < this.afterCallbacks.length; i++) { + this.queue.add(new jasmine.Block(this.env, this.afterCallbacks[i], this)); + } + for (suite = this.suite; suite; suite = suite.parentSuite) { + for (i = 0; i < suite.after_.length; i++) { + this.queue.add(new jasmine.Block(this.env, suite.after_[i], this)); + } + } + for (i = 0; i < runner.after_.length; i++) { + this.queue.add(new jasmine.Block(this.env, runner.after_[i], this)); + } +}; + +jasmine.Spec.prototype.explodes = function() { + throw 'explodes function should not have been called'; +}; + +jasmine.Spec.prototype.spyOn = function(obj, methodName, ignoreMethodDoesntExist) { + if (obj == jasmine.undefined) { + throw "spyOn could not find an object to spy upon for " + methodName + "()"; + } + + if (!ignoreMethodDoesntExist && obj[methodName] === jasmine.undefined) { + throw methodName + '() method does not exist'; + } + + if (!ignoreMethodDoesntExist && obj[methodName] && obj[methodName].isSpy) { + throw new Error(methodName + ' has already been spied upon'); + } + + var spyObj = jasmine.createSpy(methodName); + + this.spies_.push(spyObj); + spyObj.baseObj = obj; + spyObj.methodName = methodName; + spyObj.originalValue = obj[methodName]; + + obj[methodName] = spyObj; + + return spyObj; +}; + +jasmine.Spec.prototype.removeAllSpies = function() { + for (var i = 0; i < this.spies_.length; i++) { + var spy = this.spies_[i]; + spy.baseObj[spy.methodName] = spy.originalValue; + } + this.spies_ = []; +}; + +/** + * Internal representation of a Jasmine suite. + * + * @constructor + * @param {jasmine.Env} env + * @param {String} description + * @param {Function} specDefinitions + * @param {jasmine.Suite} parentSuite + */ +jasmine.Suite = function(env, description, specDefinitions, parentSuite) { + var self = this; + self.id = env.nextSuiteId ? env.nextSuiteId() : null; + self.description = description; + self.queue = new jasmine.Queue(env); + self.parentSuite = parentSuite; + self.env = env; + self.before_ = []; + self.after_ = []; + self.children_ = []; + self.suites_ = []; + self.specs_ = []; +}; + +jasmine.Suite.prototype.getFullName = function() { + var fullName = this.description; + for (var parentSuite = this.parentSuite; parentSuite; parentSuite = parentSuite.parentSuite) { + fullName = parentSuite.description + ' ' + fullName; + } + return fullName; +}; + +jasmine.Suite.prototype.finish = function(onComplete) { + this.env.reporter.reportSuiteResults(this); + this.finished = true; + if (typeof(onComplete) == 'function') { + onComplete(); + } +}; + +jasmine.Suite.prototype.beforeEach = function(beforeEachFunction) { + beforeEachFunction.typeName = 'beforeEach'; + this.before_.unshift(beforeEachFunction); +}; + +jasmine.Suite.prototype.afterEach = function(afterEachFunction) { + afterEachFunction.typeName = 'afterEach'; + this.after_.unshift(afterEachFunction); +}; + +jasmine.Suite.prototype.results = function() { + return this.queue.results(); +}; + +jasmine.Suite.prototype.add = function(suiteOrSpec) { + this.children_.push(suiteOrSpec); + if (suiteOrSpec instanceof jasmine.Suite) { + this.suites_.push(suiteOrSpec); + this.env.currentRunner().addSuite(suiteOrSpec); + } else { + this.specs_.push(suiteOrSpec); + } + this.queue.add(suiteOrSpec); +}; + +jasmine.Suite.prototype.specs = function() { + return this.specs_; +}; + +jasmine.Suite.prototype.suites = function() { + return this.suites_; +}; + +jasmine.Suite.prototype.children = function() { + return this.children_; +}; + +jasmine.Suite.prototype.execute = function(onComplete) { + var self = this; + this.queue.start(function () { + self.finish(onComplete); + }); +}; +jasmine.WaitsBlock = function(env, timeout, spec) { + this.timeout = timeout; + jasmine.Block.call(this, env, null, spec); +}; + +jasmine.util.inherit(jasmine.WaitsBlock, jasmine.Block); + +jasmine.WaitsBlock.prototype.execute = function (onComplete) { + if (jasmine.VERBOSE) { + this.env.reporter.log('>> Jasmine waiting for ' + this.timeout + ' ms...'); + } + this.env.setTimeout(function () { + onComplete(); + }, this.timeout); +}; +/** + * A block which waits for some condition to become true, with timeout. + * + * @constructor + * @extends jasmine.Block + * @param {jasmine.Env} env The Jasmine environment. + * @param {Number} timeout The maximum time in milliseconds to wait for the condition to become true. + * @param {Function} latchFunction A function which returns true when the desired condition has been met. + * @param {String} message The message to display if the desired condition hasn't been met within the given time period. + * @param {jasmine.Spec} spec The Jasmine spec. + */ +jasmine.WaitsForBlock = function(env, timeout, latchFunction, message, spec) { + this.timeout = timeout || env.defaultTimeoutInterval; + this.latchFunction = latchFunction; + this.message = message; + this.totalTimeSpentWaitingForLatch = 0; + jasmine.Block.call(this, env, null, spec); +}; +jasmine.util.inherit(jasmine.WaitsForBlock, jasmine.Block); + +jasmine.WaitsForBlock.TIMEOUT_INCREMENT = 10; + +jasmine.WaitsForBlock.prototype.execute = function(onComplete) { + if (jasmine.VERBOSE) { + this.env.reporter.log('>> Jasmine waiting for ' + (this.message || 'something to happen')); + } + var latchFunctionResult; + try { + latchFunctionResult = this.latchFunction.apply(this.spec); + } catch (e) { + this.spec.fail(e); + onComplete(); + return; + } + + if (latchFunctionResult) { + onComplete(); + } else if (this.totalTimeSpentWaitingForLatch >= this.timeout) { + var message = 'timed out after ' + this.timeout + ' msec waiting for ' + (this.message || 'something to happen'); + this.spec.fail({ + name: 'timeout', + message: message + }); + + this.abort = true; + onComplete(); + } else { + this.totalTimeSpentWaitingForLatch += jasmine.WaitsForBlock.TIMEOUT_INCREMENT; + var self = this; + this.env.setTimeout(function() { + self.execute(onComplete); + }, jasmine.WaitsForBlock.TIMEOUT_INCREMENT); + } +}; +// Mock setTimeout, clearTimeout +// Contributed by Pivotal Computer Systems, www.pivotalsf.com + +jasmine.FakeTimer = function() { + this.reset(); + + var self = this; + self.setTimeout = function(funcToCall, millis) { + self.timeoutsMade++; + self.scheduleFunction(self.timeoutsMade, funcToCall, millis, false); + return self.timeoutsMade; + }; + + self.setInterval = function(funcToCall, millis) { + self.timeoutsMade++; + self.scheduleFunction(self.timeoutsMade, funcToCall, millis, true); + return self.timeoutsMade; + }; + + self.clearTimeout = function(timeoutKey) { + self.scheduledFunctions[timeoutKey] = jasmine.undefined; + }; + + self.clearInterval = function(timeoutKey) { + self.scheduledFunctions[timeoutKey] = jasmine.undefined; + }; + +}; + +jasmine.FakeTimer.prototype.reset = function() { + this.timeoutsMade = 0; + this.scheduledFunctions = {}; + this.nowMillis = 0; +}; + +jasmine.FakeTimer.prototype.tick = function(millis) { + var oldMillis = this.nowMillis; + var newMillis = oldMillis + millis; + this.runFunctionsWithinRange(oldMillis, newMillis); + this.nowMillis = newMillis; +}; + +jasmine.FakeTimer.prototype.runFunctionsWithinRange = function(oldMillis, nowMillis) { + var scheduledFunc; + var funcsToRun = []; + for (var timeoutKey in this.scheduledFunctions) { + scheduledFunc = this.scheduledFunctions[timeoutKey]; + if (scheduledFunc != jasmine.undefined && + scheduledFunc.runAtMillis >= oldMillis && + scheduledFunc.runAtMillis <= nowMillis) { + funcsToRun.push(scheduledFunc); + this.scheduledFunctions[timeoutKey] = jasmine.undefined; + } + } + + if (funcsToRun.length > 0) { + funcsToRun.sort(function(a, b) { + return a.runAtMillis - b.runAtMillis; + }); + for (var i = 0; i < funcsToRun.length; ++i) { + try { + var funcToRun = funcsToRun[i]; + this.nowMillis = funcToRun.runAtMillis; + funcToRun.funcToCall(); + if (funcToRun.recurring) { + this.scheduleFunction(funcToRun.timeoutKey, + funcToRun.funcToCall, + funcToRun.millis, + true); + } + } catch(e) { + } + } + this.runFunctionsWithinRange(oldMillis, nowMillis); + } +}; + +jasmine.FakeTimer.prototype.scheduleFunction = function(timeoutKey, funcToCall, millis, recurring) { + this.scheduledFunctions[timeoutKey] = { + runAtMillis: this.nowMillis + millis, + funcToCall: funcToCall, + recurring: recurring, + timeoutKey: timeoutKey, + millis: millis + }; +}; + +/** + * @namespace + */ +jasmine.Clock = { + defaultFakeTimer: new jasmine.FakeTimer(), + + reset: function() { + jasmine.Clock.assertInstalled(); + jasmine.Clock.defaultFakeTimer.reset(); + }, + + tick: function(millis) { + jasmine.Clock.assertInstalled(); + jasmine.Clock.defaultFakeTimer.tick(millis); + }, + + runFunctionsWithinRange: function(oldMillis, nowMillis) { + jasmine.Clock.defaultFakeTimer.runFunctionsWithinRange(oldMillis, nowMillis); + }, + + scheduleFunction: function(timeoutKey, funcToCall, millis, recurring) { + jasmine.Clock.defaultFakeTimer.scheduleFunction(timeoutKey, funcToCall, millis, recurring); + }, + + useMock: function() { + if (!jasmine.Clock.isInstalled()) { + var spec = jasmine.getEnv().currentSpec; + spec.after(jasmine.Clock.uninstallMock); + + jasmine.Clock.installMock(); + } + }, + + installMock: function() { + jasmine.Clock.installed = jasmine.Clock.defaultFakeTimer; + }, + + uninstallMock: function() { + jasmine.Clock.assertInstalled(); + jasmine.Clock.installed = jasmine.Clock.real; + }, + + real: { + setTimeout: jasmine.getGlobal().setTimeout, + clearTimeout: jasmine.getGlobal().clearTimeout, + setInterval: jasmine.getGlobal().setInterval, + clearInterval: jasmine.getGlobal().clearInterval + }, + + assertInstalled: function() { + if (!jasmine.Clock.isInstalled()) { + throw new Error("Mock clock is not installed, use jasmine.Clock.useMock()"); + } + }, + + isInstalled: function() { + return jasmine.Clock.installed == jasmine.Clock.defaultFakeTimer; + }, + + installed: null +}; +jasmine.Clock.installed = jasmine.Clock.real; + +//else for IE support +jasmine.getGlobal().setTimeout = function(funcToCall, millis) { + if (jasmine.Clock.installed.setTimeout.apply) { + return jasmine.Clock.installed.setTimeout.apply(this, arguments); + } else { + return jasmine.Clock.installed.setTimeout(funcToCall, millis); + } +}; + +jasmine.getGlobal().setInterval = function(funcToCall, millis) { + if (jasmine.Clock.installed.setInterval.apply) { + return jasmine.Clock.installed.setInterval.apply(this, arguments); + } else { + return jasmine.Clock.installed.setInterval(funcToCall, millis); + } +}; + +jasmine.getGlobal().clearTimeout = function(timeoutKey) { + if (jasmine.Clock.installed.clearTimeout.apply) { + return jasmine.Clock.installed.clearTimeout.apply(this, arguments); + } else { + return jasmine.Clock.installed.clearTimeout(timeoutKey); + } +}; + +jasmine.getGlobal().clearInterval = function(timeoutKey) { + if (jasmine.Clock.installed.clearTimeout.apply) { + return jasmine.Clock.installed.clearInterval.apply(this, arguments); + } else { + return jasmine.Clock.installed.clearInterval(timeoutKey); + } +}; + +jasmine.version_= { + "major": 1, + "minor": 1, + "build": 0, + "revision": 1308150691 +} diff --git a/test/spec/jasmine/jasmine_favicon.png b/test/spec/jasmine/jasmine_favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..218f3b43713598fa5a3e78b57aceb909c33f46df GIT binary patch literal 905 zcmV;419tq0P)Px#AY({UO#lFTCIA3{ga82g0001h=l}q9FaQARU;qF* zm;eA5aGbhPJOBUy24YJ`L;(K){{a7>y{D4^000SaNLh0L01FcU01FcV0GgZ_0008u zNkl3{fod28|PjmA)7fYg4w8-(2my9xtBGOs}K`n&t1VzxMO^X)M zrW+Ln1udc?q6TP)z5gAjt)P&D!M$+HJK#x<`xnD030zwD?KrxxY!2tlA zGc-58?0D7SsT)7Km=v+tNVNUk`?s@;^OxCF)y6P}_mL;~7;S<@b|MzmKq)m8l@yky zT1~ECpxZw@64!nkI34QLiUsA%i%N>-$&zGYR7WJyi9ERMyS(%kf z7A_r)X>!90&m(FwDQZ>q;+nOa*KR2+E6Fz)QwU=W1Oyo*4>_qlm|~joa|{4_A_3W8 z#FFZzRp-xMIx5a7D_Fj3&#r^TbIY@cND1d0f*^qDIs{!pw!IWGQ_%l4#ASm_D5Vet z0%ek7^)@xPihX_G0&hIc9*14ca=D!8oG}vW?H%~w^F?f_s>zU|fKrNJXJ_d6{v!t( zpEoqMws_yQws>3o?VW8Txq~#->dJG^ELW5irR!s`(_JvD^6;r+ho~eIK@ia8_lH(h zt*-p?CFC1_h2MV=?jP){uW!7WjLjCaO&c1D+tf582!XEaoB#xWAYcN5f$sLtf$koW zQs{{>)ZTq?FC6|J_%n}AWbiFK(Bo-%^-{H`*)E(ucjo-r%SYm)W5f6tN=xz=S646E fNXW#U{x?4WXWJ').createWindow(); -add_script = (content) -> - scr = win.document.createElement('script'); - scr.innerHTML = content - win.document.getElementsByTagName('head')[0].appendChild(scr) -add_script(config) -add_script(raw_lib) - -setTimeout(-> - console.log(win.document.innerHTML) -, 400) - -### -jsdom.env('', ['../session.js'], (err, win) -> - win.onload= -> - console.log 'load' - console.log win.session - console.log([err, win.onload]) -) - -### -doc = jsdom.jsdom('loading...') -win = doc.createWindow() -console.log doc.innerHTML -win.onload = -> - console.log 'load' - console.log win.session \ No newline at end of file diff --git a/test/test.js b/test/test.js deleted file mode 100644 index 29854e1..0000000 --- a/test/test.js +++ /dev/null @@ -1,42 +0,0 @@ -(function() { - var add_script, assert, config, doc, fs, jsdom, raw_lib, win; - jsdom = require('jsdom'); - fs = require('fs'); - assert = require('assert'); - raw_lib = fs.readFileSync(__dirname + '/../session.js').toString(); - config = 'window.session = ' + JSON.stringify({ - config: { - gapi_location: false, - location_cookie: '', - session_cookie: '' - } - }) + ';'; - win = jsdom.jsdom('').createWindow(); - add_script = function(content) { - var scr; - scr = win.document.createElement('script'); - scr.innerHTML = content; - return win.document.getElementsByTagName('head')[0].appendChild(scr); - }; - add_script(config); - add_script(raw_lib); - setTimeout(function() { - return console.log(win.document.innerHTML); - }, 400); - /* - jsdom.env('', ['../session.js'], (err, win) -> - win.onload= -> - console.log 'load' - console.log win.session - console.log([err, win.onload]) - ) - - */ - doc = jsdom.jsdom('loading...'); - win = doc.createWindow(); - console.log(doc.innerHTML); - win.onload = function() { - console.log('load'); - return console.log(win.session); - }; -}).call(this); diff --git a/test/ua.js b/test/ua.js deleted file mode 100644 index e69de29..0000000 From aaae9f5116ecc69cfabe97fc6f35d7dc384bf7e3 Mon Sep 17 00:00:00 2001 From: Iain Nash Date: Thu, 12 Jan 2012 13:55:37 -0500 Subject: [PATCH 28/53] starting timezone / locale testing --- session.js | 4 +- test/spec/helpers/session-mocker.js | 12 ++++- test/spec/timezone-spec.js | 81 ++++++++++++++++++++++++++++- 3 files changed, 92 insertions(+), 5 deletions(-) diff --git a/session.js b/session.js index 8eb963e..711c1f6 100644 --- a/session.js +++ b/session.js @@ -174,12 +174,12 @@ var session_fetch = (function(win, doc, nav){ // Gives a browser estimation, not guaranteed to be correct. }, locale: function() { - var lang = ( + var lang = (( nav.language || nav.browserLanguage || nav.systemLanguage || nav.userLanguage - ).split("-"); + ) || '').split("-"); if (lang.length == 2){ return { country: lang[1].toLowerCase(), lang: lang[0].toLowerCase() }; } else if (lang) { diff --git a/test/spec/helpers/session-mocker.js b/test/spec/helpers/session-mocker.js index 88fffa2..c0f7574 100644 --- a/test/spec/helpers/session-mocker.js +++ b/test/spec/helpers/session-mocker.js @@ -23,6 +23,16 @@ function create_mock_doc(){ return doc; } +function create_mock_navigator(){ + return { + userAgent: navigator.userAgent, + appVersion: 0.4, + vendor: 'testing', + platform: 'linux', + language: 'en-US' + } +} + function create_mock(sess_obj){ if (!sess_obj){ var sess_obj = {options: { gapi_location: false }}; @@ -30,7 +40,7 @@ function create_mock(sess_obj){ var mock = { sess: null // sets initial state , win: {innerWidth: 200, innerHeight: 200, location: window.location, session: sess_obj} - , nav: navigator + , nav: create_mock_navigator() , get_cookie_obj: function(cookie_name){ return JSON.parse(unescape(this.doc.cookiejar.getCookie(cookie_name, cookiejar.CookieAccessInfo()).value)); }, doc: create_mock_doc() diff --git a/test/spec/timezone-spec.js b/test/spec/timezone-spec.js index 2e89836..56b7ac3 100644 --- a/test/spec/timezone-spec.js +++ b/test/spec/timezone-spec.js @@ -1,6 +1,83 @@ // drat. I have to mock the date object :/ -function mock_date(){ +describe('timezone / date tests', function(){ var internal_date = new Date(); + // get IE date format + it('should expect IE date format', function(){ + + }) + // get firefox date format + it('should expect firefox date format', function(){ + + }) + // get chrome date format + it('should expect correct chrome date format', function(){ + + }) + // time offset + it("won't blow up with a no / wierd timeoffset", function(){ + + }) +}); + +describe('locale tests', function(){ + var mock = create_mock() + it('should take only one locale', function(){ + mock.nav.language = 'Es' + mock.run_sess() + expect(mock.win.session.locale.lang).toEqual('es') + expect(mock.win.session.locale.country).toBeFalsy() + }) + it('should take no locale', function(){ + delete mock.nav.language + mock.run_sess() + expect(mock.win.session.locale).toBeDefined() + expect(mock.win.session.locale.lang).toBeFalsy() + expect(mock.win.session.locale.country).toBeFalsy() + }) + it('should take both locale and country', function(){ + mock.nav.language = 'EN-US' + mock.run_sess() + expect(mock.win.session.locale.country).toEqual('us') + expect(mock.win.session.locale.lang).toEqual('en') + }) + it('should take blank string locale', function(){ + mock.nav.language = '' + mock.run_sess() + expect(mock.win.session.locale).toBeDefined() + expect(mock.win.session.locale.country).toBeFalsy() + expect(mock.win.session.locale.lang).toBeFalsy() + }) + it('should not fail on invalid string', function(){ + mock.nav.language = 'foO1-bar-langauge-----!' + mock.run_sess() + expect(mock.win.session.locale).toBeTruthy() + }) +}); + +describe('alternate locale sources', function(){ + var mock = create_mock() + + beforeEach(function(){ + mock.nav = create_mock_navigator() + delete mock.nav.langauge; + }) -} \ No newline at end of file + it('should take prop language', function(){ + mock.nav.language = 'en-US' + }) + it('should take prop browserLanguage', function(){ + mock.nav.browserLanguage = 'en-US' + }) + it('should take prop systemLanguage', function(){ + mock.nav.systemLanguage = 'en-US' + }) + it('should take prop userLanguage', function(){ + mock.nav.userLanguage = 'en-US' + }) + afterEach(function(){ + mock.run_sess() + expect(mock.win.session.locale.country).toEqual('us') + expect(mock.win.session.locale.lang).toEqual('en') + }) +}); \ No newline at end of file From 6383b5218f2ee6d31a9e5432ebdd7113e7aa8bb0 Mon Sep 17 00:00:00 2001 From: Iain Nash Date: Thu, 12 Jan 2012 23:36:32 -0500 Subject: [PATCH 29/53] yo dawg, I herd you like code, so I tested my code with code. (more specs) --- session.js | 21 +++---- session.min.js | 2 +- test/SpecRunner.html | 2 + test/spec/browser-spec.js | 51 +++++++++++++++++ test/spec/device-spec.js | 87 +++++++++++++++++++++++++++++ test/spec/extension-spec.js | 37 ++++++++++++ test/spec/helpers/session-mocker.js | 22 +++++--- 7 files changed, 201 insertions(+), 21 deletions(-) create mode 100644 test/spec/browser-spec.js create mode 100644 test/spec/device-spec.js create mode 100644 test/spec/extension-spec.js diff --git a/session.js b/session.js index 711c1f6..96775ab 100644 --- a/session.js +++ b/session.js @@ -9,7 +9,6 @@ var session_fetch = (function(win, doc, nav){ // Changing the API Version invalidates olde cookies with previous api version tags. var API_VERSION = 0.4; - // Settings: defaults var options = { // Use the HTML5 Geolocation API @@ -189,19 +188,17 @@ var session_fetch = (function(win, doc, nav){ device: function() { var device = { screen: { - width: screen.width, - height: screen.height + width: win.screen.width, + height: win.screen.height } }; - var html = doc.documentElement, - body = doc.getElementsByTagName("body")[0]; device.viewport = { - width: win.innerWidth || html.clientWidth || body.clientWidth, - height: win.innerHeight || html.clientHeight || body.clientHeight + width: win.innerWidth || doc.body.clientWidth || doc.documentElement.clientWidth, + height: win.innerHeight || doc.body.clientHeight || doc.documentElement.clientHeight }; device.is_tablet = !!nav.userAgent.match(/(iPad|SCH-I800|xoom|kindle)/i); - device.is_phone = !device.isTablet && !!nav.userAgent.match(/(iPhone|iPod|blackberry|android 0.5|htc|lg|midp|mmp|mobile|nokia|opera mini|palm|pocket|psp|sgh|smartphone|symbian|treo mini|Playstation Portable|SonyEricsson|Samsung|MobileExplorer|PalmSource|Benq|Windows Phone|Windows Mobile|IEMobile|Windows CE|Nintendo Wii)/i); - device.is_mobile = (device.is_tablet || device.is_phone); + device.is_phone = !device.is_tablet && !!nav.userAgent.match(/(iPhone|iPod|blackberry|android 0.5|htc|lg|midp|mmp|mobile|nokia|opera mini|palm|pocket|psp|sgh|smartphone|symbian|treo mini|Playstation Portable|SonyEricsson|Samsung|MobileExplorer|PalmSource|Benq|Windows Phone|Windows Mobile|IEMobile|Windows CE|Nintendo Wii)/i); + device.is_mobile = device.is_tablet || device.is_phone; return device; }, plugins: function(){ @@ -412,9 +409,9 @@ var session_fetch = (function(win, doc, nav){ SessionRunner(); }); - -if (typeof(exports) === 'undefined'){ +// Switch for testing purposes. +if (typeof(window.exports) === 'undefined'){ session_fetch(window, document, navigator); } else { - exports.session = session_fetch; + window.exports.session = session_fetch; } diff --git a/session.min.js b/session.min.js index 1ae942b..e622331 100644 --- a/session.min.js +++ b/session.min.js @@ -5,4 +5,4 @@ * Portions of session.js are inspired or borrowed from Underscore.js, and quirksmode.org demo javascript. * This version uses google's jsapi library for location services. * For details, see: https://github.com/codejoust/session.js - */(function(a,b,c){var d=.4,e={use_html5_location:!1,ipinfodb_key:!1,gapi_location:!0,location_cookie:"location",location_cookie_timeout:5,session_timeout:32,session_cookie:"first_session"},f=function(){String.prototype.contains=function(a){if(typeof a=="string")return this.indexOf(a)!==-1;for(var b=0;b1)for(k=0;k1)for(k=0;k + + + + + +

Session.js Debug / Testing Page

+

Script Output:

+
+    loading...
+  
+ + + + + +

+  
+  
+  

Session.JS on GitHub by Iain

+ + + diff --git a/session.js b/session.js index 737e397..707bf67 100644 --- a/session.js +++ b/session.js @@ -34,9 +34,10 @@ var session_fetch = (function(win, doc, nav){ // Session object var SessionRunner = function(){ + win.session = {}; // Helper for querying. // Usage: session.current_session.referrer_info.hostname.contains(['github.com','news.ycombinator.com']) - String.prototype.contains = function(other_str){ + win.session.contains = function(other_str){ if (typeof(other_str) === 'string'){ return (this.indexOf(other_str) !== -1); } for (var i = 0; i < other_str.length; i++){ diff --git a/session.min.js b/session.min.js index 6bed9ac..dfad560 100644 --- a/session.min.js +++ b/session.min.js @@ -5,4 +5,4 @@ * Portions of session.js are inspired or borrowed from Underscore.js, and quirksmode.org demo javascript. * This version uses google's jsapi library for location services. * For details, see: https://github.com/codejoust/session.js - */var session_fetch=function(a,b,c){var d=.4,e={use_html5_location:!1,ipinfodb_key:!1,gapi_location:!0,location_cookie:"location",location_cookie_timeout:5,session_timeout:32,session_cookie:"first_session"},f=function(){String.prototype.contains=function(a){if(typeof a=="string")return this.indexOf(a)!==-1;for(var b=0;b1)for(k=0;k1)for(k=0;k Date: Mon, 11 Jun 2012 18:23:31 -0400 Subject: [PATCH 35/53] Fixed 'start' callback bug. --- session.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/session.js b/session.js index 707bf67..9dc7aad 100644 --- a/session.js +++ b/session.js @@ -34,7 +34,7 @@ var session_fetch = (function(win, doc, nav){ // Session object var SessionRunner = function(){ - win.session = {}; + win.session = win.session || {}; // Helper for querying. // Usage: session.current_session.referrer_info.hostname.contains(['github.com','news.ycombinator.com']) win.session.contains = function(other_str){ From 9545b1b9a3a96c566fd5142c9d1414125d06fbd6 Mon Sep 17 00:00:00 2001 From: Iain Nash Date: Sun, 17 Jun 2012 09:12:30 -0400 Subject: [PATCH 36/53] Adding license file and updating packed version --- session.min.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/session.min.js b/session.min.js index dfad560..d008d80 100644 --- a/session.min.js +++ b/session.min.js @@ -5,4 +5,4 @@ * Portions of session.js are inspired or borrowed from Underscore.js, and quirksmode.org demo javascript. * This version uses google's jsapi library for location services. * For details, see: https://github.com/codejoust/session.js - */var session_fetch=function(a,b,c){var d=.4,e={use_html5_location:!1,ipinfodb_key:!1,gapi_location:!0,location_cookie:"location",location_cookie_timeout:5,session_timeout:32,session_cookie:"first_session"},f=function(){a.session={},a.session.contains=function(a){if(typeof a=="string")return this.indexOf(a)!==-1;for(var b=0;b1)for(k=0;k1)for(k=0;k Date: Thu, 9 Aug 2012 14:20:01 -0700 Subject: [PATCH 37/53] Fixing viewport for ie --- LICENSE | 6 ++++++ session.js | 4 ++-- session.min.js | 2 +- 3 files changed, 9 insertions(+), 3 deletions(-) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..3080ecd --- /dev/null +++ b/LICENSE @@ -0,0 +1,6 @@ +session.js 0.4.1 +(c) 2012 Iain, CodeJoust +session.js is freely distributable under the MIT license. +Portions of session.js are inspired or borrowed from Underscore.js, and quirksmode.org demo javascript. +This version uses google's jsapi library for location services. +For details, see: https://github.com/codejoust/session.js diff --git a/session.js b/session.js index 9dc7aad..190654b 100644 --- a/session.js +++ b/session.js @@ -194,8 +194,8 @@ var session_fetch = (function(win, doc, nav){ } }; device.viewport = { - width: win.innerWidth || doc.body.clientWidth || doc.documentElement.clientWidth, - height: win.innerHeight || doc.body.clientHeight || doc.documentElement.clientHeight + width: win.innerWidth || doc.documentElement.clientWidth || doc.body.clientWidth, + height: win.innerHeight || doc.documentElement.clientHeight || doc.body.clientHeight }; device.is_tablet = !!nav.userAgent.match(/(iPad|SCH-I800|xoom|kindle)/i); device.is_phone = !device.is_tablet && !!nav.userAgent.match(/(iPhone|iPod|blackberry|android 0.5|htc|lg|midp|mmp|mobile|nokia|opera mini|palm|pocket|psp|sgh|smartphone|symbian|treo mini|Playstation Portable|SonyEricsson|Samsung|MobileExplorer|PalmSource|Benq|Windows Phone|Windows Mobile|IEMobile|Windows CE|Nintendo Wii)/i); diff --git a/session.min.js b/session.min.js index d008d80..3ab6e9b 100644 --- a/session.min.js +++ b/session.min.js @@ -5,4 +5,4 @@ * Portions of session.js are inspired or borrowed from Underscore.js, and quirksmode.org demo javascript. * This version uses google's jsapi library for location services. * For details, see: https://github.com/codejoust/session.js - */var session_fetch=function(a,b,c){var d=.4,e={use_html5_location:!1,ipinfodb_key:!1,gapi_location:!0,location_cookie:"location",location_cookie_timeout:5,session_timeout:32,session_cookie:"first_session"},f=function(){a.session=a.session||{},a.session.contains=function(a){if(typeof a=="string")return this.indexOf(a)!==-1;for(var b=0;b1)for(k=0;k1)for(l=0;l Date: Wed, 31 Oct 2012 15:48:18 -0300 Subject: [PATCH 38/53] Call ipinfodb only when the cookie expires MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previously, session.js would call the ipinfodb service on every user's request and reset the cookie, even if the cookie was already present. Now ipinfodb will only set the user location if the cookie doesn't already exist.  --- session.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/session.js b/session.js index 190654b..89b6369 100644 --- a/session.js +++ b/session.js @@ -307,7 +307,7 @@ var session_fetch = (function(win, doc, nav){ ipinfodb_location: function(api_key){ return function (callback){ var location_cookie = util.get_obj(options.location_cookie); - if (location_cookie && location_cookie.source === 'ipinfodb'){ callback(location_cookie); } + if (!location_cookie && location_cookie.source === 'ipinfodb'){ win.ipinfocb = function(data){ if (data.statusCode === "OK"){ data.source = "ipinfodb"; @@ -321,6 +321,7 @@ var session_fetch = (function(win, doc, nav){ else { callback({error: true, source: "ipinfodb", message: data.statusMessage}); } }} util.embed_script("http://api.ipinfodb.com/v3/ip-city/?key=" + api_key + "&format=json&callback=ipinfocb"); + } else { callback(location_cookie); } }} }; From 0cc51d103d22517dc940ffb49f886f35d99d5e14 Mon Sep 17 00:00:00 2001 From: Victor Homyakov Date: Fri, 3 May 2013 12:59:04 +0300 Subject: [PATCH 39/53] Fixed jshint warnings (global vars, missing or extra semicolons) --- session.js | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/session.js b/session.js index 190654b..8a35c99 100644 --- a/session.js +++ b/session.js @@ -42,10 +42,10 @@ var session_fetch = (function(win, doc, nav){ return (this.indexOf(other_str) !== -1); } for (var i = 0; i < other_str.length; i++){ if (this.indexOf(other_str[i]) !== -1){ return true; } } - return false; } + return false; }; // Merge options if (win.session && win.session.options) { - for (option in win.session.options){ + for (var option in win.session.options){ options[option] = win.session.options[option]; } } // Modules to run @@ -213,7 +213,7 @@ var session_fetch = (function(win, doc, nav){ } } return false; } return false; - } + }; return { flash: check_plugin("flash"), silverlight: check_plugin("silverlight"), @@ -298,11 +298,11 @@ var session_fetch = (function(win, doc, nav){ options.location_cookie, util.package_obj(win.google.loader.ClientLocation), options.location_cookie_timeout * 60 * 60 * 1000); - }} + }}; util.embed_script("https://www.google.com/jsapi?callback=gloader_ready"); } else { callback(location); - }} + }}; }, ipinfodb_location: function(api_key){ return function (callback){ @@ -319,7 +319,7 @@ var session_fetch = (function(win, doc, nav){ } else { if (options.gapi_location){ return modules.gapi_location()(callback); } else { callback({error: true, source: "ipinfodb", message: data.statusMessage}); } - }} + }}; util.embed_script("http://api.ipinfodb.com/v3/ip-city/?key=" + api_key + "&format=json&callback=ipinfocb"); }} }; @@ -328,7 +328,7 @@ var session_fetch = (function(win, doc, nav){ var util = { parse_url: function(url_str){ var a = doc.createElement("a"), query = {}; - a.href = url_str; query_str = a.search.substr(1); + a.href = url_str; var query_str = a.search.substr(1); // Disassemble query string if (query_str != ''){ var pairs = query_str.split("&"), i = 0, @@ -349,7 +349,7 @@ var session_fetch = (function(win, doc, nav){ }, set_cookie: function(cname, value, expires, options){ // from jquery.cookie.js if (!cname){ return null; } - if (!options){ var options = {}; } + if (!options){ options = {}; } if (value === null || value === undefined){ expires = -1; } if (expires){ options.expires = (new Date().getTime()) + expires; } return (doc.cookie = [ @@ -380,7 +380,7 @@ var session_fetch = (function(win, doc, nav){ }, get_obj: function(cookie_name){ var obj; - try { obj = JSON.parse(util.get_cookie(cookie_name)); } catch(e){}; + try { obj = JSON.parse(util.get_cookie(cookie_name)); } catch(e){} if (obj && obj.version == API_VERSION){ delete obj.version; return obj; } From fb655a882ee447f2ad14f43e019b50e0bd775a35 Mon Sep 17 00:00:00 2001 From: _pants <_pants@getlantern.org> Date: Mon, 3 Jun 2013 15:56:06 -0400 Subject: [PATCH 40/53] add missing "var" found with "use strict" --- session.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/session.js b/session.js index 190654b..70f1bad 100644 --- a/session.js +++ b/session.js @@ -7,6 +7,7 @@ * For details, see: https://github.com/codejoust/session.js */ var session_fetch = (function(win, doc, nav){ + 'use strict'; // Changing the API Version invalidates olde cookies with previous api version tags. var API_VERSION = 0.4; // Settings: defaults @@ -328,7 +329,7 @@ var session_fetch = (function(win, doc, nav){ var util = { parse_url: function(url_str){ var a = doc.createElement("a"), query = {}; - a.href = url_str; query_str = a.search.substr(1); + a.href = url_str; var query_str = a.search.substr(1); // Disassemble query string if (query_str != ''){ var pairs = query_str.split("&"), i = 0, From 08ac4df66636cc1a77c39fed1a9b0fe20e738c32 Mon Sep 17 00:00:00 2001 From: Iain Nash Date: Mon, 2 Dec 2013 08:49:16 -0800 Subject: [PATCH 41/53] Session updates for os and cookie changes --- session.js | 60 ++++++++++++++++++++++++++++++++++++++++++-------- session.min.js | 9 +------- 2 files changed, 52 insertions(+), 17 deletions(-) diff --git a/session.js b/session.js index 2ea4629..4a9244a 100644 --- a/session.js +++ b/session.js @@ -30,7 +30,9 @@ var session_fetch = (function(win, doc, nav){ // Session expiration in days session_timeout: 32, // Session cookie name (set blank to disable cookie) - session_cookie: "first_session" + session_cookie: "first_session", + get_object: null, set_object: null // used for cookie session adaptors + // if null, will be reset to use cookies by default. }; // Session object @@ -110,11 +112,22 @@ var session_fetch = (function(win, doc, nav){ // Browser (and OS) detection var browser = { detect: function(){ - return { + var ret = { browser: this.search(this.data.browser), version: this.search(nav.userAgent) || this.search(nav.appVersion), os: this.search(this.data.os) - } }, + }; + if (ret.os=='Linux'){ + var distros = ['CentOS','Debian','Fedora','Gentoo','Mandriva','Mageia','Red Hat','Slackware','SUSE','Turbolinux','Ubuntu']; + for (var i = 0; i < distros.length;i++){ + if (nav.useragent.toLowerCase().match(distros[i].toLowerCase())){ + ret.distro = distros[i]; + break; + } + } + } + return ret; + }, search: function(data) { if (typeof data === "object"){ // search for string match @@ -215,8 +228,19 @@ var session_fetch = (function(win, doc, nav){ return false; } return false; }; + var check_activex_flash = function(versions){ + var found = true; + for (var i = 0; i < versions.length; i++){ + try { + var obj = new ActiveXObject("ShockwaveFlash.ShockwaveFlash" + versions[i]) + , found = !0; + } catch (e){ /* nil */ } + if (found) return true; + } + return false; + } return { - flash: check_plugin("flash"), + flash: check_plugin("flash") || check_activex_flash(['.7','.6','']), silverlight: check_plugin("silverlight"), java: check_plugin("java"), quicktime: check_plugin("quicktime") @@ -267,7 +291,7 @@ var session_fetch = (function(win, doc, nav){ session.visits++; session.time_since_last_visit = session.last_visit - session.prev_visit; } - util.set_cookie(cookie, util.package_obj(session), expires); + util.set_obj(cookie, session, expires); return session; }, html5_location: function(){ @@ -295,9 +319,9 @@ var session_fetch = (function(win, doc, nav){ } else { callback({error: true, source: "google"}); } - util.set_cookie( + util.set_obj( options.location_cookie, - util.package_obj(win.google.loader.ClientLocation), + win.google.loader.ClientLocation, options.location_cookie_timeout * 60 * 60 * 1000); }}; util.embed_script("https://www.google.com/jsapi?callback=gloader_ready"); @@ -305,6 +329,15 @@ var session_fetch = (function(win, doc, nav){ callback(location); }}; }, + architecture: function(){ + var arch = n.userAgent.match(/x86_64|Win64|WOW64|x86-64|x64\;|AMD64|amd64/) || + (n.cpuClass === 'x64') ? 'x64' : 'x86'; + return { + arch: arch, + is_x64: arch == 'x64', + is_x86: arch == 'x68' + } + }, ipinfodb_location: function(api_key){ return function (callback){ var location_cookie = util.get_obj(options.location_cookie); @@ -312,9 +345,9 @@ var session_fetch = (function(win, doc, nav){ win.ipinfocb = function(data){ if (data.statusCode === "OK"){ data.source = "ipinfodb"; - util.set_cookie( + util.set_obj( options.location_cookie, - util.package_obj(data), + data, options.location_cookie * 60 * 60 * 1000); callback(data); } else { @@ -380,6 +413,9 @@ var session_fetch = (function(win, doc, nav){ return ret; } }, + set_obj: function(cname, value, expires, options){ + util.set_cookie(cname, util.package_obj(value), expires, options); + }, get_obj: function(cookie_name){ var obj; try { obj = JSON.parse(util.get_cookie(cookie_name)); } catch(e){} @@ -389,6 +425,12 @@ var session_fetch = (function(win, doc, nav){ } }; + // cookie options override + if (options.get_object != null){ + util.get_obj = options['get_object']; } + if (options.set_object != null){ + util.set_obj = options['set_object']; } + // JSON var JSON = { parse: (win.JSON && win.JSON.parse) || function(data){ diff --git a/session.min.js b/session.min.js index 3ab6e9b..85b992c 100644 --- a/session.min.js +++ b/session.min.js @@ -1,8 +1 @@ -/** - * session.js 0.4.1 - * (c) 2012 Iain, CodeJoust - * session.js is freely distributable under the MIT license. - * Portions of session.js are inspired or borrowed from Underscore.js, and quirksmode.org demo javascript. - * This version uses google's jsapi library for location services. - * For details, see: https://github.com/codejoust/session.js - */var session_fetch=function(e,t,n){var r=.4,i={use_html5_location:!1,ipinfodb_key:!1,gapi_location:!0,location_cookie:"location",location_cookie_timeout:5,session_timeout:32,session_cookie:"first_session"},s=function(){e.session=e.session||{},e.session.contains=function(e){if(typeof e=="string")return this.indexOf(e)!==-1;for(var t=0;t1)for(l=0;l1){for(i=0;i Date: Thu, 26 Dec 2013 18:24:17 +0900 Subject: [PATCH 42/53] Fix typo useragent to userAgent --- session.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/session.js b/session.js index 4a9244a..3d343f9 100644 --- a/session.js +++ b/session.js @@ -120,7 +120,7 @@ var session_fetch = (function(win, doc, nav){ if (ret.os=='Linux'){ var distros = ['CentOS','Debian','Fedora','Gentoo','Mandriva','Mageia','Red Hat','Slackware','SUSE','Turbolinux','Ubuntu']; for (var i = 0; i < distros.length;i++){ - if (nav.useragent.toLowerCase().match(distros[i].toLowerCase())){ + if (nav.userAgent.toLowerCase().match(distros[i].toLowerCase())){ ret.distro = distros[i]; break; } From f4a4073587037f31db71ac0f8540730e21edf0a2 Mon Sep 17 00:00:00 2001 From: Kyohei Hamada Date: Fri, 7 Feb 2014 20:19:52 +0900 Subject: [PATCH 43/53] Set width/height to 0 if clientWidth/clientHeight is undefined ex) inside iframe --- session.js | 7 +++++-- test/spec/device-spec.js | 10 ++++++++++ 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/session.js b/session.js index 4a9244a..bdb9292 100644 --- a/session.js +++ b/session.js @@ -207,9 +207,12 @@ var session_fetch = (function(win, doc, nav){ height: win.screen.height } }; + var width, height; + try{ width = win.innerWidth || doc.documentElement.clientWidth || doc.body.clientWidth; } catch(e) { width = 0; } + try{ height = win.innerHeight || doc.documentElement.clientHeight || doc.body.clientHeight; } catch(e) { height = 0; } device.viewport = { - width: win.innerWidth || doc.documentElement.clientWidth || doc.body.clientWidth, - height: win.innerHeight || doc.documentElement.clientHeight || doc.body.clientHeight + width: width, + height: height }; device.is_tablet = !!nav.userAgent.match(/(iPad|SCH-I800|xoom|kindle)/i); device.is_phone = !device.is_tablet && !!nav.userAgent.match(/(iPhone|iPod|blackberry|android 0.5|htc|lg|midp|mmp|mobile|nokia|opera mini|palm|pocket|psp|sgh|smartphone|symbian|treo mini|Playstation Portable|SonyEricsson|Samsung|MobileExplorer|PalmSource|Benq|Windows Phone|Windows Mobile|IEMobile|Windows CE|Nintendo Wii)/i); diff --git a/test/spec/device-spec.js b/test/spec/device-spec.js index e313ae1..8ccd3fe 100644 --- a/test/spec/device-spec.js +++ b/test/spec/device-spec.js @@ -39,6 +39,16 @@ describe('screen size', function(){ expect(mock.win.session.device.viewport.width).toEqual(342) expect(mock.win.session.device.viewport.height).toEqual(434) }) + it('gets 0 size when inside iframe', function(){ + mock.doc.documentElement = {} + mock.doc.documentElement.clientWidth = 0 + mock.doc.documentElement.clientHeight = 0 + mock.doc.body = undefined + mock.run_sess() + expect(mock.win.session.device.viewport).toBeTruthy() + expect(mock.win.session.device.viewport.width).toEqual(0) + expect(mock.win.session.device.viewport.height).toEqual(0) + }) }) describe('mobile / tablet device checks', function(){ From 800021692a74b617a4fd56566ee46beb6d826987 Mon Sep 17 00:00:00 2001 From: Ilya Tsuryev Date: Wed, 23 Jul 2014 17:08:05 +0300 Subject: [PATCH 44/53] FIx IE11 detection, fix tests for IE --- session.js | 1 + test/SpecRunner.html | 1 + test/spec/browser-spec.js | 59 +++++++++++++++++++++++++-------------- 3 files changed, 40 insertions(+), 21 deletions(-) diff --git a/session.js b/session.js index 55fe89b..db6dedf 100644 --- a/session.js +++ b/session.js @@ -162,6 +162,7 @@ var session_fetch = (function(win, doc, nav){ { string: nav.vendor, subString: "Camino", identity: "Camino" }, { string: nav.userAgent, subString: "Netscape", identity: "Netscape" }, { string: nav.userAgent, subString: "MSIE", identity: "Explorer", versionSearch: "MSIE" }, + { string: nav.userAgent, subString: "Trident", identity: "Explorer", versionSearch: "rv" }, { string: nav.userAgent, subString: "Gecko", identity: "Mozilla", versionSearch: "rv" }, { string: nav.userAgent, subString: "Mozilla", identity: "Netscape", versionSearch: "Mozilla" } ], diff --git a/test/SpecRunner.html b/test/SpecRunner.html index b9e97e4..e722ba6 100644 --- a/test/SpecRunner.html +++ b/test/SpecRunner.html @@ -25,6 +25,7 @@ + diff --git a/test/spec/browser-spec.js b/test/spec/browser-spec.js index b13609b..197147c 100644 --- a/test/spec/browser-spec.js +++ b/test/spec/browser-spec.js @@ -1,25 +1,45 @@ // @todo - can tests be dynamically created in Jasmine for this to work? -/* -var mock = create_mock() +var mock = create_mock(); +var _getTestHandler = function(testData, expectedResult) { + return function() { + mock.nav.userAgent = testData[3]; + mock.nav.appVersion = testData[2]; + mock.nav.platform = testData[1]; + mock.run_sess(); + + var browser_data = mock.win.session.browser; + expect(browser_data.browser).toEqual(expectedResult.browser); + expect(browser_data.version).toEqual(expectedResult.version); + expect(browser_data.os).toEqual(expectedResult.os); + }; +}; describe('IE Browser Names', function(){ var ie_tests = [ - ['mise 9', 'Win', '9.0', 'Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0)'], - ['mise 8 .net', 'Win', '8.0', 'Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0; .NET CLR 1.1.4322; .NET CLR 2.0.50727; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729; .NET4.0C; .NET4.0E)'], - ['mise 9', 'Win', '9.0', 'Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0)'], - ['mise 8 .net', 'Win', '8.0', 'Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0; .NET CLR 2.0.50727; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729)'], - ['mise 8 .net 2', 'Win', '8.0', 'Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0; .NET CLR 2.0.50727; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729)'] + ['msie 11', 'Win', '11.0', 'Mozilla/5.0 (Windows NT 6.3; Trident/7.0; rv 11.0) like Gecko'], + ['msie 10', 'Win', '10.0', 'Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; WOW64; Trident/6.0)'], + ['msie 9', 'Win', '9.0', 'Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0)'], + ['msie 8 .net', 'Win', '8.0', 'Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0; .NET CLR 1.1.4322; .NET CLR 2.0.50727; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729; .NET4.0C; .NET4.0E)'], + ['msie 9', 'Win', '9.0', 'Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0)'], + ['msie 8 .net', 'Win', '8.0', 'Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0; .NET CLR 2.0.50727; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729)'], + ['msie 8 .net 2', 'Win', '8.0', 'Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0; .NET CLR 2.0.50727; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729)'] + ]; + var ie_tests_expected_result = [ + { browser: 'Explorer', version: 11, os: 'Windows' }, + { browser: 'Explorer', version: 10, os: 'Windows' }, + { browser: 'Explorer', version: 9, os: 'Windows' }, + { browser: 'Explorer', version: 8, os: 'Windows' }, + { browser: 'Explorer', version: 9, os: 'Windows' }, + { browser: 'Explorer', version: 8, os: 'Windows' }, + { browser: 'Explorer', version: 8, os: 'Windows' }, ]; - for (var i = 0; i < ie_tests.length; i++){ + for (var i = 0; i < ie_tests.length; i++) { var ie_test = ie_tests[i]; - it(ie_test[0], function(){ - mock.nav.userAgent = ie_test[3]; - mock.nav.appVersion = ie_test[2]; - mock.run_sess() - }) + var expected_result = ie_tests_expected_result[i]; + it(ie_test[0], _getTestHandler(ie_test, expected_result)); } -}) +}); describe('Firefox Browser Names', function(){ var firefox_tests = [ @@ -28,7 +48,7 @@ describe('Firefox Browser Names', function(){ ['firefox 3.6', '3.6.24', 'Mozilla/5.0 (X11; U; Linux i686; sk; rv:1.9.2.24) Gecko/20111107 Ubuntu/10.04 (lucid) Firefox/3.6.24'], ['firefox beta', '11.0a2', 'Mozilla/5.0 (X11; Linux x86_64; rv:11.0a2) Gecko/20120107 Firefox/11.0a2'] ]; -}) +}); describe('Safari Browser Names', function(){ var safari_tests = [ @@ -38,14 +58,11 @@ describe('Safari Browser Names', function(){ ['5.1 ipad', '5.1', 'Mozilla/5.0 (iPad; CPU OS 5_0_1 like Mac OS X) AppleWebKit/534.46 (KHTML, like Gecko) Version/5.1 Mobile/9A405 Safari/7534.48.3'], ['4.1 iphone', '5.1', 'Mozilla/5.0 (iPhone; CPU iPhone OS 5_0_1 like Mac OS X) AppleWebKit/534.46 (KHTML, like Gecko) Version/5.1 Mobile/9A405 Safari/7534.48.3'] ]; - -}) - +}); describe('Chrome Browser Names', function(){ var chrome_tests = [ - + ]; -}) -*/ \ No newline at end of file +}); From ecea37e6f191882ec6aaef00fb1822a8ca566910 Mon Sep 17 00:00:00 2001 From: Ilya Tsuryev Date: Tue, 26 Aug 2014 11:49:47 +0300 Subject: [PATCH 45/53] Fix document body check --- session.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/session.js b/session.js index 55fe89b..c5c5a1b 100644 --- a/session.js +++ b/session.js @@ -406,7 +406,7 @@ var session_fetch = (function(win, doc, nav){ var element = doc.createElement("script"); element.type = "text/javascript"; element.src = url; - doc.getElementsByTagName("body")[0].appendChild(element); + (doc.body || doc.getElementsByTagName("body")[0] || doc.head).appendChild(element); }, package_obj: function (obj){ if(obj) { From 195d8c510033fc97496bb8552fc350eafe49593e Mon Sep 17 00:00:00 2001 From: Jury Razumau Date: Fri, 7 Aug 2015 17:45:06 +0300 Subject: [PATCH 46/53] Add Microsoft Edge support --- session.js | 1 + test/spec/browser-spec.js | 26 +++++++++++++++++++++++++- 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/session.js b/session.js index 7cb4776..49e63f9 100644 --- a/session.js +++ b/session.js @@ -152,6 +152,7 @@ var session_fetch = (function(win, doc, nav){ }, data: { browser: [ + { string: nav.userAgent, subString: "Edge", identity: "Edge"}, { string: nav.userAgent, subString: "Chrome", identity: "Chrome" }, { string: nav.userAgent, subString: "OmniWeb", versionSearch: "OmniWeb/", identity: "OmniWeb" }, { string: nav.vendor, subString: "Apple", identity: "Safari", versionSearch: "Version" }, diff --git a/test/spec/browser-spec.js b/test/spec/browser-spec.js index 197147c..b4e9d86 100644 --- a/test/spec/browser-spec.js +++ b/test/spec/browser-spec.js @@ -41,6 +41,21 @@ describe('IE Browser Names', function(){ } }); +describe('Edge Browser Names', function (){ + var edge_tests = [ + ['edge', 'Win', '12', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.135 Safari/537.36 Edge/12.10240'] + ]; + var edge_tests_expected_result = [ + { browser: 'Edge', version: 12.1024, os: 'Windows' } + ]; + for (var i = 0; i < edge_tests.length; i++) { + var edge_test = edge_tests[i]; + var expected_result = edge_tests_expected_result[i]; + it(edge_test[0], _getTestHandler(edge_test, expected_result)); + } + +}); + describe('Firefox Browser Names', function(){ var firefox_tests = [ ['firefox 9.0.1', '9.0.1', 'Mozilla/5.0 (Windows NT 5.1; rv:9.0.1) Gecko/20100101 Firefox/9.0.1'], @@ -63,6 +78,15 @@ describe('Safari Browser Names', function(){ describe('Chrome Browser Names', function(){ var chrome_tests = [ - + ['chrome 44', 'Win', '44', 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.125 Safari/537.36'], ]; + + var chrome_tests_expected_result = [ + { browser: 'Chrome', version: 44, os: 'Windows' } + ]; + for (var i = 0; i < chrome_tests.length; i++) { + var chrome_test = chrome_tests[i]; + var expected_result = chrome_tests_expected_result[i]; + it(chrome_test[0], _getTestHandler(chrome_test, expected_result)); + } }); From c11e6f4ad588f36d1516131c87eabf7ca37f8aee Mon Sep 17 00:00:00 2001 From: Anfa Abukar Date: Fri, 31 Mar 2017 15:33:06 -0400 Subject: [PATCH 47/53] removed version from android regexp --- session.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/session.js b/session.js index 49e63f9..0fecd79 100644 --- a/session.js +++ b/session.js @@ -217,7 +217,7 @@ var session_fetch = (function(win, doc, nav){ height: height }; device.is_tablet = !!nav.userAgent.match(/(iPad|SCH-I800|xoom|kindle)/i); - device.is_phone = !device.is_tablet && !!nav.userAgent.match(/(iPhone|iPod|blackberry|android 0.5|htc|lg|midp|mmp|mobile|nokia|opera mini|palm|pocket|psp|sgh|smartphone|symbian|treo mini|Playstation Portable|SonyEricsson|Samsung|MobileExplorer|PalmSource|Benq|Windows Phone|Windows Mobile|IEMobile|Windows CE|Nintendo Wii)/i); + device.is_phone = !device.is_tablet && !!nav.userAgent.match(/(iPhone|iPod|blackberry|android|htc|lg|midp|mmp|mobile|nokia|opera mini|palm|pocket|psp|sgh|smartphone|symbian|treo mini|Playstation Portable|SonyEricsson|Samsung|MobileExplorer|PalmSource|Benq|Windows Phone|Windows Mobile|IEMobile|Windows CE|Nintendo Wii)/i); device.is_mobile = device.is_tablet || device.is_phone; return device; }, From 66ba3d7366feee0d353096d4bd1c5063cd828328 Mon Sep 17 00:00:00 2001 From: SandeepJoelFreshdesk Date: Fri, 10 Aug 2018 16:55:21 +0530 Subject: [PATCH 48/53] Add npm package.json --- package.json | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 package.json diff --git a/package.json b/package.json new file mode 100644 index 0000000..9158349 --- /dev/null +++ b/package.json @@ -0,0 +1,25 @@ +{ + "name": "session.js", + "version": "0.4.1", + "description": "Gives information about the current session.", + "main": "session.js", + "directories": { + "test": "test" + }, + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/codejoust/session.js.git" + }, + "keywords": [ + "session" + ], + "author": "codejoust", + "license": "MIT", + "bugs": { + "url": "https://github.com/codejoust/session.js/issues" + }, + "homepage": "https://github.com/codejoust/session.js#readme" +} From f966e9a695ac5c52ed24ab289f9fce838a7a24a8 Mon Sep 17 00:00:00 2001 From: HacKan Date: Wed, 9 Oct 2019 01:48:44 -0300 Subject: [PATCH 49/53] Refactor demo and debug pages Fix missing character encoding and html lang tag. Refactor code structure and missplaced elements. Change script source for session.js to run locally. --- debug.html | 377 +++++++++++++++++++++++++++++++---------------------- demo.html | 345 ++++++++++++++++++++++++++++-------------------- 2 files changed, 422 insertions(+), 300 deletions(-) diff --git a/debug.html b/debug.html index b295e9a..928e9d4 100644 --- a/debug.html +++ b/debug.html @@ -1,177 +1,240 @@ - + - Demo for Session.js - - - + Session.js example usage + + + + -

Session.js Debug / Testing Page

-

Script Output:

-
+

Session.js Debug / Testing Page

+

Script Output:

+
     loading...
   
- - - + + - -

-  
+
+
+

+
-  
-  

Session.JS on GitHub by Iain

+ + +

Session.JS on GitHub by Iain

diff --git a/demo.html b/demo.html index 26c7146..fabe432 100644 --- a/demo.html +++ b/demo.html @@ -1,154 +1,213 @@ - - Demo for Session.js - + + Demo for Session.js + + -

Session.js Demo Page

-

Demo output:

-
+

Session.js Demo Page

+

Demo output:

+
     loading...
   
- -

Session.JS on GitHub by Iain

- +

Session.JS on GitHub by Iain

+ - + + From 09c84bfe6dc9b5d5fe900dd09980f3ce9e48757f Mon Sep 17 00:00:00 2001 From: HacKan Date: Wed, 9 Oct 2019 01:55:18 -0300 Subject: [PATCH 50/53] Refactor session.js Massive refactor to polish the code removing unused bits, changing `var` for `let`, fixing insecure http protocol for https and fixing architecture module that was broken because of a missnamed variable. Rename cookie to a more significative name: `session-js`. --- session.js | 972 +++++++++++++++++++++++++++++------------------------ 1 file changed, 529 insertions(+), 443 deletions(-) diff --git a/session.js b/session.js index 0fecd79..8ee5451 100644 --- a/session.js +++ b/session.js @@ -6,467 +6,553 @@ * This version uses google's jsapi library for location services. * For details, see: https://github.com/codejoust/session.js */ -var session_fetch = (function(win, doc, nav){ - 'use strict'; - // Changing the API Version invalidates olde cookies with previous api version tags. - var API_VERSION = 0.4; - // Settings: defaults - var options = { - // Use the HTML5 Geolocation API - // this ONLY returns lat & long, no city/address - use_html5_location: false, - // Attempts to use IPInfoDB if provided a valid key - // Get a key at http://ipinfodb.com/register.php - ipinfodb_key: false, - // Leaving true allows for fallback for both - // the HTML5 location and the IPInfoDB - gapi_location: true, - // Name of the location cookie (set blank to disable cookie) - // - WARNING: different providers use the same cookie - // - if switching providers, remember to use another cookie or provide checks for old cookies - location_cookie: "location", - // Location cookie expiration in hours - location_cookie_timeout: 5, - // Session expiration in days - session_timeout: 32, - // Session cookie name (set blank to disable cookie) - session_cookie: "first_session", - get_object: null, set_object: null // used for cookie session adaptors - // if null, will be reset to use cookies by default. - }; - - // Session object - var SessionRunner = function(){ - win.session = win.session || {}; - // Helper for querying. - // Usage: session.current_session.referrer_info.hostname.contains(['github.com','news.ycombinator.com']) - win.session.contains = function(other_str){ - if (typeof(other_str) === 'string'){ - return (this.indexOf(other_str) !== -1); } - for (var i = 0; i < other_str.length; i++){ - if (this.indexOf(other_str[i]) !== -1){ return true; } } - return false; }; - // Merge options - if (win.session && win.session.options) { - for (var option in win.session.options){ - options[option] = win.session.options[option]; } - } - // Modules to run - // If the module has arguments, - // it _needs_ to return a callback function. - var unloaded_modules = { - api_version: API_VERSION, - locale: modules.locale(), - current_session: modules.session(), - original_session: modules.session( - options.session_cookie, - options.session_timeout * 24 * 60 * 60 * 1000), - browser: modules.browser(), - plugins: modules.plugins(), - time: modules.time(), - device: modules.device() - }; - // Location switch - if (options.use_html5_location){ - unloaded_modules.location = modules.html5_location(); - } else if (options.ipinfodb_key){ - unloaded_modules.location = modules.ipinfodb_location(options.ipinfodb_key); - } else if (options.gapi_location){ - unloaded_modules.location = modules.gapi_location(); - } - // Cache win.session.start - if (win.session && win.session.start){ - var start = win.session.start; - } - // Set up checking, if all modules are ready - var asynchs = 0, module, result, - check_asynch = function(deinc){ - if (deinc){ asynchs--; } - if (asynchs === 0){ - // Run start calback - if (start){ start(win.session); } - } +let session_fetch = (function (win, doc, nav) { + 'use strict'; + // Changing the API Version invalidates old cookies with previous api version tags. + let API_VERSION = 0.4; + // Settings: defaults + let options = { + // Use the HTML5 Geolocation API + // this ONLY returns lat & long, no city/address + use_html5_location: false, + // Attempts to use IPInfoDB if provided a valid key + // Get a key at https://ipinfodb.com/register.php + ipinfodb_key: false, + // Leaving true allows for fallback for both + // the HTML5 location and the IPInfoDB + gapi_location: true, + // Name of the location cookie (set blank to disable cookie) + // - WARNING: different providers use the same cookie + // - if switching providers, remember to use another cookie or provide checks for old cookies + location_cookie: 'location', + // Location cookie expiration in hours + location_cookie_timeout: 5, + // Session expiration in days + session_timeout: 32, + // Session cookie name (set blank to disable cookie) + session_cookie: 'session-js', + get_object: null, set_object: null, // used for cookie session adaptors + // if null, will be reset to use cookies by default. }; - win.session = {}; - // Run asynchronous methods - for (var name in unloaded_modules){ - module = unloaded_modules[name]; - if (typeof module === "function"){ - try { - module(function(data){ - win.session[name] = data; - check_asynch(true); - }); - asynchs++; - } catch(err){ - if (win.console && typeof(console.log) === "function"){ - console.log(err); check_asynch(true); } - } - } else { - win.session[name] = module; - } } - check_asynch(); - }; - - // Browser (and OS) detection - var browser = { - detect: function(){ - var ret = { - browser: this.search(this.data.browser), - version: this.search(nav.userAgent) || this.search(nav.appVersion), - os: this.search(this.data.os) - }; - if (ret.os=='Linux'){ - var distros = ['CentOS','Debian','Fedora','Gentoo','Mandriva','Mageia','Red Hat','Slackware','SUSE','Turbolinux','Ubuntu']; - for (var i = 0; i < distros.length;i++){ - if (nav.userAgent.toLowerCase().match(distros[i].toLowerCase())){ - ret.distro = distros[i]; - break; - } - } - } - return ret; - }, - search: function(data) { - if (typeof data === "object"){ - // search for string match - for(var i = 0; i < data.length; i++) { - var dataString = data[i].string, - dataProp = data[i].prop; - this.version_string = data[i].versionSearch || data[i].identity; - if (dataString){ - if (dataString.indexOf(data[i].subString) != -1){ - return data[i].identity; + // Session object + let SessionRunner = function () { + win.session = win.session || {}; + // Helper for querying. + // Usage: session.current_session.referrer_info.hostname.contains(['github.com','news.ycombinator.com']) + win.session.contains = function (other_str) { + if (typeof (other_str) === 'string') { + return (this.indexOf(other_str) !== -1); + } + for (let i = 0; i < other_str.length; i++) { + if (this.indexOf(other_str[i]) !== -1) { + return true; + } + } + return false; + }; + // Merge options + if (win.session && win.session.options) { + for (let option in win.session.options) { + options[option] = win.session.options[option]; } - } else if (dataProp){ - return data[i].identity; - } } - } else { - // search for version number - var index = data.indexOf(this.version_string); - if (index == -1) return; - return parseFloat(data.substr(index + this.version_string.length + 1)); - } - }, - data: { - browser: [ - { string: nav.userAgent, subString: "Edge", identity: "Edge"}, - { string: nav.userAgent, subString: "Chrome", identity: "Chrome" }, - { string: nav.userAgent, subString: "OmniWeb", versionSearch: "OmniWeb/", identity: "OmniWeb" }, - { string: nav.vendor, subString: "Apple", identity: "Safari", versionSearch: "Version" }, - { prop: win.opera, identity: "Opera", versionSearch: "Version" }, - { string: nav.vendor, subString: "iCab",identity: "iCab" }, - { string: nav.vendor, subString: "KDE", identity: "Konqueror" }, - { string: nav.userAgent, subString: "Firefox", identity: "Firefox" }, - { string: nav.vendor, subString: "Camino", identity: "Camino" }, - { string: nav.userAgent, subString: "Netscape", identity: "Netscape" }, - { string: nav.userAgent, subString: "MSIE", identity: "Explorer", versionSearch: "MSIE" }, - { string: nav.userAgent, subString: "Trident", identity: "Explorer", versionSearch: "rv" }, - { string: nav.userAgent, subString: "Gecko", identity: "Mozilla", versionSearch: "rv" }, - { string: nav.userAgent, subString: "Mozilla", identity: "Netscape", versionSearch: "Mozilla" } - ], - os: [ - { string: nav.platform, subString: "Win", identity: "Windows" }, - { string: nav.platform, subString: "Mac", identity: "Mac" }, - { string: nav.userAgent, subString: "iPhone", identity: "iPhone/iPod" }, - { string: nav.userAgent, subString: "iPad", identity: "iPad" }, - { string: nav.userAgent, subString: "Android", identity: "Android" }, - { string: nav.platform, subString: "Linux", identity: "Linux" } - ]} - }; - - var modules = { - browser: function(){ - return browser.detect(); - }, - time: function(){ - // split date and grab timezone estimation. - // timezone estimation: http://www.onlineaspect.com/2007/06/08/auto-detect-a-time-zone-with-javascript/ - var d1 = new Date(), d2 = new Date(); - d1.setMonth(0); d1.setDate(1); d2.setMonth(6); d2.setDate(1); - return({tz_offset: -(new Date().getTimezoneOffset()) / 60, observes_dst: (d1.getTimezoneOffset() !== d2.getTimezoneOffset()) }); - // Gives a browser estimation, not guaranteed to be correct. - }, - locale: function() { - var lang = (( - nav.language || - nav.browserLanguage || - nav.systemLanguage || - nav.userLanguage - ) || '').split("-"); - if (lang.length == 2){ - return { country: lang[1].toLowerCase(), lang: lang[0].toLowerCase() }; - } else if (lang) { - return {lang: lang[0].toLowerCase(), country: null }; - } else { return{lang: null, country: null }; } - }, - device: function() { - var device = { - screen: { - width: win.screen.width, - height: win.screen.height + // Modules to run + // If the module has arguments, + // it _needs_ to return a callback function. + let unloaded_modules = { + api_version: API_VERSION, + locale: modules.locale(), + current_session: modules.session( + null, + null, + ), + original_session: modules.session( + options.session_cookie, + options.session_timeout * 24 * 60 * 60 * 1000, + ), + browser: modules.browser(), + plugins: modules.plugins(), + time: modules.time(), + device: modules.device(), + architecture: modules.architecture(), + }; + // Location switch + if (options.use_html5_location) { + unloaded_modules.location = modules.html5_location(); + } else if (options.ipinfodb_key) { + unloaded_modules.location = modules.ipinfodb_location(options.ipinfodb_key); + } else if (options.gapi_location) { + unloaded_modules.location = modules.gapi_location(); } - }; - var width, height; - try{ width = win.innerWidth || doc.documentElement.clientWidth || doc.body.clientWidth; } catch(e) { width = 0; } - try{ height = win.innerHeight || doc.documentElement.clientHeight || doc.body.clientHeight; } catch(e) { height = 0; } - device.viewport = { - width: width, - height: height - }; - device.is_tablet = !!nav.userAgent.match(/(iPad|SCH-I800|xoom|kindle)/i); - device.is_phone = !device.is_tablet && !!nav.userAgent.match(/(iPhone|iPod|blackberry|android|htc|lg|midp|mmp|mobile|nokia|opera mini|palm|pocket|psp|sgh|smartphone|symbian|treo mini|Playstation Portable|SonyEricsson|Samsung|MobileExplorer|PalmSource|Benq|Windows Phone|Windows Mobile|IEMobile|Windows CE|Nintendo Wii)/i); - device.is_mobile = device.is_tablet || device.is_phone; - return device; - }, - plugins: function(){ - var check_plugin = function(name){ - if (nav.plugins){ - var plugin, i = 0, length = nav.plugins.length; - for (; i < length; i++ ){ - plugin = nav.plugins[i]; - if (plugin && plugin.name && plugin.name.toLowerCase().indexOf(name) !== -1){ - return true; - } } - return false; - } return false; - }; - var check_activex_flash = function(versions){ - var found = true; - for (var i = 0; i < versions.length; i++){ - try { - var obj = new ActiveXObject("ShockwaveFlash.ShockwaveFlash" + versions[i]) - , found = !0; - } catch (e){ /* nil */ } - if (found) return true; + // Cache win.session.start + let start; + if (win.session && win.session.start) { + start = win.session.start; } - return false; - } - return { - flash: check_plugin("flash") || check_activex_flash(['.7','.6','']), - silverlight: check_plugin("silverlight"), - java: check_plugin("java"), - quicktime: check_plugin("quicktime") - }; - }, - session: function (cookie, expires){ - var session = util.get_obj(cookie); - if (session == null){ - session = { - visits: 1, - start: new Date().getTime(), last_visit: new Date().getTime(), - url: win.location.href, path: win.location.pathname, - referrer: doc.referrer, referrer_info: util.parse_url(doc.referrer), - search: { engine: null, query: null } + // Set up checking, if all modules are ready + let asynchs = 0; + let module; + let check_asynch = function (deinc) { + if (deinc) { + asynchs--; + } + if (asynchs === 0) { + // Run start calback + if (start) { + start(win.session); + } + } }; - var search_engines = [ - { name: "Google", host: "google", query: "q" }, - { name: "Bing", host: "bing.com", query: "q" }, - { name: "Yahoo", host: "search.yahoo", query: "p" }, - { name: "AOL", host: "search.aol", query: "q" }, - { name: "Ask", host: "ask.com", query: "q" }, - { name: "Baidu", host: "baidu.com", query: "wd" } - ], length = search_engines.length, - engine, match, i = 0, - fallbacks = 'q query term p wd query text'.split(' '); - for (i = 0; i < length; i++){ - engine = search_engines[i]; - if (session.referrer_info.host.indexOf(engine.host) !== -1){ - session.search.engine = engine.name; - session.search.query = session.referrer_info.query[engine.query]; - session.search.terms = session.search.query ? session.search.query.split(" ") : null; - break; - } + win.session = {}; + // Run asynchronous methods + for (let name in unloaded_modules) { + module = unloaded_modules[name]; + if (typeof module === 'function') { + try { + module(function (data) { + win.session[name] = data; + check_asynch(true); + }); + asynchs++; + } catch (err) { + if (win.console && typeof (console.log) === 'function') { + console.log(err); + check_asynch(true); + } + } + } else { + win.session[name] = module; + } } - if (session.search.engine === null && session.referrer_info.search.length > 1){ - for (i = 0; i < fallbacks.length; i++){ - var terms = session.referrer_info.query[fallbacks[i]]; - if (terms){ - session.search.engine = "Unknown"; - session.search.query = terms; session.search.terms = terms.split(" "); - break; + check_asynch(); + }; + + + // Browser (and OS) detection + // noinspection JSUnresolvedVariable + let browser = { + detect: function () { + let ret = { + browser: this.search(this.data.browser), + version: this.search(nav.userAgent) || this.search(nav.appVersion), + os: this.search(this.data.os) + }; + if (ret.os === 'Linux') { + let distros = ['CentOS', 'Debian', 'Fedora', 'Gentoo', 'Mandriva', 'Mageia', 'Red Hat', 'Slackware', 'SUSE', 'Turbolinux', 'Ubuntu']; + for (let i = 0; i < distros.length; i++) { + if (nav.userAgent.toLowerCase().match(distros[i].toLowerCase())) { + ret.distro = distros[i]; + break; + } + } + } + return ret; + }, + search: function (data) { + if (typeof data === 'object') { + // search for string match + for (let i = 0; i < data.length; i++) { + let dataString = data[i].string, + dataProp = data[i].prop; + this.version_string = data[i].versionSearch || data[i].identity; + if (dataString) { + if (dataString.indexOf(data[i].subString) !== -1) { + return data[i].identity; + } + } else if (dataProp) { + return data[i].identity; + } + } + } else { + // search for version number + let index = data.indexOf(this.version_string); + if (index === -1) return; + return parseFloat(data.substr(index + this.version_string.length + 1)); } - } + }, + data: { + browser: [ + {string: nav.userAgent, subString: 'Edge', identity: 'Edge'}, + {string: nav.userAgent, subString: 'Chrome', identity: 'Chrome'}, + {string: nav.userAgent, subString: 'OmniWeb', versionSearch: 'OmniWeb/', identity: 'OmniWeb'}, + {string: nav.vendor, subString: 'Apple', identity: 'Safari', versionSearch: 'Version'}, + {prop: win.opera, identity: 'Opera', versionSearch: 'Version'}, + {string: nav.vendor, subString: 'iCab', identity: 'iCab'}, + {string: nav.vendor, subString: 'KDE', identity: 'Konqueror'}, + {string: nav.userAgent, subString: 'Firefox', identity: 'Firefox'}, + {string: nav.vendor, subString: 'Camino', identity: 'Camino'}, + {string: nav.userAgent, subString: 'Netscape', identity: 'Netscape'}, + {string: nav.userAgent, subString: 'MSIE', identity: 'Explorer', versionSearch: 'MSIE'}, + {string: nav.userAgent, subString: 'Trident', identity: 'Explorer', versionSearch: 'rv'}, + {string: nav.userAgent, subString: 'Gecko', identity: 'Mozilla', versionSearch: 'rv'}, + {string: nav.userAgent, subString: 'Mozilla', identity: 'Netscape', versionSearch: 'Mozilla'}, + ], + os: [ + {string: nav.platform, subString: 'Win', identity: 'Windows'}, + {string: nav.platform, subString: 'Mac', identity: 'Mac'}, + {string: nav.userAgent, subString: 'iPhone', identity: 'iPhone/iPod'}, + {string: nav.userAgent, subString: 'iPad', identity: 'iPad'}, + {string: nav.userAgent, subString: 'Android', identity: 'Android'}, + {string: nav.platform, subString: 'Linux', identity: 'Linux'}, + ] } - } else { - session.prev_visit = session.last_visit; - session.last_visit = new Date().getTime(); - session.visits++; - session.time_since_last_visit = session.last_visit - session.prev_visit; - } - util.set_obj(cookie, session, expires); - return session; - }, - html5_location: function(){ - return function(callback){ - nav.geolocation.getCurrentPosition(function(pos){ - pos.source = 'html5'; - callback(pos); - }, function(err) { - if (options.gapi_location){ - modules.gapi_location()(callback); - } else { - callback({error: true, source: 'html5'}); } - }); - }; - }, - gapi_location: function(){ - return function(callback){ - var location = util.get_obj(options.location_cookie); - if (!location || location.source !== 'google'){ - win.gloader_ready = function() { - if ("google" in win){ - if (win.google.loader.ClientLocation){ - win.google.loader.ClientLocation.source = "google"; - callback(win.google.loader.ClientLocation); - } else { - callback({error: true, source: "google"}); - } - util.set_obj( - options.location_cookie, - win.google.loader.ClientLocation, - options.location_cookie_timeout * 60 * 60 * 1000); - }}; - util.embed_script("https://www.google.com/jsapi?callback=gloader_ready"); - } else { - callback(location); - }}; - }, - architecture: function(){ - var arch = n.userAgent.match(/x86_64|Win64|WOW64|x86-64|x64\;|AMD64|amd64/) || - (n.cpuClass === 'x64') ? 'x64' : 'x86'; - return { - arch: arch, - is_x64: arch == 'x64', - is_x86: arch == 'x68' - } - }, - ipinfodb_location: function(api_key){ - return function (callback){ - var location_cookie = util.get_obj(options.location_cookie); - if (!location_cookie && location_cookie.source === 'ipinfodb'){ - win.ipinfocb = function(data){ - if (data.statusCode === "OK"){ - data.source = "ipinfodb"; - util.set_obj( - options.location_cookie, - data, - options.location_cookie * 60 * 60 * 1000); - callback(data); - } else { - if (options.gapi_location){ return modules.gapi_location()(callback); } - else { callback({error: true, source: "ipinfodb", message: data.statusMessage}); } - }}; - util.embed_script("http://api.ipinfodb.com/v3/ip-city/?key=" + api_key + "&format=json&callback=ipinfocb"); - } else { callback(location_cookie); } - }} - }; + }; - // Utilities - var util = { - parse_url: function(url_str){ - var a = doc.createElement("a"), query = {}; - a.href = url_str; var query_str = a.search.substr(1); - // Disassemble query string - if (query_str != ''){ - var pairs = query_str.split("&"), i = 0, - length = pairs.length, parts; - for (; i < length; i++){ - parts = pairs[i].split("="); - if (parts.length === 2){ - query[parts[0]] = decodeURI(parts[1]); } + let modules = { + browser: function () { + return browser.detect(); + }, + time: function () { + // split date and grab timezone estimation. + // timezone estimation: https://www.onlineaspect.com/2007/06/08/auto-detect-a-time-zone-with-javascript/ + let d1 = new Date(), d2 = new Date(); + d1.setMonth(0); + d1.setDate(1); + d2.setMonth(6); + d2.setDate(1); + return ({ + tz_offset: -(new Date().getTimezoneOffset()) / 60, + observes_dst: (d1.getTimezoneOffset() !== d2.getTimezoneOffset()) + }); + // Gives a browser estimation, not guaranteed to be correct. + }, + locale: function () { + let lang = (( + nav.language || + (nav.hasOwnProperty('browserLanguage') + ? nav.browserLanguage + : nav.systemLanguage) + ) || '').split('-'); + if (lang.length === 2) { + return {country: lang[1].toLowerCase(), lang: lang[0].toLowerCase()}; + } else if (lang) { + return {lang: lang[0].toLowerCase(), country: null}; + } else { + return {lang: null, country: null}; + } + }, + device: function () { + let device = { + screen: { + width: win.screen.width, + height: win.screen.height + } + }; + let width, height; + try { + width = win.innerWidth || doc.documentElement.clientWidth || doc.body.clientWidth; + } catch (e) { + width = 0; + } + try { + height = win.innerHeight || doc.documentElement.clientHeight || doc.body.clientHeight; + } catch (e) { + height = 0; + } + device.viewport = { + width: width, + height: height + }; + device.is_tablet = !!nav.userAgent.match(/(iPad|SCH-I800|xoom|kindle)/i); + device.is_phone = !device.is_tablet && !!nav.userAgent.match(/(iPhone|iPod|blackberry|android|htc|lg|midp|mmp|mobile|nokia|opera mini|palm|pocket|psp|sgh|smartphone|symbian|treo mini|Playstation Portable|SonyEricsson|Samsung|MobileExplorer|PalmSource|Benq|Windows Phone|Windows Mobile|IEMobile|Windows CE|Nintendo Wii)/i); + device.is_mobile = device.is_tablet || device.is_phone; + return device; + }, + plugins: function () { + let check_plugin = function (name) { + if (nav.plugins) { + let plugin, i = 0, length = nav.plugins.length; + for (; i < length; i++) { + plugin = nav.plugins[i]; + if (plugin && plugin.name && plugin.name.toLowerCase().indexOf(name) !== -1) { + return true; + } + } + return false; + } + return false; + }; + let check_activex_flash = function (versions) { + let found = false; + for (let i = 0; i < versions.length; i++) { + try { + new ActiveXObject('ShockwaveFlash.ShockwaveFlash' + versions[i]); + found = true; + break; + } catch (e) { /* nil */ + } + } + return found; + }; + return { + flash: check_plugin('flash') || check_activex_flash(['.7', '.6', '']), + silverlight: check_plugin('silverlight'), + java: check_plugin('java'), + quicktime: check_plugin('quicktime') + }; + }, + session: function (cookie, expires) { + let session = util.get_obj(cookie); + if (session == null) { + session = { + visits: 1, + start: new Date().getTime(), last_visit: new Date().getTime(), + url: win.location.href, path: win.location.pathname, + referrer: doc.referrer, referrer_info: util.parse_url(doc.referrer), + search: {engine: null, query: null}, + }; + let search_engines = [ + {name: 'Google', host: 'google', query: 'q'}, + {name: 'Bing', host: 'bing.com', query: 'q'}, + {name: 'Yahoo', host: 'search.yahoo', query: 'p'}, + {name: 'AOL', host: 'search.aol', query: 'q'}, + {name: 'Ask', host: 'ask.com', query: 'q'}, + {name: 'Baidu', host: 'baidu.com', query: 'wd'}, + ]; + let length = search_engines.length; + let engine; + let fallbacks = 'q query term p wd query text'.split(' '); + for (let i = 0; i < length; i++) { + engine = search_engines[i]; + if (session.referrer_info.host.indexOf(engine.host) !== -1) { + session.search.engine = engine.name; + session.search.query = session.referrer_info.query[engine.query]; + session.search.terms = session.search.query ? session.search.query.split(' ') : null; + break; + } + } + if (session.search.engine === null && session.referrer_info.search.length > 1) { + for (let i = 0; i < fallbacks.length; i++) { + let terms = session.referrer_info.query[fallbacks[i]]; + if (terms) { + session.search.engine = 'Unknown'; + session.search.query = terms; + session.search.terms = terms.split(' '); + break; + } + } + } + } else { + session.prev_visit = session.last_visit; + session.last_visit = new Date().getTime(); + session.visits++; + session.time_since_last_visit = session.last_visit - session.prev_visit; + } + util.set_obj(cookie, session, expires); + return session; + }, + html5_location: function () { + return function (callback) { + nav.geolocation.getCurrentPosition(function (pos) { + pos.source = 'html5'; + callback(pos); + }, function (err) { + if (options.gapi_location) { + modules.gapi_location()(callback); + } else { + callback({ + error: true, + source: 'html5', + message: err.message, + }); + } + }); + }; + }, + gapi_location: function () { + return function (callback) { + let location = util.get_obj(options.location_cookie); + if (!location || location.source !== 'google') { + win.gloader_ready = function () { + if ('google' in win + && win.google.hasOwnProperty('loader') + && win.google.loader.hasOwnProperty('ClientLocation') + ) { + if (win.google.loader.ClientLocation) { + win.google.loader.ClientLocation.source = 'google'; + callback(win.google.loader.ClientLocation); + } else { + callback({error: true, source: 'google'}); + } + // noinspection JSUnresolvedVariable + util.set_obj( + options.location_cookie, + win.google.loader.ClientLocation, + options.location_cookie_timeout * 60 * 60 * 1000, + ); + } + }; + util.embed_script('https://www.google.com/jsapi?callback=gloader_ready'); + } else { + callback(location); + } + }; + }, + ipinfodb_location: function (api_key) { + return function (callback) { + let location_cookie = util.get_obj(options.location_cookie); + if (!location_cookie && location_cookie.source === 'ipinfodb') { + win.ipinfocb = function (data) { + if (data.statusCode === 'OK') { + data.source = 'ipinfodb'; + util.set_obj( + options.location_cookie, + data, + options.location_cookie * 60 * 60 * 1000); + callback(data); + } else { + if (options.gapi_location) { + return modules.gapi_location()(callback); + } else { + callback({error: true, source: 'ipinfodb', message: data.statusMessage}); + } + } + }; + util.embed_script('https://api.ipinfodb.com/v3/ip-city/?key=' + api_key + '&format=json&callback=ipinfocb'); + } else { + callback(location_cookie); + } + } + }, + architecture: function () { + let arch = nav.userAgent.match(/x86_64|Win64|WOW64|x86-64|x64;|AMD64|amd64/) || + (nav.hasOwnProperty('cpuClass') && nav.cpuClass === 'x64') ? 'x64' : 'x86'; + return { + arch: arch, + is_x64: arch === 'x64', + is_x86: arch === 'x68', + } + }, + }; + + // Utilities + let util = { + parse_url: function (url_str) { + let a = doc.createElement('a'), query = {}; + a.href = url_str; + let query_str = a.search.substr(1); + // Disassemble query string + if (query_str !== '') { + let pairs = query_str.split('&'), i = 0, + length = pairs.length, parts; + for (; i < length; i++) { + parts = pairs[i].split('='); + if (parts.length === 2) { + query[parts[0]] = decodeURI(parts[1]); + } + } + } + return { + host: a.host, + path: a.pathname, + protocol: a.protocol, + port: a.port === '' ? 80 : a.port, + search: a.search, + query: query + } + }, + set_cookie: function (cname, value, expires, options) { // from jquery.cookie.js + if (!cname) { + return null; + } + if (!options) { + options = {}; + } + if (value === null || value === undefined) { + expires = -1; + } + if (expires) { + options.expires = (new Date().getTime()) + expires; + } + return (doc.cookie = [ + encodeURIComponent(cname), '=', + encodeURIComponent(String(value)), + options.expires ? '; expires=' + new Date(options.expires).toUTCString() : '', // use expires attribute, max-age is not supported by IE + '; path=' + (options.path ? options.path : '/'), + options.domain ? '; domain=' + options.domain : '', + (win.location && win.location.protocol === 'https:') ? '; secure' : '' + ].join('')); + }, + get_cookie: function (cookie_name, result) { // from jquery.cookie.js + return (result = new RegExp('(?:^|; )' + encodeURIComponent(cookie_name) + '=([^;]*)').exec(doc.cookie)) ? decodeURIComponent(result[1]) : null; + }, + embed_script: function (url) { + let element = doc.createElement('script'); + element.type = 'text/javascript'; + element.src = url; + (doc.body || doc.getElementsByTagName('body')[0] || doc.head).appendChild(element); + }, + package_obj: function (obj) { + if (obj) { + obj.version = API_VERSION; + let ret = JSON.stringify(obj); + delete obj.version; + return ret; + } + }, + set_obj: function (cname, value, expires, options) { + util.set_cookie(cname, util.package_obj(value), expires, options); + }, + get_obj: function (cookie_name) { + let obj; + try { + obj = JSON.parse(util.get_cookie(cookie_name)); + } catch (e) { + console.log(e); + } + if (obj && obj.version === API_VERSION) { + delete obj.version; + return obj; + } } - } - return { - host: a.host, - path: a.pathname, - protocol: a.protocol, - port: a.port === '' ? 80 : a.port, - search: a.search, - query: query } - }, - set_cookie: function(cname, value, expires, options){ // from jquery.cookie.js - if (!cname){ return null; } - if (!options){ options = {}; } - if (value === null || value === undefined){ expires = -1; } - if (expires){ options.expires = (new Date().getTime()) + expires; } - return (doc.cookie = [ - encodeURIComponent(cname), '=', - encodeURIComponent(String(value)), - options.expires ? '; expires=' + new Date(options.expires).toUTCString() : '', // use expires attribute, max-age is not supported by IE - '; path=' + (options.path ? options.path : '/'), - options.domain ? '; domain=' + options.domain : '', - (win.location && win.location.protocol === 'https:') ? '; secure' : '' - ].join('')); - }, - get_cookie: function(cookie_name, result){ // from jquery.cookie.js - return (result = new RegExp('(?:^|; )' + encodeURIComponent(cookie_name) + '=([^;]*)').exec(doc.cookie)) ? decodeURIComponent(result[1]) : null; - }, - embed_script: function(url){ - var element = doc.createElement("script"); - element.type = "text/javascript"; - element.src = url; - (doc.body || doc.getElementsByTagName("body")[0] || doc.head).appendChild(element); - }, - package_obj: function (obj){ - if(obj) { - obj.version = API_VERSION; - var ret = JSON.stringify(obj); - delete obj.version; - return ret; - } - }, - set_obj: function(cname, value, expires, options){ - util.set_cookie(cname, util.package_obj(value), expires, options); - }, - get_obj: function(cookie_name){ - var obj; - try { obj = JSON.parse(util.get_cookie(cookie_name)); } catch(e){} - if (obj && obj.version == API_VERSION){ - delete obj.version; return obj; - } - } - }; + }; - // cookie options override - if (options.get_object != null){ - util.get_obj = options['get_object']; } - if (options.set_object != null){ - util.set_obj = options['set_object']; } + // cookie options override + if (options.get_object != null) { + util.get_obj = options['get_object']; + } + if (options.set_object != null) { + util.set_obj = options['set_object']; + } - // JSON - var JSON = { - parse: (win.JSON && win.JSON.parse) || function(data){ - if (typeof data !== "string" || !data){ return null; } - return (new Function("return " + data))(); - }, - stringify: (win.JSON && win.JSON.stringify) || function(object){ - var type = typeof object; - if (type !== "object" || object === null) { - if (type === "string"){ return '"' + object + '"'; } - } else { - var k, v, json = [], - isArray = (object && object.constructor === Array); - for (k in object ) { - v = object[k]; type = typeof v; - if (type === "string") - v = '"' + v + '"'; - else if (type === "object" && v !== null) - v = this.stringify(v); - json.push((isArray ? "" : '"' + k + '":') + v); + // JSON + let JSON = { + parse: (win.hasOwnProperty('JSON') && win.JSON && win.JSON.parse) || function (data) { + if (typeof data !== 'string' || !data) { + return null; + } + return (new Function('return ' + data))(); + }, + stringify: (win.hasOwnProperty('JSON') && win.JSON && win.JSON.stringify) || function (object) { + let type = typeof object; + if (type !== 'object' || object === null) { + if (type === 'string') { + return '"' + object + '"'; + } + } else { + let k, v, json = [], isArray = (object && object.constructor === Array); + for (k in object) { + v = object[k]; + type = typeof v; + if (type === 'string') + v = '"' + v + '"'; + else if (type === 'object' && v !== null) + v = this.stringify(v); + json.push((isArray ? "" : '"' + k + '":') + v); + } + return (isArray ? '[' : '{') + json.join(',') + (isArray ? ']' : '}'); + } } - return (isArray ? "[" : "{") + json.join(",") + (isArray ? "]" : "}"); - } } }; + }; - // Initialize SessionRunner - SessionRunner(); + // Initialize SessionRunner + SessionRunner(); }); // Switch for testing purposes. -if (typeof(window.exports) === 'undefined'){ - session_fetch(window, document, navigator); +if (typeof (window.exports) === 'undefined') { + session_fetch(window, document, navigator); } else { - window.exports.session = session_fetch; + window.exports.session = session_fetch; } From ca6c9c2f6aef7c79acc8002070389c85d35e5933 Mon Sep 17 00:00:00 2001 From: HacKan Date: Wed, 9 Oct 2019 02:01:54 -0300 Subject: [PATCH 51/53] Add a tracker property This property allows to track the session from external libs or a backend. Once set, it will be written to the session and kept in the cookie. --- session.js | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/session.js b/session.js index 8ee5451..4e27ab2 100644 --- a/session.js +++ b/session.js @@ -33,6 +33,9 @@ let session_fetch = (function (win, doc, nav) { session_cookie: 'session-js', get_object: null, set_object: null, // used for cookie session adaptors // if null, will be reset to use cookies by default. + // Tracker ID: initialize with a random string so that repeated visits are + // easily tracked + tracker_id: null, }; // Session object @@ -66,10 +69,12 @@ let session_fetch = (function (win, doc, nav) { current_session: modules.session( null, null, + options.tracker_id, ), original_session: modules.session( options.session_cookie, options.session_timeout * 24 * 60 * 60 * 1000, + options.tracker_id, ), browser: modules.browser(), plugins: modules.plugins(), @@ -292,10 +297,11 @@ let session_fetch = (function (win, doc, nav) { quicktime: check_plugin('quicktime') }; }, - session: function (cookie, expires) { + session: function (cookie, expires, tracker = null) { let session = util.get_obj(cookie); if (session == null) { session = { + tracker: tracker, visits: 1, start: new Date().getTime(), last_visit: new Date().getTime(), url: win.location.href, path: win.location.pathname, @@ -334,6 +340,10 @@ let session_fetch = (function (win, doc, nav) { } } } else { + // Could've been initialized w/o id, so check and set + if (tracker && !session.tracker) { + session.tracker = tracker; + } session.prev_visit = session.last_visit; session.last_visit = new Date().getTime(); session.visits++; From 640779a0a8e9f684a2c3d408b2a4c2436b06d95b Mon Sep 17 00:00:00 2001 From: HacKan Date: Wed, 9 Oct 2019 02:04:34 -0300 Subject: [PATCH 52/53] Add extra client tracking with on/off switch Create a switch named extra that when set records extra client information. It was added from the demo page. --- session.js | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/session.js b/session.js index 4e27ab2..2253ca9 100644 --- a/session.js +++ b/session.js @@ -36,6 +36,8 @@ let session_fetch = (function (win, doc, nav) { // Tracker ID: initialize with a random string so that repeated visits are // easily tracked tracker_id: null, + // Set to true to record extra client data + extra: false, }; // Session object @@ -90,6 +92,10 @@ let session_fetch = (function (win, doc, nav) { } else if (options.gapi_location) { unloaded_modules.location = modules.gapi_location(); } + // Extra switch + if (options.extra) { + unloaded_modules.extra = modules.extra_data() + } // Cache win.session.start let start; if (win.session && win.session.start) { @@ -434,6 +440,27 @@ let session_fetch = (function (win, doc, nav) { is_x86: arch === 'x68', } }, + extra_data: function () { + let nav = {}; + for (let obj in navigator) { + if (typeof (navigator[obj]) === 'string') { + nav[obj] = navigator[obj]; + } + } + return { + navigator: nav, + document: { + referrer: document.referrer, + clientWidth: document.documentElement.clientWidth, + clientHeight: document.documentElement.clientHeight, + }, + window: { + innerWidth: window.innerWidth, + innerHeight: window.innerHeight, + location: window.location.href, + }, + } + } }; // Utilities From 7af1e85580660463c4912a7d057bf9fad855a07b Mon Sep 17 00:00:00 2001 From: HacKan Date: Wed, 9 Oct 2019 02:09:13 -0300 Subject: [PATCH 53/53] Add minified script --- session.min.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/session.min.js b/session.min.js index 85b992c..8899030 100644 --- a/session.min.js +++ b/session.min.js @@ -1 +1 @@ -var session_fetch=function(win,doc,nav){"use strict";var API_VERSION=.4;var options={use_html5_location:false,ipinfodb_key:false,gapi_location:true,location_cookie:"location",location_cookie_timeout:5,session_timeout:32,session_cookie:"first_session",get_object:null,set_object:null};var SessionRunner=function(){win.session=win.session||{};win.session.contains=function(other_str){if(typeof other_str==="string"){return this.indexOf(other_str)!==-1}for(var i=0;i1){for(i=0;i1){for(let i=0;i