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/README.md b/README.md index 021b0e2..5408329 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ Quick Example: if (session.first_session.visits > 1){ alert('Hi again from ' + session.location.address.city); } else { - if (session.current_session.referrer_info.host.contains('facebook')){ + if (session.contains(session.current_session.referrer_info.host, 'facebook')){ alert('Hi there from '+ session.location.address.city +'. How about liking us on facebook?'); } else if (session.current_session.search.engine){ alert('Did you find what you were looking for from ' + session.current_session.search.engine + '?'); diff --git a/debug.html b/debug.html new file mode 100644 index 0000000..928e9d4 --- /dev/null +++ b/debug.html @@ -0,0 +1,240 @@ + + + + Session.js example usage + + + + + + + +

Session.js Debug / Testing Page

+

Script Output:

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

+
+
+

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

+ - + + 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" +} diff --git a/session.js b/session.js index 96775ab..2253ca9 100644 --- a/session.js +++ b/session.js @@ -6,412 +6,590 @@ * 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){ - // 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" - }; - - // 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) { - 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. - 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. + // 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, }; - 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(){ - return { - browser: this.search(this.data.browser), - version: this.search(nav.userAgent) || this.search(nav.appVersion), - os: this.search(this.data.os) - } }, - 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); } - } else if (dataProp){ - return data[i].identity; - } + 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]; + } + } + // 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, + 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(), + 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(); + } + // Extra switch + if (options.extra) { + unloaded_modules.extra = modules.extra_data() } - } 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: "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: "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", identitiy: "iPad" }, - { string: nav.platform, subString: "Linux", identity: "Linux" }, - { string: nav.userAgent, subString: "Android", identity: "Android" } - ]} - }; - - 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 + // Cache win.session.start + let start; + if (win.session && win.session.start) { + start = win.session.start; } - }; - device.viewport = { - 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.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(){ - 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; - } - return { - flash: check_plugin("flash"), - 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; + } + } + 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'}, + ] } - 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; + }; + + 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, 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, + 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 { + // 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++; + 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', + } + }, + 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, + }, } - } } - } else { - session.last_visit = new Date().getTime(); - session.visits++; - } - util.set_cookie(cookie, util.package_obj(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_cookie( - 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){ - var location_cookie = util.get_obj(options.location_cookie); - if (location_cookie && location_cookie.source === 'ipinfodb'){ callback(location_cookie); } - win.ipinfocb = function(data){ - if (data.statusCode === "OK"){ - data.source = "ipinfodb"; - util.set_cookie( - options.location_cookie, - util.package_obj(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"); - }} - }; - - // Utilities - var util = { - parse_url: function(url_str){ - var a = doc.createElement("a"), query = {}; - a.href = url_str; 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]); } + }; + + // 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){ var 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.getElementsByTagName("body")[0].appendChild(element); - }, - package_obj: function (obj){ - obj.version = API_VERSION; - var ret = JSON.stringify(obj); - delete obj.version; return ret; - }, - 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']; } - }; - - // 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; } diff --git a/session.min.js b/session.min.js index e622331..8899030 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(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(let i=0;i + diff --git a/test/spec/browser-spec.js b/test/spec/browser-spec.js index b13609b..b4e9d86 100644 --- a/test/spec/browser-spec.js +++ b/test/spec/browser-spec.js @@ -1,25 +1,60 @@ // @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('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 = [ @@ -28,7 +63,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 +73,20 @@ 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 = [ - + ['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'], ]; -}) -*/ \ No newline at end of file + + 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)); + } +}); 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(){ diff --git a/test/spec/extension-spec.js b/test/spec/extension-spec.js index 1d1f326..1643ddf 100644 --- a/test/spec/extension-spec.js +++ b/test/spec/extension-spec.js @@ -3,35 +3,38 @@ * Author: Iain, CodeJoust 2012. MIT License ***/ +var mock = create_mock() +var session = mock.run_sess().win.session + describe("String contains string", function(){ it('should work with a string', function(){ - expect('asdf'.contains('as')).toBe(true) - expect('asdf'.contains('asdf')).toBe(true) - expect('ASDF'.contains('asdf')).toBe(false) + expect(session.contains('asdf', 'as')).toBe(true) + expect(session.contains('asdf', 'asdf')).toBe(true) + expect(session.contains('ASDF', 'asdf')).toBe(false) }) it('should not fail with a blank string', function(){ - expect(''.contains('')).toBe(true) - expect(''.contains('foo')).toBe(false) - expect('asdf'.contains('b')).toBe(false) + expect(session.contains('', '')).toBe(true) + expect(session.contains('', 'foo')).toBe(false) + expect(session.contains('asdf', 'b')).toBe(false) }) it('needs to work with wrong types', function(){ - expect('true'.contains(true)).toBe(false) - expect('false'.contains(false)).toBe(false) - expect('asdf'.contains(43)).toBe(false) - expect('asdf'.contains('z')).toBe(false) + expect(session.contains('true', true)).toBe(false) + expect(session.contains('false', false)).toBe(false) + expect(session.contains('asdf', 43)).toBe(false) + expect(session.contains('asdf', 'z')).toBe(false) }) }) describe('String contains in array', function(){ it('should use an array', function(){ - expect('asdf'.contains(['a','s','d','f'])).toBe(true) - expect('asdf'.contains(['ASDF','A','a'])).toBe(true) - expect('aSdF'.contains(['as','df','asdf'])).toBe(false) - expect('adfs'.contains(['asd','asb','ad','bbb','b'])).toBe(true) + expect(session.contains('asdf', ['a','s','d','f'])).toBe(true) + expect(session.contains('asdf', ['ASDF','A','a'])).toBe(true) + expect(session.contains('aSdF', ['as','df','asdf'])).toBe(false) + expect(session.contains('adfs', ['asd','asb','ad','bbb','b'])).toBe(true) }) it('should handle bad types in an array', function(){ - expect('asdf'.contains([4,3,23])).toBe(false) - expect('asdf'.contains([true, false])).toBe(false) - expect('true'.contains([true,'trua'])).toBe(true) + expect(session.contains('asdf', [4,3,23])).toBe(false) + expect(session.contains('asdf', [true, false])).toBe(false) + expect(session.contains('true', [true,'trua'])).toBe(true) }) -}) \ No newline at end of file +})