Giter Club home page Giter Club logo

xapiwrapper's Introduction

xAPIWrapper

Wrapper to simplify communication to an LRS. Read more about the Experience API Spec here.

Check out the Reference Documentation Here

xapiwrapper.js

Javascript Experience API wrapper.
This javascript file can be included to web based xAPI clients to simplify the process of connecting and communicating to an LRS. It is enclosed in an ADL object like the ADL xAPI Verbs project, allowing a single object to contain both the ADL verbs and the ADL xapiwrapper.

This wrapper has two version identifiers within the code. One, xapiVersion is the version of the Experience API Specification for which it was built, and can be used to determine if the wrapper is compatible with an LRS implementing a specific xAPI Specification version. The second is the build date in the header of the minified file, which can be used to tell if you're using the latest version.

Dependencies

The wrapper relies on external dependencies to perform some actions. Make sure you include our compilation of the necessary CryptoJS components in your pages if you're not using xapiwrapper.min.js.

<script type="text/javascript" src="./lib/cryptojs_v3.1.2.js"></script>

In the past we used the below libraries for the same purpose. You may continue to use them for current systems, but the CryptoJS compilation is recommended.

For IE/Edge support you will need to include a TextEncoder and TextDecoder shim if you're not using xapiwrapper.min.js

<script type="text/javascript" src="./lib/utf8-text-encoding.js"></script>

Installing

Using this wrapper could either be done by downloading the latest release or cloning the project.

Downloading the latest release version

The minified wrapper is self-contained. It includes all required dependencies in addition to the ADL Verbs and the XAPIStatement module. For production sites, this version of the wrapper is recommended.

Download the latest release

Follow the instructions for including the wrapper in your source files.

Cloning and building the project

You can optionally clone and use the dist/xapiwrapper.min.js:

git clone https://github.com/adlnet/xAPIWrapper/

Building the project

Compiling the minified version is easy. Install Node.js and NPM if you don't already have them (download them here) or

$ sudo apt-get install nodejs
$ sudo apt-get install npm

$ sudo ln -s /usr/bin/nodejs /usr/bin/node

Then install the build system, Grunt. This may require root/admin privileges on your system.

$ cd xAPIWrapper
$ sudo npm install -g grunt

Install the xAPIWrapper dependencies:

$ sudo npm install

Then execute the build script:

$ grunt

This will overwrite dist/xapiwrapper.min.js with the minifed versions of the wrapper and all its dependencies.

Including in your Software.

Include the wrapper file, and optionally the dependencies.

<script type="text/javascript" src="./lib/cryptojs_v3.1.2.js"></script>
<script type="text/javascript" src="./lib/utf8-text-encoding.js"></script>
<script type="text/javascript" src="./src/activitytypes.js"></script>
<script type="text/javascript" src="./src/verbs.js"></script>
<script type="text/javascript" src="./src/xapiwrapper.js"></script>
<script type="text/javascript" src="./src/xapistatement.js"></script>
<script type="text/javascript" src="./src/xapi-util.js"></script>
<script type="text/javascript" src="./src/xapi-launch.js"></script>

Alternatively, use the minified version:

<script type="text/javascript" src="./dist/xapiwrapper.min.js"></script>

Configuration

The wrapper at a minimum needs to know the url of the LRS, though most cases will also require the authorization information as well.

This wrapper provides two options for configuration. You may:

  • Edit the configuration object (Config) in the xapiwrapper.js file
var Config = function()
{
    var conf = {};
    conf['endpoint'] = "http://localhost:8000/xapi/";
    try
    {
        conf['auth'] = "Basic " + toBase64('tom:1234');
    }
    catch (e)
    {
        log("Exception in Config trying to encode auth: " + e);
    }

    // Statement defaults [optional configuration]
    // conf["actor"] = {"mbox":"[email protected]"};
    // conf["registration"] =  ruuid();
    // conf["grouping"] = {"id":"ctxact:default/grouping"};
    // conf["activity_platform"] = "default platform";

    // Behavior defaults
    // conf["strictCallbacks"] = false; // Strict error-first callbacks
    return conf
}();
  • Create your own configuration object and pass it to the xapiwrapper object
var conf = {
  "endpoint" : "https://lrs.adlnet.gov/xapi/",
  "auth" : "Basic " + toBase64('tom:1234'),
};
ADL.XAPIWrapper.changeConfig(conf);

Optionally, auth credentials can be updated by user and password properties on the configuration object:

var conf = {
  "endpoint" : "https://lrs.adlnet.gov/xapi/",
  "user" : "tom",
  "password" : "1234",
};
ADL.XAPIWrapper.changeConfig(conf);

or

var creds = {
  "user" : "tom",
  "password" : "1234",
};
ADL.XAPIWrapper.updateAuth(ADL.XAPIWrapper.lrs, creds.user, creds.password);

The script automatically runs, creating or adding to an ADL object an instantiated xAPI Wrapper object. The object is created using the configuration object inside the xapiwrapper.js file. If you modified this object with your configuration, then xAPI Wrapper object is ready to use.

ADL.XAPIWrapper.testConfig();
>> true

Launch Parameters

The configuration will also look for url query parameters and use those name - value pairs in the XAPIWrapper's internal configuration. That means that http://localhost:8000/content/example.html?actor={"mbox":"mailto:[email protected]"}
(not url encoded for illustrative purposes) would be parsed for an actor, which would automatically be included in the wrapper configuration.
NOTE: endpoint, auth, actor, registration, activity_id, grouping, and activity_platform are keywords that if found are used in send statement requests. See below for usage examples.

Logging

The wrapper comes with a logging function (ADL.XAPIWrapper.log(message)) which attempts to write a message to console.log. This can be configured to not write messages by setting log.debug = false;.

xAPI Launch support

The xAPI Wrapper supports ADL's xAPI Launch. This allows configuration - agent info, lrs endpoint info - to be sent to the wrapper, instead of using hard-coded configurations. See Using the xAPI-Launch library for more details.

If you are using the src files, include xapi-launch.js.

<script type="text/javascript" src="./lib/cryptojs_v3.1.2.js"></script>
<script type="text/javascript" src="./lib/utf8-text-encoding.js"></script>
<script type="text/javascript" src="./src/activitytypes.js"></script>
<script type="text/javascript" src="./src/verbs.js"></script>
<script type="text/javascript" src="./src/xapiwrapper.js"></script>
<script type="text/javascript" src="./src/xapistatement.js"></script>
<script type="text/javascript" src="./src/xapi-util.js"></script>
<script type="text/javascript" src="./src/xapi-launch.js"></script>

Alternatively, use the minified xapiwrapper version, which includes xapi-launch:

<script type="text/javascript" src="./dist/xapiwrapper.min.js"></script>

To use, construct and ADL.launch object passing in a callback.

var wrapper;
ADL.launch(function(err, launchdata, xAPIWrapper) {
    if (!err) {
        wrapper = xAPIWrapper;
        console.log("--- content launched via xAPI Launch ---\n", wrapper.lrs, "\n", launchdata);
    } else {
        wrapper = ADL.XAPIWrapper;
        wrapper.changeConfig({
            endpoint: "https://lrs.adlnet.gov/xapi/",
            user: 'tom',
            password: '1234'
        });
        console.log("--- content statically configured ---\n", wrapper.lrs);
    }
    $('#endpoint').text(wrapper.lrs.endpoint);
}, true);

Use

Statements

Statement Object (xapistatement.js)
new ADL.XAPIStatement(actor, verb, object)
new ADL.XAPIStatement.Agent(identifier, name)
new ADL.XAPIStatement.Group(identifier, members, name)
new ADL.XAPIStatement.Verb(id, description)
new ADL.XAPIStatement.Activity(id, name, description)
new ADL.XAPIStatement.StatementRef(uuid)
new ADL.XAPIStatement.SubStatement(actor, verb, object)

This sub-API makes it easier to author valid xAPI statements by adding constructors and encouraging best practices. All objects in this API are fully JSON-compatible, so anything expecting an xAPI statement can take an improved statement and vice versa.

In addition to the above forms, each constructor can instead take as an argument another instance of the object or the equivalent plain object. So you can convert a plain xAPI statement to an improved one by calling new XAPIStatement(plain_obj).

Building a Simple "I Did This"

Passing in strings produces a default form: Agent Verb Activity.

var stmt = new ADL.XAPIStatement(
	'mailto:[email protected]',
	'http://adlnet.gov/expapi/verbs/launched',
	'http://vwf.adlnet.gov/xapi/virtual_world_sandbox'
);
>> {
	"actor": {
		"objectType": "Agent",
		"mbox": "mailto:[email protected]" },
	"verb": {
		"id": "http://adlnet.gov/expapi/verbs/launched" },
	"object": {
		"objectType": "Activity",
		"id": "http://vwf.adlnet.gov/xapi/virtual_world_sandbox" }}
Adding Descriptors
var stmt = new ADL.XAPIStatement(
	new ADL.XAPIStatement.Agent(ADL.XAPIWrapper.hash('mailto:[email protected]'), 'Steven Vergenz'),
	new ADL.XAPIStatement.Verb('http://adlnet.gov/expapi/verbs/launched', 'launched'),
	new ADL.XAPIStatement.Activity('http://vwf.adlnet.gov/xapi/virtual_world_sandbox', 'the Virtual World Sandbox')
);
>> {
	"actor": {
		"objectType": "Agent",
		"name": "Steven Vergenz",
		"mbox_sha1sum": "f9fa1a084b2b0825cabb802fcc1bb6024141eee2" },
	"verb": {
		"id": "http://adlnet.gov/expapi/verbs/launched",
		"display": {
			"en-US": "launched" }},
	"object": {
		"objectType": "Activity",
		"id": "http://vwf.adlnet.gov/xapi/virtual_world_sandbox",
		"definition": {
			"name": {
				"en-US": "the Virtual World Sandbox" }}}}
Adding Additional Fields

You can mix generated and manual fields without any conflicts.

var stmt = new ADL.XAPIStatement(
	'mailto:[email protected]',
	'http://adlnet.gov/expapi/verbs/launched',
	'http://vwf.adlnet.gov/xapi/virtual_world_sandbox'
);
stmt.generateId();
stmt.result = { 'response': 'Everything is a-okay!' };
>> {
	"actor": {
		"objectType": "Agent",
		"mbox": "mailto:[email protected]" },
	"verb": {
		"id": "http://adlnet.gov/expapi/verbs/launched" },
	"object": {
		"objectType": "Activity",
		"id": "http://vwf.adlnet.gov/xapi/virtual_world_sandbox" },
	"id": "d60ffbaa-52af-44b6-932d-c08865c540ff",
	"result": {
		"response": "Everything is a-okay!" }}
Setting the context attribute

Considering the behavior of xapiwrapper.js:

XAPIWrapper.prototype.prepareStatement = function(stmt)
{
    ...........
    if (this.lrs.grouping ||
        this.lrs.registration ||
        this.lrs.activity_platform) {
        if (!stmt.context) {
            stmt.context = {};
        }
    }

    if (this.lrs.grouping) {
        if (!stmt.context.contextActivities) {
            stmt.context.contextActivities = {};
        }
        
        if (!Array.isArray(stmt.context.contextActivities.grouping)) {
            stmt.context.contextActivities.grouping = [{ id : this.lrs.grouping }];
        } else {
            stmt.context.contextActivities.grouping.splice(0, 0, { id : this.lrs.grouping });
        }
    }
    ...........
}

Here we notice 2 behaviours:

  1. In order to set statement.context.contextActivities you must set grouping.

  2. In order to be able to set the context property of a statement you have to set at least one of grouping, registration or activity_platform during ADL configuration like so:

const configuration = {
      'endpoint' : LRS.apiUrl,
      'auth' : 'Basic ' + btoa(LRS.key + ':' + LRS.secret),
      'registration': this.ADL.ruuid(),
      'activity_platform': `${this.organizationId}_organizationId`
    };
this.ADL.XAPIWrapper.changeConfig(configuration);

As you can see we set only registration and activity_platform.

Using Multiple Languages

Any of the name or description fields in the constructors can instead take a language map, as defined in the xAPI specification.

var stmt = new ADL.XAPIStatement();
stmt.actor = new ADL.XAPIStatement.Agent('https://plus.google.com/113407910174370737235');
stmt.verb = new ADL.XAPIStatement.Verb(
	'http://adlnet.gov/expapi/verbs/launched',
	{
		'en-US': 'launched',
		'de-DE': 'startete'
	}
);
stmt.object = new ADL.XAPIStatement.Activity('http://vwf.adlnet.gov/xapi/virtual_world_sandbox');
>> {
	"actor": {
		"objectType": "Agent",
		"openid": "https://plus.google.com/113407910174370737235" },
	"verb": {
		"id": "http://adlnet.gov/expapi/verbs/launched",
		"display": {
			"en-US": "launched",
			"de-DE": "startete" }},
	"object": {
		"objectType": "Activity",
		"id": "http://vwf.adlnet.gov/xapi/virtual_world_sandbox" }}
Using an ADL Verb

Manually specified verbs have been used until now for illustrative purposes, but you could just as easily use the ADL verbs.

var stmt = ADL.XAPIStatement(myactor, ADL.verbs.launched, myactivity);
Send Statement

function sendStatement(statement, callback, [attachments])
Sends a single Statement to the LRS using a PUT request. This method will automatically create the Statement ID. Providing a function to call after the send Statement request will make the request happen asynchronously, otherwise Send Statement will block until it receives the response from the LRS.

Send Statement without Callback
var stmt = {"actor" : {"mbox" : "mailto:[email protected]"},
            "verb" : {"id" : "http://adlnet.gov/expapi/verbs/answered",
                      "display" : {"en-US" : "answered"}},
            "object" : {"id" : "http://adlnet.gov/expapi/activities/question"}};
var resp_obj = ADL.XAPIWrapper.sendStatement(stmt);
ADL.XAPIWrapper.log("[" + resp_obj.id + "]: " + resp_obj.xhr.status + " - " + resp_obj.xhr.statusText);
>> [3e616d1c-5394-42dc-a3aa-29414f8f0dfe]: 200 - OK
Send Statement with Callback
var stmt = {"actor" : {"mbox" : "mailto:[email protected]"},
            "verb" : {"id" : "http://adlnet.gov/expapi/verbs/answered",
                      "display" : {"en-US" : "answered"}},
            "object" : {"id" : "http://adlnet.gov/expapi/activities/question"}};
ADL.XAPIWrapper.sendStatement(stmt, function(resp, obj){  
    ADL.XAPIWrapper.log("[" + obj.id + "]: " + resp.status + " - " + resp.statusText);});
>> [4edfe763-8b84-41f1-a355-78b7601a6fe8]: 200 - OK
Send Statement with strict error-first callback

Requires the config option strictCallbacks to be true.

var stmt = {"actor" : {"mbox" : "mailto:[email protected]"},
            "verb" : {"id" : "http://adlnet.gov/expapi/verbs/answered",
                      "display" : {"en-US" : "answered"}},
            "object" : {"id" : "http://adlnet.gov/expapi/activities/question"}};
ADL.XAPIWrapper.sendStatement(stmt, function(err, res, body) {
    if (err) {
        // Handle error case
        return;
    }

    ADL.XAPIWrapper.log("[" + body.id + "]: " + res.status + " - " + res.statusText);});
>> [4edfe763-8b84-41f1-a355-78b7601a6fe8]: 200 - OK
Send Statement with Attachments

The wrapper can construct a multipart/mixed POST for a single statement that includes attachments. Attachments should be supplied as an array in the 3rd parameter to sendStatement. Attachments are optional. The attachments array should consist of objects that have a type and a value field. Type should be the metadata description of the attachment as described by the spec, and value should be a string containing the data to post. The type field does not need to include the SHA2 or the length. These will be computed for you. The type may optionally be the string 'signature'. In this case, the wrapper will construct the proper metadata block.

var attachment = {};
attachment.type = {
       "usageType": "http://adlnet.gov/expapi/attachments/signature",
       "display": {
        "en-US": "A JWT signature"
       },
       "description": {
        "en-US": "A signature proving the statement was not modified"
       },
       "contentType": "application/octet-stream"
};
attachment.value = "somehugestring";
ADL.XAPIWrapper.sendStatement(stmt,callback,[attachment]);
Send Statement with URL query string values

The wrapper looks for URL query string values to include in its internal configuration. If certain keys ("endpoint","auth","actor","registration","activity_id", "grouping", "activity_platform") are found, the values are included in a Statement.
URL
http://localhost:8000/content/example.html?registration=51a6f860-1997-11e3-8ffd-0800200c9a66
Client Calls

var stmt = {"actor" : {"mbox" : "mailto:[email protected]"},
            "verb" : {"id" : "http://adlnet.gov/expapi/verbs/answered",
                      "display" : {"en-US" : "answered"}},
            "object" : {"id" : "http://adlnet.gov/expapi/activities/question"}};
var resp_obj = ADL.XAPIWrapper.sendStatement(stmt);
ADL.XAPIWrapper.getStatements({"statementId":resp_obj.id});
>> {"version": "1.0.0",
    "timestamp": "2013-09-09 21:36:40.185841+00:00",
    "object": {"id": "http://adlnet.gov/expapi/activities/question", "objectType": "Activity"},
    "actor": {"mbox": "mailto:[email protected]", "name": "tom creighton", "objectType": "Agent"},
    "stored": "2013-09-09 21:36:40.186124+00:00",
    "verb": {"id": "http://adlnet.gov/expapi/verbs/answered", "display": {"en-US": "answered"}},
    "authority": {"mbox": "mailto:[email protected]", "name": "tom", "objectType": "Agent"},
>   "context": {"registration": "51a6f860-1997-11e3-8ffd-0800200c9a66"},
    "id": "ea9c1d01-0606-4ec7-8e5d-20f87b1211ed"}
Send Statement with ADL xAPI Verbs

ADL also has collected the ADL xAPI Verbs into a Javascript object to easily include. To use...
Include verbs.js
<script type="text/javascript" src="./src/verbs.js"></script>
Client Calls

var stmt = {"actor" : {"mbox" : "mailto:[email protected]"},
            "verb" : ADL.verbs.answered,
            "object" : {"id" : "http://adlnet.gov/expapi/activities/question"}};
var resp_obj = ADL.XAPIWrapper.sendStatement(stmt);
ADL.XAPIWrapper.getStatements({"statementId":resp_obj.id});
>> {"version": "1.0.0",
    "timestamp": "2013-09-09 22:08:51.440327+00:00",
    "object": {"id": "http://adlnet.gov/expapi/activities/question", "objectType": "Activity"},
    "actor": {"mbox": "mailto:[email protected]", "name": "tom creighton", "objectType": "Agent"},
    "stored": "2013-09-09 22:08:51.440614+00:00",
>   "verb": {"id": "http://adlnet.gov/expapi/verbs/answered", "display": {"en-US": "answered"}},
    "authority": {"mbox": "mailto:[email protected]", "name": "tom", "objectType": "Agent"},
    "id": "9c5a910b-83c2-4114-84f5-d41ed790f8cf"}
Send Statement with XAPIStatement

By including xapistatement.js, you gain access to a convenience wrapper to ease the building of xAPI statements without a lot of the formatting fluff.

var stmt = new ADL.XAPIStatement("mailto:[email protected]", null, "http://adlnet.gov/expapi/activities/question");
stmt.verb = new ADL.XAPIStatement.Verb("http://adlnet.gov/expapi/verbs/answered", "answered");
stmt.generateId();
ADL.XAPIWrapper.sendStatement(stmt);
ADL.XAPIWrapper.getStatements({"statementId": stmt.id});
>> {"version": "1.0.0",
    "timestamp": "2013-09-09 22:08:51.440327+00:00",
    "object": {"id": "http://adlnet.gov/expapi/activities/question", "objectType": "Activity"},
    "actor": {"mbox": "mailto:[email protected]", "objectType": "Agent"},
    "stored": "2013-09-09 22:08:51.440614+00:00",
>   "verb": {"id": "http://adlnet.gov/expapi/verbs/answered", "display": {"en-US": "answered"}},
    "authority": {"mbox": "mailto:[email protected]", "name": "tom", "objectType": "Agent"},
    "id": "9c5a910b-83c2-4114-84f5-d41ed790f8cf"}
Send Statements

function sendStatements(statementArray, callback)
Sends a list of Statements to the LRS in one batch. It accepts the list of Statements and a callback function as arguments and returns the XHR request object if no callback is supplied. The response of the XHR request upon success will contain a list of Statement IDs.

Send Statements without callback
var stmt = {"actor" : {"mbox" : "mailto:[email protected]"},
            "verb" : ADL.verbs.answered,
            "object" : {"id" : "http://adlnet.gov/expapi/activities/question/1"}};
var stmt2 = {"actor" : {"mbox" : "mailto:[email protected]"},
            "verb" : ADL.verbs.answered,
            "object" : {"id" : "http://adlnet.gov/expapi/activities/question/2"}};
var stmts = [stmt, stmt2];
var r = ADL.XAPIWrapper.sendStatements(stmts);
JSON.parse(r.response)
>> ["2d819ea4-1a1e-11e3-a888-08002787eb49", "409c27de-1a1e-11e3-a888-08002787eb49"]
Send Statements with callback
var stmt = {"actor" : {"mbox" : "mailto:[email protected]"},
            "verb" : ADL.verbs.answered,
            "object" : {"id" : "http://adlnet.gov/expapi/activities/question/1"}};
var stmt2 = {"actor" : {"mbox" : "mailto:[email protected]"},
            "verb" : ADL.verbs.answered,
            "object" : {"id" : "http://adlnet.gov/expapi/activities/question/2"}};
var stmts = [stmt, stmt2];
ADL.XAPIWrapper.sendStatements(stmts, function(r){ADL.XAPIWrapper.log(JSON.parse(r.response));});
>> ["2d819ea4-1a1e-11e3-a888-08002787eb49", "409c27de-1a1e-11e3-a888-08002787eb49"]
Send Statements with strict error-first callback

Requires the config option strictCallbacks to be true.

var stmt = {"actor" : {"mbox" : "mailto:[email protected]"},
            "verb" : ADL.verbs.answered,
            "object" : {"id" : "http://adlnet.gov/expapi/activities/question/1"}};
var stmt2 = {"actor" : {"mbox" : "mailto:[email protected]"},
            "verb" : ADL.verbs.answered,
            "object" : {"id" : "http://adlnet.gov/expapi/activities/question/2"}};
var stmts = [stmt, stmt2];
ADL.XAPIWrapper.sendStatements(stmts, function(err, res, body) {
    if (err) {
        // Handle error case
        return;
    }

    ADL.XAPIWrapper.log(body);
});
>> ["2d819ea4-1a1e-11e3-a888-08002787eb49", "409c27de-1a1e-11e3-a888-08002787eb49"]
Get Statements

function getStatements(searchParams, more, callback)
Get a single or collection of Statements based on search parameters or a StatementResult more value.

Get all Statements without callback

This could be potentially a very large request. It is preferable to include a search parameter object to narrow down the StatementResult set. However, this call is included to support report style pages.

var ret = ADL.XAPIWrapper.getStatements();
if (ret)
   ADL.XAPIWrapper.log(ret.statements);

>> <Array of statements>
Get all Statements with callback
ADL.XAPIWrapper.getStatements(null, null,
        function(r){ADL.XAPIWrapper.log(JSON.parse(r.response).statements);});
>> <Array of statements>
Use the more property to get more Statements
var res = ADL.XAPIWrapper.getStatements();
ADL.XAPIWrapper.log(res.statements);
>> <Array of statements>

if (res.more && res.more !== ""){
   var more = ADL.XAPIWrapper.getStatements(null, res.more);
   ADL.XAPIWrapper.log(more.statements);
}
>> <Array of statements>
Use the more property to get more Statements with callback
ADL.XAPIWrapper.getStatements(null, null,
   function getmore(r){
      var res = JSON.parse(r.response);
      ADL.XAPIWrapper.log(res.statements);
      if (res.more && res.more !== ""){
         ADL.XAPIWrapper.getStatements(null, res.more, getmore);
      }
   });
>> <Array of statements>
>> <Array of statements>
...
Get all Statements with with strict error-first callback

Requires the config option strictCallbacks to be true.

ADL.XAPIWrapper.getStatements(null, null, function(err, res, body) {
    if (err) {
        // Handle error case
        return;
    }

    ADL.XAPIWrapper.log(body.statements);
});
>> <Array of statements>
Get Statements based on search parameters

The Experience API provides search parameters to narrow down the result of a Statement request. See the Experience API Spec for more information.

var search = ADL.XAPIWrapper.searchParams();
search['verb'] = ADL.verbs.answered.id;
var res = ADL.XAPIWrapper.getStatements(search);
ADL.XAPIWrapper.log(res.statements);
>> <Array of statements with verb id of "http://adlnet.gov/expapi/verbs/answered">
var search = ADL.XAPIWrapper.searchParams();
search['verb'] = ADL.verbs.terminated.id;
search['activity'] = "http://adlnet.gov/courses/roses/posttest";
search['related_activities'] = "true";
var res = ADL.XAPIWrapper.getStatements(search);
ADL.XAPIWrapper.log(res.statements);
>> <Array of statements with verb id of "http://adlnet.gov/expapi/verbs/terminated" and an activity id of "http://adlnet.gov/courses/roses/posttest" in the statement>

Activities

Get Activity

function getActivities(activityid, callback) Get the Activity object from the LRS by providing an Activity ID.

Get Activity without callback
var res = ADL.XAPIWrapper.getActivities("http://adlnet.gov/expapi/activities/question");
ADL.XAPIWrapper.log(res);
>> <Activity object>
Get Activity with callback
ADL.XAPIWrapper.getActivities("http://adlnet.gov/expapi/activities/question",
                         function(r){ADL.XAPIWrapper.log(JSON.parse(r.response));});
>> <Activity object>
Get Activity with strict error-first callback

Requires the config option strictCallbacks to be true.

ADL.XAPIWrapper.getActivities("http://adlnet.gov/expapi/activities/question", function(err, res, body) {
    if (err) {
        // Handle error case
        return;
    }

    ADL.XAPIWrapper.log(body);
});
>> <Activity object>
Activity State

function sendState(activityid, agent, stateid, registration, statevalue, matchHash, noneMatchHash, callback)
function getState(activityid, agent, stateid, registration, since, callback)
function deleteState(activityid, agent, stateid, registration, matchHash, noneMatchHash, callback) Save / Retrieve / Delete activity state information for a particular agent, and optional registration.

Send / Retrieve New Activity State
var stateval = {"info":"the state info"};
ADL.XAPIWrapper.sendState("http://adlnet.gov/expapi/activities/question",
                          {"mbox":"mailto:[email protected]"},
                          "questionstate", null, stateval);
ADL.XAPIWrapper.getState("http://adlnet.gov/expapi/activities/question",
                        {"mbox":"mailto:[email protected]"}, "questionstate");
>> {info: "the state info"}
Change Activity State
var oldstateval = {"info":"the state info"};
var newstateval = {"info":"the new value"};
ADL.XAPIWrapper.sendState("http://adlnet.gov/expapi/activities/question",
                          {"mbox":"mailto:[email protected]"},
                          "questionstate", null, newstateval,
                          ADL.XAPIWrapper.hash(JSON.stringify(oldstateval)));
ADL.XAPIWrapper.getState("http://adlnet.gov/expapi/activities/question",
                        {"mbox":"mailto:[email protected]"}, "questionstate",
                        null, null, function(r){ADL.XAPIWrapper.log(JSON.parse(r.response));});
>> {info: "the new value"}
Get all states for given Activity and Agent
var anotherstate = {"more": "info about act and agent"};
ADL.XAPIWrapper.sendState("http://adlnet.gov/expapi/activities/question",
                          {"mbox":"mailto:[email protected]"},
                          "another_state", null, anotherstate);
var states = ADL.XAPIWrapper.getState("http://adlnet.gov/expapi/activities/question",
                        {"mbox":"mailto:[email protected]"});
ADL.XAPIWrapper.log(states);
>> ["another_state", "questionstate"]
Get states for given Activity and Agent since a certain time
var actid = "tag:adlnet.gov,2013:expapi:1.0.0:activity:question/1";
var stateval = {"info":"the state info"};
var statehash = ADL.XAPIWrapper.hash(JSON.stringify(stateval));
ADL.XAPIWrapper.sendState(actid, {"mbox":"mailto:[email protected]"}, "questionstate", null, stateval);
var stateret = ADL.XAPIWrapper.getState(actid, {"mbox":"mailto:[email protected]"}, "questionstate");
ADL.XAPIWrapper.log(stateret);
>> {"info":"the state info"}

var sincehere = new Date();
var anotherstate = {"more": "info about act and agent","other":"stuff"};
ADL.XAPIWrapper.sendState(actid, {"mbox":"mailto:[email protected]"}, "another_state", null, anotherstate);
var states = ADL.XAPIWrapper.getState(actid, {"mbox":"mailto:[email protected]"});
ADL.XAPIWrapper.log(states);
>> ["questionstate", "another_state"]

var states = ADL.XAPIWrapper.getState(actid, {"mbox":"mailto:[email protected]"}, null, null, sincehere);
ADL.XAPIWrapper.log(states);
>> ["another_state"]
Delete Activity State
var stateval = {"info":"the state info"};
ADL.XAPIWrapper.sendState("http://adlnet.gov/expapi/activities/question",
                          {"mbox":"mailto:[email protected]"},
                          "questionstate", null, stateval);
ADL.XAPIWrapper.getState("http://adlnet.gov/expapi/activities/question",
                        {"mbox":"mailto:[email protected]"}, "questionstate");
>> {info: "the state info"}

ADL.XAPIWrapper.deleteState("http://adlnet.gov/expapi/activities/question",
                        {"mbox":"mailto:[email protected]"}, "questionstate");
>> XMLHttpRequest {statusText: "NO CONTENT", status: 204, response: "", responseType: "", responseXML: nullโ€ฆ}

ADL.XAPIWrapper.getState("http://adlnet.gov/expapi/activities/question",
                        {"mbox":"mailto:[email protected]"}, "questionstate");
>> 404
Activity Profile

function sendActivityProfile(activityid, profileid, profilevalue, matchHash, noneMatchHash, callback)
function getActivityProfile(activityid, profileid, since, callback)
function deleteActivityProfile(activityid, profileid, matchHash, noneMatchHash, callback)
Allows for the storage, retrieval and deletion of data about an Activity.

Send / Retrieve New Activity Profile
var profile = {"info":"the profile"};
ADL.XAPIWrapper.sendActivityProfile("http://adlnet.gov/expapi/activities/question",
                                    "actprofile", profile, null, "*");
ADL.XAPIWrapper.getActivityProfile("http://adlnet.gov/expapi/activities/question",
                                  "actprofile", null,
                                  function(r){ADL.XAPIWrapper.log(JSON.parse(r.response));});
>> {info: "the profile"}
Update Activity Profile
var profile = ADL.XAPIWrapper.getActivityProfile("http://adlnet.gov/expapi/activities/question",
                                                 "actprofile");
var oldprofhash = ADL.XAPIWrapper.hash(JSON.stringify(profile));
profile['new'] = "changes to profile";
ADL.XAPIWrapper.sendActivityProfile("http://adlnet.gov/expapi/activities/question",
                                    "actprofile", profile, oldprofhash);
ADL.XAPIWrapper.getActivityProfile("http://adlnet.gov/expapi/activities/question",
                                  "actprofile", null,
                                  function(r){ADL.XAPIWrapper.log(JSON.parse(r.response));});
>> {info: "the profile", new: "changes to profile"}
Get all profiles about a specific Activity
var profile = {"info":"the profile"};
ADL.XAPIWrapper.sendActivityProfile("http://adlnet.gov/expapi/activities/question",
                                    "otheractprofile", profile, null, "*");
ADL.XAPIWrapper.getActivityProfile("http://adlnet.gov/expapi/activities/question",
                                  null, null,
                                  function(r){ADL.XAPIWrapper.log(JSON.parse(r.response));});
>> ["otheractprofile", "actprofile"]
Get profiles about an Activity since a certain time
var actid = "tag:adlnet.gov,2013:expapi:1.0.0:activity:testing/xapiwrapper/activityprofile";
var profid = "testprofile";
var actprof = {"info":"the activity profile info"};
var actprofhash = ADL.XAPIWrapper.hash(JSON.stringify(actprof));

ADL.XAPIWrapper.sendActivityProfile(actid, profid, actprof, null, actprofhash);
var actprofret = ADL.XAPIWrapper.getActivityProfile(actid, profid);

ADL.XAPIWrapper.log(actprofret);
>> {"info": "the activity profile info"}

var since = new Date();

var newprofid = "new-profile";
var profile = {"info":"the profile"};

ADL.XAPIWrapper.sendActivityProfile(actid, newprofid, profile, null, "*");
var profiles = ADL.XAPIWrapper.getActivityProfile(actid, null, since);

ADL.XAPIWrapper.log(profiles);
>> ["new-profile"]
Delete Activity Profile
var profile = {"info":"the profile"};
ADL.XAPIWrapper.sendActivityProfile("http://adlnet.gov/expapi/activities/question",
                                    "actprofile", profile, null, "*");
ADL.XAPIWrapper.getActivityProfile("http://adlnet.gov/expapi/activities/question",
                                  "actprofile", null,
                                  function(r){ADL.XAPIWrapper.log(JSON.parse(r.response));});
>> {info: "the profile"}

ADL.XAPIWrapper.deleteActivityProfile("http://adlnet.gov/expapi/activities/question",
                        "actprofile");
>> XMLHttpRequest {statusText: "NO CONTENT", status: 204, response: "", responseType: "", responseXML: nullโ€ฆ}

ADL.XAPIWrapper.getActivityProfile("http://adlnet.gov/expapi/activities/question",
                                  "actprofile");
>> 404

Agents

Get Agent

function getAgents(agent, callback)
Gets a special Person object containing all the values of an Agent the LRS knows about. The Person object's identifying properties are arrays and it may have more than one identifier. See more about Person in the spec

Get Agent without callback
var res = ADL.XAPIWrapper.getAgents({"mbox":"mailto:[email protected]"});
ADL.XAPIWrapper.log(res);
>> <Person object>
Get Agent with callback
ADL.XAPIWrapper.getAgents({"mbox":"mailto:[email protected]"},
                         function(r){ADL.XAPIWrapper.log(JSON.parse(r.response));});
>> <Person object>
Get Agent with strict error-first callbacks

Requires the config option strictCallbacks to be true.

ADL.XAPIWrapper.getAgents({"mbox":"mailto:[email protected]"}, function(err, res, body) {
    if (err) {
        // Handle error case
        return;
    }

    ADL.XAPIWrapper.log(body);
});
>> <Person object>
Agent Profile

function sendAgentProfile(agent, profileid, profilevalue, matchHash, noneMatchHash, callback)
function getAgentProfile(agent, profileid, since, callback)
function deleteAgentProfile(agent, profileid, matchHash, noneMatchHash, callback)
Allows for the storage, retrieval and deletion of data about an Agent.

Send / Retrieve New Agent Profile
var profile = {"info":"the agent profile"};
ADL.XAPIWrapper.sendAgentProfile({"mbox":"mailto:[email protected]"},
                                  "agentprofile", profile, null, "*");
ADL.XAPIWrapper.getAgentProfile({"mbox":"mailto:[email protected]"},
                                 "agentprofile", null,
                                 function(r){ADL.XAPIWrapper.log(JSON.parse(r.response));});
>> {info: "the agent profile"}
Update Agent Profile
var profile = ADL.XAPIWrapper.getAgentProfile({"mbox":"mailto:[email protected]"},
                                               "agentprofile");
var oldprofhash = ADL.XAPIWrapper.hash(JSON.stringify(profile));
profile['new'] = "changes to the agent profile";
ADL.XAPIWrapper.sendAgentProfile({"mbox":"mailto:[email protected]"},
                                  "agentprofile", profile, oldprofhash);
ADL.XAPIWrapper.getAgentProfile({"mbox":"mailto:[email protected]"},
                                 "agentprofile", null,
                                 function(r){ADL.XAPIWrapper.log(JSON.parse(r.response));});
>> {info: "the agent profile", new: "changes to the agent profile"}
Get all profiles about a specific Agent
var profile = {"info":"the agent profile"};
ADL.XAPIWrapper.sendAgentProfile({"mbox":"mailto:[email protected]"},
                                  "othergentprofile", profile, null, "*");
ADL.XAPIWrapper.getAgentProfile({"mbox":"mailto:[email protected]"},
                                 null, null,
                                 function(r){ADL.XAPIWrapper.log(JSON.parse(r.response));});
>> ["otheragentprofile", "agentprofile"]
Get profiles about an Agent since a certain time
var otheragent = {"mbox":"mailto:[email protected]"};
var profile = {"info":"the other agent profile"};
var otherprofid = "the-other-profile-id";

ADL.XAPIWrapper.sendAgentProfile(otheragent, otherprofid, profile, null, "*");

var since = new Date();
var newprof = {"info":"the new other agent profile"};
var newotherprofid = "the-new-other-profile-id";

ADL.XAPIWrapper.sendAgentProfile(otheragent, newotherprofid, newprof, null, "*");
var sinceprofiles = ADL.XAPIWrapper.getAgentProfile(otheragent, null, since);

ADL.XAPIWrapper.log(sinceprofiles);
>> ["the-new-other-profile-id"]
Delete Agent Profile
var profile = {"info":"the agent profile"};
ADL.XAPIWrapper.sendAgentProfile({"mbox":"mailto:[email protected]"},
                                  "agentprofile", profile, null, "*");
ADL.XAPIWrapper.getAgentProfile({"mbox":"mailto:[email protected]"},
                                 "agentprofile", null,
                                 function(r){ADL.XAPIWrapper.log(JSON.parse(r.response));});
>> {info: "the agent profile"}

ADL.XAPIWrapper.deleteAgentProfile({"mbox":"mailto:[email protected]"},
                        "agentprofile");
>> XMLHttpRequest {statusText: "NO CONTENT", status: 204, response: "", responseType: "", responseXML: nullโ€ฆ}

ADL.XAPIWrapper.getAgentProfile({"mbox":"mailto:[email protected]"},
                                 "agentprofile");
>> 404

Browser support

IE10+

Contributing to the project

We welcome contributions to this project. Fork this repository, make changes, re-minify, and submit pull requests. If you're not comfortable with editing the code, please submit an issue and we'll be happy to address it.

License

Copyright ยฉ2016 Advanced Distributed Learning

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at

   http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.

xapiwrapper's People

Contributors

adl-trey avatar ajanderson1209 avatar bharatpareek avatar brian-learningpool avatar cr8onski avatar creighton avatar dependabot[bot] avatar ecointest avatar floriantolk avatar hsmith-adl avatar ioannap avatar jsma avatar ljwolford avatar neboduus avatar oliverfoster avatar pauliejes avatar rchadwic avatar rneatrour avatar ryanrolds avatar samthill avatar simis-trey avatar ty- avatar vbhayden avatar webb-nickj avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

xapiwrapper's Issues

Launch problems with v0.9 TinCan launch

The Rustici launch mechanism passes through the mbox as an array rather than a string. I know the spec should be considered deprecated at this point but there do appear to be some popular learning management systems which still use this. See the comments at RusticiSoftware/launch#6 for background.

Security, username and password client-side

Isn't it a security risk to have the password to your LRS be sent client-side to be used with this library? I'm in the process of using this library to connect to our LRSs, but I'm a little confused at the security. Any insight would be greatly appreciated, thanks!

Cross Domain and IE10

Hey
when I try to use this with IE10 (works great with ie11, chrome) I get an error:

SCRIPT5: Access is denied.
xapiwrapper.js, line 1342 character 13

This is the line it refers to:
xhr.open(ieModeRequest.method, ieModeRequest.url);

Any ideas how to fix this - having the course on a secure server makes no diffence.

Sorry - pressed submit before adding the error!

John

using this API with IE10

Hi

I ran into a problem while using this API with IE10. The API was throwing invalid state error in IE 10 while trying to fetch statements from SCORM CLOUD. All works well in IE 11 and other browsers.

Debugging further I realized that the issue was actually with the xhr.withCredentials in the ADL.XHR_request function. Moving it after xhr.open fixed the issue. Is it possible to raise a pull request to include it in the code?

xhr.open(method, url, callback != null);
xhr.withCredentials = withCredentials;

or
if(typeof(withCredentials) != "undefined") // if Boolean is not passed for withCredentials
xhr.withCredentials = withCredentials;
xhr.open(method, url, callback != null);

send state pass array is not array form one page to another page

Content-Type is application/json if state value is Array. But Content-Type is application/octet-stream if you pass Array form one page to another page . The reason is use instanceof to test state value .

see javascript-when-i-pass-array-from-iframe-function-the-array-lose-his-type

Test in chrome with windows 10.
t1. html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script>
        function test() {
            var arr = [];
            arr.push(1);
            var r1 = arr instanceof Array;
            console.log("is array (instanceof) " + r1.toString());
            console.log("is array (Array.isArray) " + Array.isArray(arr))
            console.log("call iframe")
            window.f1.demo(arr);
        }
    </script>
</head>
<body>
<iframe src="t2.html" name="f1"  height="600" width="100%" frameborder="0" scrolling="no" onload="test()"></iframe>
</body>
</html>

t2.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script>
        demo = function (param) {
            var r1 = param instanceof Array;
            console.log("is array (instanceof) " +  r1.toString())
            console.log("is array (Array.isArray) " +  Array.isArray(param))
        }
    </script>
</head>
<body>
</body>
</html>

The consloe result

is array (instanceof) true
is array (Array.isArray) true
call iframe
is array (instanceof) false
is array (Array.isArray) true

url extended param not pass to LRS

Hi
I use launch server and want to pass some param(ex: a=b&c=d) to launch url. But the xAPIWrapper.js is not pass extended param to LRS. I find #14 , it is delete pass param code. I do not know why? Anyone can help me

More URLs are not constructed properly

https://github.com/adlnet/xAPIWrapper/blob/master/xapiwrapper.js#L256

"Relative IRL that may be used to fetch more results, including the full path and optionally a query string but excluding scheme, host, and port. Empty string if there are no more results to fetch.

This IRL must be usable for at least 24 hours after it is returned by the LRS. In order to avoid the need to store these IRLs and associated query data, an LRS may include all necessary information within the IRL to continue the query, but should avoid generating extremely long IRLs. The consumer should not attempt to interpret any meaning from the IRL returned. "

So, it must be a relative URL and it must include the full path. Relative URLs (also called relative references or URL fragments) are not combined with a URL by concatenating them together, but by a special set of rules. For instance, combining http://a.example.com/stuff/orange with the relative reference alpha results in http://a.example.com/stuff/alpha . Combining http://a.example.com/stuff/orange with the relative reference /alpha results in http://a.example.com/alpha , and there are a variety of other rules as well. Since the more value is required to include the full path and not the hostname and scheme, the only legal sort of relative reference is one that starts with /, which will replace the whole URL path, not be concatenated to the end.

Invalid launch parameters

I got 'Invalid Launch Parameters' error while launching course build using adapt-authoring tool and tkhub xAPI plugin.

var wrapper;
ADL.launch(function(err, launchdata, xAPIWrapper) {
    if (!err) {
        wrapper = xAPIWrapper;
        console.log("--- content launched via xAPI Launch ---\n", wrapper.lrs, "\n", launchdata);
    } else {
       console.log(err);
    }
    $('#endpoint').text(wrapper.lrs.endpoint);
}, true);

How to provide launch parameter?

Development roadmap?

Hi adlnet,

I wonder what is the future roadmap for the xAPIWrapper? Are there any structural changes or extensions planned? We are starting to use this great lib as the base for our apps and tests so it would be good to know if important changes are ahead.

It would be fantastic to put this code under semantic versioning, this would make the integration and maintenance easier for app devs.

Thanks

funny conditional

This conditional bugs me. If the first part of the conditional is true, then the second part can't possible be false. Isn't the second part redundant? It is used several times in the documentation.
if (res.more && res.more !== "") ...

Support for result object

I was using this JS to push h5p statements from Moodle to an LRS.

But the new ADL.XAPIStatement(), does not accept result parameter, the result paramenter can be used to send user's response to LRS.

For example answer to a quiz question.

All for-in loops do not have checks for hasOwnProperty

Array.prototype.foo = function() {}
var statements = [ ... ];
xapiWrapper.sendStatements(statements, ...);
-> SyntaxError: Unexpected token u in JSON at position 0
    at JSON.parse (<anonymous>)
    at XAPIWrapper.prepareStatement
    at XAPIWrapper.sendStatements

Reason:

for (var key in statements) { console.log(key) }
-> 0
-> 1
-> 2
-> ...
-> foo
-> other prototype extensions...

Implement as a RequireJS module if available

Rather than forcing the namespace this would let it work properly with modern good practices - there's various patterns, but basically if there's no define function defined then nothing changes, otherwise it should effectively have define(function(){return ADL;}) - but possibly without using the global namespace at all.

sendStatements() not functionally equivalent to sendStatement()?

I noticed while testing the ADL launch method that sendStatement() and sendStatements() are not functionally equivalent. For instance, while testing with Learning Locker v1, xAPI statements sent via sendStatements() would fail.

The same statements would succeed if used in a for-each call to sendStatement().

ADL.launch not compatible with IE

Looks like the ADL.launch method makes use of the URL API which is not supported by IE. This means the current code cannot initialize the launch.

callback for Errors in xhr_request

ADL.XHR_request() and callees: It would be nice to have the opportunity to submit an extra callback for errors. With the alerts removed in #26 (which is great!) we need an easy way to communicate errors to the users. Currently I am monkey-patching the XMLHttpRequest object to do this but that is not a good solution.

A few examples on "getting LRS statements."

Would it be possible to create a folder with several examples of pulling records from an LRS. Something like the following would be enormously helpful to what we are trying to accomplish in my company.

getStatements1.html
This would be an example of a getting LRS records. It would pull down all verbs with no data restrictions and no special formatting. It would look like a large data dump.

getStatements2.html
This would be an example of a getting LRS records but giving the user a way to specify what data they want. For example, I would like to be able sometimes to just grab Actor, verb, object, date/time and context activity (like category). It would also have a variable that can be adjusted that would allow for getting only a certain number of records.

getStatements3.html
This would be like getStatements2 but with additional features that would allow the user to limit data to a date range or a category name. It would also be parsed for a neat appearance.

Thank you very much for your consideration. ~ John M.

Auth param in url containing the "=" character

Hi,

While using xAPIWrapper, I encountered a little issue to get auth in the url. My authentication token ends with a "=" char. I found that if a parameter contain this char, it is not used. Maybe this should be modified.

Thanks,
Corentin

400 Bad Request

I've been trying out the xAPIWrapper for the first time but I keep getting this error in the browser console (XXXXX refers to my endpoint ID):

cloud.scorm.com/tc/XXXXX/statements:1
POST 
https://cloud.scorm.com/tc/XXXXXX/statements 400 (Bad Request)

My authentication code is:

var conf = {
            "endpoint": "https://cloud.scorm.com/tc/XXXX/",
            "user": "XXXX",
            "password": "XXXXX"
        };
ADL.XAPIWrapper.changeConfig(conf);

I've used these same credentials with the tin can prototypes (http://tincanapi.com/prototypes/) and it works fine. Not sure what's going on?

NPM module

Is this repo up on NPM? I'm not able to find it, it would be nice to npm install

console.warning()

Hi,

firstly, thanks for removing the alerts in ADL.XHR_request()
However I believe there is a syntax error in commit a998c356f6970b99e057da161deb2c7d356bfda0

Instead of console.warning() it should be console.warn(), see docs

Firebug is currently throwing an exception and stops logging:)
TypeError: console.warning is not a function

I might do a pull request. What is your preferred workflow in such cases? First an issue and then a pull request or a straight pull request?

Cheers

sendActivityProfile function using If-Match and LearningLocker LRS

I have a question about the sendActivityProfile functiion. When passing an object as the profileval the function will always use the POST method.

Unfortunately, when using POST the LearningLocker LRS will ignore the If-Match header and will always store the data sent even if it overwrites an updated profile object. If I modify the code so that it uses PUT, LearningLocker behaves as expected and returns http status 412 - precondtion failed.

Just wondering what the correct behaviour here should be? Is this a fault with the wrapper or the LRS?

And what is the reason POST always being used when profileval is an object but not if profileval is an array? Of the top of my head I would expect POST when creating a new profile and PUT when updating an existing profile, why should the method be dependent on the profile data type?

parseQueryString is not working properly if hashes appear after the search

The parseQueryString method in xAPIWrapper takes no account of hashes potentially appearing after the search paramters.

Something like this would overcome the problem:

function parseQueryString()
{
    var loc, qs, pairs, pair, ii, parsed;
    qs = window.location.search.substr(1);

    pairs = qs.split('&');
    parsed = {};
    for ( ii = 0; ii < pairs.length; ii++) {
        pair = pairs[ii].split('=');
        if (pair.length === 2 && pair[0]) {
            parsed[pair[0]] = decodeURIComponent(pair[1]);
        }
    }
    return parsed;
}

Sending statements with binary attachments is not supported

The current implementation for sending statements with attachments does not support sending binary data, for example an image or pdf. Currently the code uses strings to store / concat together the multipart/mixed request, to support sending binary data this would need to be update to use something like ArrayBuffers.

having issue working with get more statements

I am facing issue processing more statements from XAPI. In my code below, I am using XAPI Wrapper and I am getting around 900 statements from lrs in rek, however, when I convert them into a collection and try to use the where clause, it does not give me any result.

Please Note: I can get the same thing done using fetchAllStatementes and it also works fine with getStatements (for only the first 100 records), however, I wish to do it manually using getMoreStatements.

Is there an example of merging statements using get more statements, creating a collection on top of it and then using the where query to filter the data?

My code:
var stmt1 = JSON.stringify({
"mbox": actor.mbox ,
"name": actor.name
});

var stmt2 = JSON.stringify({
	"mbox": "mailto:[email protected]"  
});

var stmtVerb = JSON.stringify({
	"id": "http://adlnet.gov/expapi/verbs/started"
});

var stmtCtx = JSON.stringify({
	"contextActivities": {
		"grouping": [
			{
				"id": competency.iri
			}
		]
	}
});

var search = ADL.XAPIWrapper.searchParams();
search['agent'] = stmt1;
search['authority'] = stmt2;
search['context'] = stmtCtx;
var rek = []
ADL.XAPIWrapper.getStatements(search, null,
   function getmore(r){
      var res = JSON.parse(r.response);
      $.each(res.statements, function(x,y){
        rek.push(y)  ;
      });
      
      if (res.more && res.more !== ""){
         ADL.XAPIWrapper.getStatements(search, res.more, getmore);
      }
   });

console.log(rek);

//var ret = ADL.XAPIWrapper.getStatements(search);	//works fine
//var statements = new ADL.Collection(ret.statements);  

var stmt = new ADL.Collection(rek);
var filtered_data = stmt.where('actor.name = "ccazabon"').exec(function(data){
					console.log(data);//no output-empty array, however, matching data does exists
				});

var p = [{'name': 'Steven', 'age': 25, 'dob':{
'mm': 'jan',
'dd': '18'
}},
{'name': 'John', 'age': 35, 'dob':{
'mm': 'feb',
'dd': '19'
}}];
//console.log(p);
var a = new ADL.Collection(p);
//console.log(a);
var d = a.where('dob.mm = "jan"').exec(function(data){console.log(data)});//works as expected

I think there is some issue with merging all the statements and creating the ADL.Collection, however, I am not getting any online help to resolve the same.
Your help is greatly appreciated.

example.html - invalid auth data

            ADL.XAPIWrapper.changeConfig({
                'endpoint': 'https://lrs.adlnet.gov/xapi/',
                'user': 'steven',
                'password': 'test123'
            });

returns 401 UNAUTHORIZED

XHR callback not being fired on error

Running in to an issue when calling sendState(..., callback)/sendStatements(..., callback) the supplied callback is not invoked when an error is encountered (either the request is bad or the LRS is down). Looking through the code I see that a global error handler has been added, but that is not optimal as it doesn't easily facilitate context specific handling of the failure.

IMO, the expected behavior behavior is that the callback is invoked regardless of the outcome. It's up to the dev to inspect the status code (or an error object) and determine the correct course of action inside of the callback. Other platforms, like Node.js, have the first argument in callbacks be the error or null (if no error). That would require that callbacks confirm to function(error, response, body) where error is null when the request is successful.

Adding a option/flag to enable the expected behavior (the callback is always invoked regardless of outcome) would be a significant step towards correcting the problem. I'm willing to do the work of adding this option and behavior.

Library broken

Commit 0f0c4c0 broke the XAPIWrapper for me. AllI get from Chrome is:
Uncaught TypeError: Cannot read property 'log' of undefinedmergeRecursive @ xapiwrapper.js:1095getLRSObject @ xapiwrapper.js:1148XAPIWrapper @ xapiwrapper.js:111window.ADL @ xapiwrapper.js:1427(anonymous function) @ xapiwrapper.js:1429

Whereafter ADL.XAPIWrapper is not available.
The Problem seems to be that ADL.XAPIWrapper.log() is used before ADL.XAPIWrapper is registered.

Can you please fix that?

Statement id collision (UUID v4)

After generating about 5k statements across 40 devices (same model tablet) we encountered a id collision. JavaScript's Math.random() isn't that great of a PRNG and the Web Crypto API isn't widely supported yet. It may be better to use a v3/v5 UUID (hash versions) and use actor id + course id + activity + timestamp to produce UUIDs that are less likely to collide in this context.

If you're OK switching to v3 or v5 I'm happy to do the work.

submit statement

submit statement returns an error "internal server error"
at the same time the statements are saved any clarification?

Why is there no ADL.activities object?

Since ADL.verbs exists, I'm just wondering why ADL.activities (or activityTypes) doesn't?

I'm using the xAPIWrapper in a project I'm working on and was surprised that it wasn't there.

Statement.Context can not be set

Hi All! I don't know if this is an issue or just under engineering, but Statement.Context can not be set. If I look at xapistatement.js I can see just some context references, but any Context Object. Is this normal? How could I use Context on statements?

Thanks

Attachments assume a contentType of application/octet-stream

I noticed this while testing a PDF upload. Setting contentType: application/pdfon the attachment item it was still being set to application/octet-stream in the POST request. This caused the LRS (Learning Locker) to save the file as a .bin rather than .pdf.

toISOString is undefined

Uncaught TypeError: undefined is not a function

Came across this when working with the State API, it applies to any function using toISOString() which is defined in the top of xapiwrapper.js if the browser doesn't support the native function. I can call Date.prototype.toISOString in the console, but I cannot process any dates.

RangeError: Invalid time value

This bug has also brought to my attention leniency on the date string in the getStatements function. This will encodeURIComponent() any date string and the LRS will process it properly. e.g. 02/11/2015 11:04 AM will query https://lrs.adlnet.gov/xapi/statements?format=exact&since=02%2F11%2F2015%2011%3A04%20AM with results returned as if an ISO 8601 date was passed.

The xAPI spec is unclear as to whether timestamps MUST be ISO 8601 everywhere (statements, queries, database).

A timestamp MUST be formatted according to ISO 8601.

getStatements with attachments

getStatements always expects JSON back in the response, even for when using the attachments query. Is it worth discussing this a bit more to have it parse out the statement and do something with the attachments?

XAPI launch error with IE11

xapi_luanch_error_ie11

Test environment

  • xAPIWrapper 1.10.1
  • IE11

Error Message

{
    "description": "Object doesn't support this action",
    "number": -2146827843,
    "stack": "TypeError: Object doesn't support this action\n   at f (http://127.0.0.1:8099/lesson/6918064/xapiwrapper.min.js:2:30984)\n   at Anonymous function (http://127.0.0.1:8099/lesson/6918064/demo.html?xAPILaunchKey=abea8db9-da55-4dfa-82ec-98daa675259a&xAPILaunchService=http%3A%2F%2F127.0.0.1%3A8099%2F:22:8)\n   at j (http://127.0.0.1:8099/static/js/common/jquery.min.js:2:29992)\n   at Anonymous function (http://127.0.0.1:8099/static/js/common/jquery.min.js:2:30313)"
}

Send multiple parameters for searchParams in getStatements

Not exactly an issue, but I cant find anything related to this in the documentation.

Suppose I want to query for multiple parameters like all statements with verbs pause or play.
Or all Users whose id(mail) is on a particular domain, etc. How do I do such queries? If possible please
direct to a complete documentation on the searchParams object as it looks quite essential.

Thanks.

Allow for a custom log function

Similar to how ADL.xhrRequestOnError is implemented.

currently log(), this.log() and ADL.XAPIWrapper.log() are the three ways log is being called. this should be limited.

Sendind statement on IE10

Hi,

I'm using your library for some tests and I've get an issue :

ADL.XAPIWrapper.sendStatement(stmt);
this line works fine on Chrome, FF (latest versions) and IE11 ... but it doesn't work with IE10.

An exception is sent with message : "Error : Access denied".

Have I missed something, or is it a real bug ?

Thanks,
Corentin

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.