/** * @module Container * @namespace springroll.pbskids */ (function() { // Import classes var UUID; var BufferedQueue; var Platform; var Identity; /** * A module that consumers can use to expose Super Vision kid labels to this plugin. Basically, the * "PBS.KIDS.CurrentKidLabel" (if it exists), should be an object with a get and set method to allow "setters" to * update the value on the fly, so that this plugin can always get the latest value when needed * * @var {Object} CurrentKidLabel */ var CurrentKidLabel; /** * A module that consumers can use to expose the Super Vision channel id to this plugin. Basically, the * "PBS.KIDS.CurrentChannelId" (if it exists), should be an object witha get and set method to allow "setters" to * update the value on the fly, so that this plugin can always get the latest value when needed * * @var {Object} CurrentChannelId */ var CurrentChannelId; /** * A module that consumers can use to expose the a content origin to this plugin. Basically, the * "PBS.KIDS.ContentOrigin" (if it exists), should be an object with a get and set method to allow "setters" to * update the value on the fly, so that this plugin can always get the latest value when needed. For instance, a value * might look like "org.pbskids.measureup", or "org.pbskids.gamesapp" to distinguish *where* the event came from. As * more Springroll games get embedded in multiple places, this will be valuable when wanting to filter those events * based on *where* the game was played (Measure Up vs. pbskids.org vs. Games vs. whatever) * * @var {Object} ContentOrigin */ var ContentOrigin; /** * Handle the Learning Analytics events * @class LearningAnalytics * @constructor * @param {string} [domain='http://progresstracker.pbskids.org'] The domain for the end-point * this should only be set for mobile devices or testing otherwise * it uses a root-relative end-point and will use the progresstracker * on the current domain * @param {string} [resource='game'] The type of resource */ var LearningAnalytics = function(domain, resource) { if (UUID === undefined) { UUID = include('springroll.pbskids.UUID'); BufferedQueue = include('springroll.pbskids.BufferedQueue'); Platform = include('springroll.pbskids.Platform'); Identity = include('PBS.KIDS.identity', false); CurrentKidLabel = include('PBS.KIDS.CurrentKidLabel', false); CurrentChannelId = include('PBS.KIDS.CurrentChannelId', false); ContentOrigin = include('PBS.KIDS.ContentOrigin', false); } // Param defaults resource = resource || 'game'; domain = domain || 'http://progresstracker.pbskids.org:8000'; // Determine the service path to use var servicePath = SERVICE_PATHS.v1; /** * The valid event keys for the resource type * @property {array} eventKeys * @private */ this.eventKeys = EVENT_KEYS.v1; // Upgrade to version 2 of the API if (resource && !!SERVICE_PATHS.v2[resource]) { servicePath = SERVICE_PATHS.v2[resource]; this.eventKeys = EVENT_KEYS.v2[resource]; } /** * Queuing object for the event buffer * @property {pbskids.BufferedQueue} queue */ this.queue = new BufferedQueue( BATCH_SIZE, KEY_PREFIX, domain + servicePath ); /** * For getting platform details * @property {pbskids.Platform} platform */ this.platform = new Platform(); /** * This variable contains the Session Id for current play session. * @property {string} sessionId */ this.sessionId = null; /** * The setInterval ping to check online status * @property {int} _timer * @private */ this._timer = null; /** * Internal enabled boolean * @property {Boolean} _enabled * @private * @default true */ this._enabled = false; // Create a new session id this.resetSessionId(); // Enable the tracker by default this.enabled = false; }; /** * The prefix for the event data * @property {string} KEY_PREFIX * @private * @default 'PBS_event_' * @static * @readOnly */ var KEY_PREFIX = 'PBS_event_'; /** * The root-relative service URL pathing * @property {object} SERVICE_PATHS * @private * @static * @readOnly */ var SERVICE_PATHS = { v2: { video: '/progresstracker/api/v2/videos/events.json', game: '/progresstracker/api/v2/games/events.json' }, v1: '/progresstracker/api/v1/rawevents.json' }; /** * The batch size of the queue * @property {int} BATCH_SIZE * @private * @default 5 * @static * @readOnly */ var BATCH_SIZE = 5; /** * JSON object keys for validation * @property {object} EVENT_KEYS * @private * @static * @readOnly */ var EVENT_KEYS = { v2: { game: [ 'timestamp', 'user_ids', 'game_id', 'device_id', 'platform_id', 'event_id', 'event_data', 'game_session' ], video: [ 'timestamp', 'user_ids', 'video_id', 'device_id', 'platform_id', 'event_id', 'event_data', 'video_session' ] }, v1: [ 'timestamp', 'user_ids', 'game_id', 'device_id', 'platform_id', 'event_id', 'event_data', 'game_session' ] }; // Reference to the prototype var p = LearningAnalytics.prototype; /** * Reset the current session id * @method resetSessionId */ p.resetSessionId = function() { this.sessionId = UUID.genV4().hexString + UUID.genV4().hexString; }; /** * End the current queue * @method end */ p.end = function() { this.queue.end(); }; /** * If the queueing should autoflush * @property {boolean} autoFlush */ p.setAutoFlush = function(autoFlush) { this.queue.setAutoFlush(autoFlush); }; /** * Check the online status internally on a timer * Auto calling function to keep updating online staus * @method checkStatus * @private */ p.checkStatus = function() { this.queue.setOnline(navigator.onLine); }; /** * If the tracking is enabled * @property {boolean} enabled * @default true */ Object.defineProperty(p, 'enabled', { get: function() { return this._enabled; }, set: function(enabled) { this._enabled = enabled; if (this._timer) { clearInterval(this._timer); this._timer = null; } if (enabled) { this._timer = setInterval(this.checkStatus.bind(this), 10000); this.queue.enable(); } else { this.queue.disable(); } } }); /** * Flush the queue * @method flushAll */ p.flushAll = function() { this.queue.flushAll(); }; /** * Insert Game SessionId in JSON Data * @method appendSessionId * @private * @param {object} eventData The event object * @return {LearningAnalytics} Instance for chaining */ p.appendSessionId = function(eventData) { eventData.game_session = this.sessionId; return this; }; /** * Insert platform in JSON Data * @method appendPlatformDetails * @private * @param {object} eventData The event object data * @return {LearningAnalytics} Instance for chaining */ p.appendPlatformDetails = function(eventData) { eventData.platform_id = this.platform.browser; eventData.device_id = this.platform.OS; return this; }; /** * Insert user information in JSON Data * @method appendUserData * @private * @param {object} eventData The event object data * @return {LearningAnalytics} Instance for chaining */ p.appendUserData = function(eventData) { if (Identity) { eventData.user_ids = []; var users = Identity.getCurrentUsers(); for (var i = 0; i < users.length; i++) { eventData.user_ids.push(users[i].userid); eventData.is_logged_in = users[i].isloggedin; } } // If clients have defined a "PBS.KIDS.CurrentKidLabel" module, we'll also include the kid label there as well if (CurrentKidLabel instanceof Object) { eventData.kid_label_guid = CurrentKidLabel.get(); } return this; }; /** * Adds the current super vision channel id to the event JSON data * @method appendChannelId * @private * @param {object} eventData The event object data * @return {LearningAnalytics} Instance for chaining */ p.appendChannelId = function(eventData) { // If clients have defined a "PBS.KIDS.CurrentChannelId" module, we'll also include the channel id as well if (CurrentChannelId instanceof Object) { eventData.channel_id = CurrentChannelId.get(); } return this; }; /** * Adds the current Super Vision content origin to the event JSON data * @method appendContentOrigin * @private * @param {Object} eventData The event object data * @return {LearningAnalytics} Instance for chaining */ p.appendContentOrigin = function(eventData) { // If clients have defined a content origin, we'll attach it if (ContentOrigin instanceof Object) { eventData.content_origin = ContentOrigin.get(); } return this; }; /** * Add the timestamp to the event JSON data * @method appendTimeStamp * @private * @param {object} eventData The event object data * @return {LearningAnalytics} Instance for chaining */ p.appendTimeStamp = function(eventData) { var da = new Date(); eventData.timestamp = da.getTime(); return this; }; /** * Push a learning event * @method pushEvent * @param {object} eventData The event object data * @param {string} eventData.game_id The GUID for the game * @param {string} eventData.event_id The GUID for the event * @param {object} eventData.event_data The data for event * @return {object} The event data with any appended data */ p.pushEvent = function(eventData) { // Ignore if we aren't enabled if (!this._enabled) { return this; } if (eventData.user_ids === undefined) { eventData.user_ids = []; } eventData.is_logged_in = false; this.appendTimeStamp(eventData) .appendSessionId(eventData) .appendPlatformDetails(eventData) .appendUserData(eventData) .appendChannelId(eventData) .appendContentOrigin(eventData); if (this.validateEvent(eventData)) { this.queue.pushEvent(eventData); } return eventData; }; /** * Validate Event JSON data * @method validateEvent * @param {object} eventData * @private * @return {Boolean} if the event is valid */ p.validateEvent = function(eventData) { for (var i = 0; i < this.eventKeys.length; i++) { if (!validateKey(eventData[this.eventKeys[i]])) { return false; } } return true; }; /** * Validate event data keys from EVENT_KEYS * @method validateKaye * @private * @param {object} key Validate * @return {boolean} If the key is valid */ var validateKey = function(key) { return !(key === null || key == 'undefined' || key === ''); }; /** * Cleanup and don't use after this * @method destroy */ p.destroy = function() { this.enabled = false; this.queue.destroy(); this.queue = null; this.platform = null; }; // Assign to namespace namespace('springroll.pbskids').LearningAnalytics = LearningAnalytics; }());