diff --git a/libraries/js-cookie.js b/libraries/js-cookie.js new file mode 100644 index 00000000..80a75512 --- /dev/null +++ b/libraries/js-cookie.js @@ -0,0 +1,163 @@ +/*! + * JavaScript Cookie v2.2.1 + * https://github.com/js-cookie/js-cookie + * + * Copyright 2006, 2015 Klaus Hartl & Fagner Brack + * Released under the MIT license + */ +;(function (factory) { + var registeredInModuleLoader; + if (typeof define === 'function' && define.amd) { + define(factory); + registeredInModuleLoader = true; + } + if (typeof exports === 'object') { + module.exports = factory(); + registeredInModuleLoader = true; + } + if (!registeredInModuleLoader) { + var OldCookies = window.Cookies; + var api = window.Cookies = factory(); + api.noConflict = function () { + window.Cookies = OldCookies; + return api; + }; + } +}(function () { + function extend () { + var i = 0; + var result = {}; + for (; i < arguments.length; i++) { + var attributes = arguments[ i ]; + for (var key in attributes) { + result[key] = attributes[key]; + } + } + return result; + } + + function decode (s) { + return s.replace(/(%[0-9A-Z]{2})+/g, decodeURIComponent); + } + + function init (converter) { + function api() {} + + function set (key, value, attributes) { + if (typeof document === 'undefined') { + return; + } + + attributes = extend({ + path: '/' + }, api.defaults, attributes); + + if (typeof attributes.expires === 'number') { + attributes.expires = new Date(new Date() * 1 + attributes.expires * 864e+5); + } + + // We're using "expires" because "max-age" is not supported by IE + attributes.expires = attributes.expires ? attributes.expires.toUTCString() : ''; + + try { + var result = JSON.stringify(value); + if (/^[\{\[]/.test(result)) { + value = result; + } + } catch (e) {} + + value = converter.write ? + converter.write(value, key) : + encodeURIComponent(String(value)) + .replace(/%(23|24|26|2B|3A|3C|3E|3D|2F|3F|40|5B|5D|5E|60|7B|7D|7C)/g, decodeURIComponent); + + key = encodeURIComponent(String(key)) + .replace(/%(23|24|26|2B|5E|60|7C)/g, decodeURIComponent) + .replace(/[\(\)]/g, escape); + + var stringifiedAttributes = ''; + for (var attributeName in attributes) { + if (!attributes[attributeName]) { + continue; + } + stringifiedAttributes += '; ' + attributeName; + if (attributes[attributeName] === true) { + continue; + } + + // Considers RFC 6265 section 5.2: + // ... + // 3. If the remaining unparsed-attributes contains a %x3B (";") + // character: + // Consume the characters of the unparsed-attributes up to, + // not including, the first %x3B (";") character. + // ... + stringifiedAttributes += '=' + attributes[attributeName].split(';')[0]; + } + + return (document.cookie = key + '=' + value + stringifiedAttributes); + } + + function get (key, json) { + if (typeof document === 'undefined') { + return; + } + + var jar = {}; + // To prevent the for loop in the first place assign an empty array + // in case there are no cookies at all. + var cookies = document.cookie ? document.cookie.split('; ') : []; + var i = 0; + + for (; i < cookies.length; i++) { + var parts = cookies[i].split('='); + var cookie = parts.slice(1).join('='); + + if (!json && cookie.charAt(0) === '"') { + cookie = cookie.slice(1, -1); + } + + try { + var name = decode(parts[0]); + cookie = (converter.read || converter)(cookie, name) || + decode(cookie); + + if (json) { + try { + cookie = JSON.parse(cookie); + } catch (e) {} + } + + jar[name] = cookie; + + if (key === name) { + break; + } + } catch (e) {} + } + + return key ? jar[key] : jar; + } + + api.set = set; + api.get = function (key) { + return get(key, false /* read as raw */); + }; + api.getJSON = function (key) { + return get(key, true /* read as json */); + }; + api.remove = function (key, attributes) { + set(key, '', extend(attributes, { + expires: -1 + })); + }; + + api.defaults = {}; + + api.withConverter = init; + + return api; + } + + return init(function () {}); +})); diff --git a/required/offline_API_wrapper.js b/required/offline_API_wrapper.js index 99aa087c..33e0f544 100644 --- a/required/offline_API_wrapper.js +++ b/required/offline_API_wrapper.js @@ -1,329 +1,182 @@ -//API that stores data to a cookie for LMS behaviour testing -function createResetButton(API) { - $('body').append($('')); - var $button = $(''); - $('body').append($button); - $button.on("click", function() { - if (API) API.LMSClear(); - alert("Status Reset"); - window.location.reload(); - }); -} - -function storageWarning() { - var Adapt; - var notificationMethod = alert; - this.__storageWarningTimeoutId = null; - if (require) Adapt = require('coreJS/adapt'); - if (Adapt && Adapt.config && Adapt.config.has('_spoor')) { - if (Adapt.config.get('_spoor')._advancedSettings && - Adapt.config.get('_spoor')._advancedSettings._suppressErrors === true) { - notificationMethod = console.error; - } +function checkJQueryStatus() { + if (window.jQuery === undefined) { + setTimeout(checkJQueryStatus, 100); + return; } - notificationMethod('Warning: possible cookie storage limit exceeded - tracking may malfunction'); -} -var API = { - - __offlineAPIWrapper:true, + $.extend(true, window.API, GenericAPI); + $.extend(true, window.API_1484_11, GenericAPI); +} - LMSInitialize: function() { - //if (window.ISCOOKIELMS !== false) createResetButton(this); - if (!API.LMSFetch()) { - this.data["cmi.core.lesson_status"] = "not attempted"; - this.data["cmi.suspend_data"] = ""; - this.data["cmi.core.student_name"] = "Surname, Sam"; - this.data["cmi.core.student_id"] = "sam.surname@example.org"; - API.LMSStore(true); - } - return "true"; - }, - LMSFinish: function() { - return "true"; - }, - LMSGetValue: function(key) { - return this.data[key]; - }, - LMSSetValue: function(key, value) { - var str = 'cmi.interactions.'; - if (key.indexOf(str) != -1) return "true"; +var GenericAPI = { - this.data[key] = value; + __offlineAPIWrapper: true, + __storageWarningTimeoutId: null, - API.LMSStore(); - return "true"; - }, - LMSCommit: function() { - return "true"; - }, - LMSGetLastError: function() { - return 0; - }, - LMSGetErrorString: function() { - return "Fake error string."; - }, - LMSGetDiagnostic: function() { - return "Fake diagnostic information."; - }, - LMSStore: function(force) { + store: function(force) { if (window.ISCOOKIELMS === false) return; - if (!force && API.cookie("_spoor") === undefined) return; - var stringified = JSON.stringify(this.data); + if (!force && window.Cookies.get('_spoor') === undefined) return; - API.cookie("_spoor", stringified); + window.Cookies.set('_spoor', this.data); // a length mismatch will most likely indicate cookie storage limit exceeded - if (API.cookie("_spoor").length != stringified.length) { + if (window.Cookies.get('_spoor').length !== JSON.stringify(this.data).length) { // defer call to avoid excessive alerts if (this.__storageWarningTimeoutId == null) { - this.__storageWarningTimeoutId = setTimeout(function() {storageWarning.apply(API);}, 1000); + this.__storageWarningTimeoutId = setTimeout(function() { + this.storageWarning(); + }.bind(this), 1000); } } }, - LMSFetch: function() { + + fetch: function() { if (window.ISCOOKIELMS === false) { this.data = {}; return; } - this.data = API.cookie("_spoor"); - if (this.data === undefined) { + + this.data = window.Cookies.getJSON('_spoor'); + + if (!this.data) { this.data = {}; return false; - } else { - this.data = JSON.parse(this.data); - return true; } + + return true; }, - LMSClear: function() { - API.removeCookie("_spoor"); + + reset: function() { + window.Cookies.remove('_spoor'); + }, + + createResetButton: function() { + $('body').append($('')); + var $button = $(''); + $('body').append($button); + $button.on('click', function() { + this.reset(); + alert('SCORM tracking cookie has been deleted!'); + window.location.reload(); + }.bind(this)); + }, + + storageWarning: function() { + var Adapt; + var notificationMethod = alert; + this.__storageWarningTimeoutId = null; + if (require) Adapt = require('core/js/adapt'); + if (Adapt && Adapt.config && Adapt.config.has('_spoor')) { + if (Adapt.config.get('_spoor')._advancedSettings && + Adapt.config.get('_spoor')._advancedSettings._suppressErrors === true) { + notificationMethod = console.error; + } + } + notificationMethod('Warning: possible cookie storage limit exceeded - tracking may malfunction'); } -}; -var API_1484_11 = { +}; - __offlineAPIWrapper:true, +// SCORM 1.2 API +window.API = { - Initialize: function() { - //if (window.ISCOOKIELMS !== false) createResetButton(this); - if (!API_1484_11.LMSFetch()) { - this.data["cmi.completion_status"] = "not attempted"; - this.data["cmi.suspend_data"] = ""; - this.data["cmi.learner_name"] = "Surname, Sam"; - this.data["cmi.learner_id"] = "sam.surname@example.org"; - API_1484_11.LMSStore(true); + LMSInitialize: function() { + // if (window.ISCOOKIELMS !== false) this.createResetButton(); + if (!this.fetch()) { + this.data['cmi.core.lesson_status'] = 'not attempted'; + this.data['cmi.suspend_data'] = ''; + this.data['cmi.core.student_name'] = 'Surname, Sam'; + this.data['cmi.core.student_id'] = 'sam.surname@example.org'; + this.store(true); } - return "true"; + return 'true'; }, - Terminate: function() { - return "true"; + + LMSFinish: function() { + return 'true'; }, - GetValue: function(key) { + + LMSGetValue: function(key) { return this.data[key]; }, - SetValue: function(key, value) { + + LMSSetValue: function(key, value) { var str = 'cmi.interactions.'; - if (key.indexOf(str) != -1) return "true"; + if (key.indexOf(str) !== -1) return 'true'; this.data[key] = value; - API_1484_11.LMSStore(); - return "true"; + this.store(); + return 'true'; }, - Commit: function() { - return "true"; + + LMSCommit: function() { + return 'true'; }, - GetLastError: function() { + + LMSGetLastError: function() { return 0; }, - GetErrorString: function() { - return "Fake error string."; - }, - GetDiagnostic: function() { - return "Fake diagnostic information."; - }, - LMSStore: function(force) { - if (window.ISCOOKIELMS === false) return; - if (!force && API_1484_11.cookie("_spoor") === undefined) return; - - var stringified = JSON.stringify(this.data); - API_1484_11.cookie("_spoor", stringified); - - // a length mismatch will most likely indicate cookie storage limit exceeded - if (API_1484_11.cookie("_spoor").length != stringified.length) { - // defer call to avoid excessive alerts - if (this.__storageWarningTimeoutId == null) { - this.__storageWarningTimeoutId = setTimeout(function() {storageWarning.apply(API_1484_11);}, 1000); - } - } - }, - LMSFetch: function() { - if (window.ISCOOKIELMS === false) { - this.data = {}; - return; - } - this.data = API_1484_11.cookie("_spoor"); - if (this.data === undefined) { - this.data = {}; - return false; - } else { - this.data = JSON.parse(this.data); - return true; - } + LMSGetErrorString: function() { + return 'Fake error string.'; }, - LMSClear: function() { - API_1484_11.removeCookie("_spoor"); + + LMSGetDiagnostic: function() { + return 'Fake diagnostic information.'; } }; -//Extend both APIs with the jquery.cookie code https://github.com/carhartl/jquery-cookie -(function () { - - var $; - - for (var i = 0, count = arguments.length; i < count; i++) { - - $ = arguments[i]; - - $.extend = function() { - var src, copyIsArray, copy, name, options, clone, - target = arguments[0] || {}, - i = 1, - length = arguments.length; - - // Handle case when target is a string or something (possible in deep copy) - if ( typeof target !== "object" && typeof target != "function" ) { - target = {}; - } - - // extend jQuery itself if only one argument is passed - if ( i === length ) { - target = this; - i--; - } - - for ( ; i < length; i++ ) { - // Only deal with non-null/undefined values - if ( (options = arguments[ i ]) != null ) { - // Extend the base object - for ( name in options ) { - src = target[ name ]; - copy = options[ name ]; - - // Prevent never-ending loop - if ( target === copy ) { - continue; - } - - if ( copy !== undefined ) { - target[ name ] = copy; - } - } - } - } - - // Return the modified object - return target; - }; - - var pluses = /\+/g; - - /** - * Safari-for-iOS 10 doesn't like values in JSON strings to contain commas, and will simply - * truncate the data at the point it finds one - see https://github.com/adaptlearning/adapt_framework/issues/1589 - * We can't just URL-encode the entire thing as that pretty much doubles the size of the data, see: - * https://github.com/adaptlearning/adapt_framework/issues/1535 - * According to https://developer.mozilla.org/en-US/docs/Web/API/document/cookie, semi-colons and whitespace - * are also disallowed, but so far we don't seem to be having problems because of them - */ - function urlEncodeDisallowedChars(value) { - var s = (config.json ? JSON.stringify(value) : String(value)); - return s.replace(/,/g, '%2C'); - } - - function parseCookieValue(s) { - if (s.indexOf('"') === 0) { - // This is a quoted cookie as according to RFC2068, unescape... - s = s.slice(1, -1).replace(/\\"/g, '"').replace(/\\\\/g, '\\'); - } +// SCORM 2004 API +window.API_1484_11 = { - try { - // Replace server-side written pluses with spaces. - // If we can't decode the cookie, ignore it, it's unusable. - // If we can't parse the cookie, ignore it, it's unusable. - s = decodeURIComponent(s.replace(pluses, ' ')); - return config.json ? JSON.parse(s) : s; - } catch(e) {} - } - - function read(s, converter) { - var value = config.raw ? s : parseCookieValue(s); - return typeof converter == "function" ? converter(value) : value; + Initialize: function() { + // if (window.ISCOOKIELMS !== false) this.createResetButton(); + if (!this.fetch()) { + this.data['cmi.completion_status'] = 'not attempted'; + this.data['cmi.suspend_data'] = ''; + this.data['cmi.learner_name'] = 'Surname, Sam'; + this.data['cmi.learner_id'] = 'sam.surname@example.org'; + this.store(true); } + return 'true'; + }, - var config = $.cookie = function (key, value, options) { - - // Write - - if (arguments.length > 1 && typeof value != "function") { - options = $.extend({}, config.defaults, options); - - if (typeof options.expires === 'number') { - var days = options.expires, t = options.expires = new Date(); - t.setTime(+t + days * 864e+5); - } - - return (document.cookie = [ - key, '=', urlEncodeDisallowedChars(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('')); - } - - // Read - - var result = key ? undefined : {}; + Terminate: function() { + return 'true'; + }, - // To prevent the for loop in the first place assign an empty array - // in case there are no cookies at all. Also prevents odd result when - // calling $.cookie(). - var cookies = document.cookie ? document.cookie.split('; ') : []; + GetValue: function(key) { + return this.data[key]; + }, - for (var i = 0, l = cookies.length; i < l; i++) { - var parts = cookies[i].split('='); - var name = parts.shift(); - var cookie = parts.join('='); + SetValue: function(key, value) { + var str = 'cmi.interactions.'; + if (key.indexOf(str) !== -1) return 'true'; - if (key && key === name) { - // If second argument (value) is a function it's a converter... - result = read(cookie, value); - break; - } + this.data[key] = value; - // Prevent storing a cookie that we couldn't decode. - if (!key && (cookie = read(cookie)) !== undefined) { - result[name] = cookie; - } - } + this.store(); + return 'true'; + }, - return result; - }; + Commit: function() { + return 'true'; + }, - config.defaults = {}; + GetLastError: function() { + return 0; + }, - $.removeCookie = function (key, options) { - if ($.cookie(key) === undefined) { - return false; - } + GetErrorString: function() { + return 'Fake error string.'; + }, - // Must not alter options, thus extending a fresh object... - $.cookie(key, '', $.extend({}, options, { expires: -1 })); - return !$.cookie(key); - }; + GetDiagnostic: function() { + return 'Fake diagnostic information.'; } -})(API, API_1484_11); +}; + +checkJQueryStatus(); diff --git a/required/scorm_test_harness.html b/required/scorm_test_harness.html index 3f9972ec..808a2610 100644 --- a/required/scorm_test_harness.html +++ b/required/scorm_test_harness.html @@ -1,12 +1,13 @@
-