/**
* @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;
}());