diff --git a/.gitignore b/.gitignore
index 57bcff0..485195d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -66,3 +66,4 @@ typings/
.DS_Store
test-results.xml
+/.vscode/
diff --git a/.mocharc.yml b/.mocharc.yml
new file mode 100644
index 0000000..1ee64be
--- /dev/null
+++ b/.mocharc.yml
@@ -0,0 +1,7 @@
+recursive: true
+require: '@babel/register'
+spec: 'test/**/*.spec.js'
+ui: 'bdd'
+watch: true
+watch-files:
+ - 'test/**/*.js'
\ No newline at end of file
diff --git a/README.md b/README.md
index fb09d70..8a74c20 100644
--- a/README.md
+++ b/README.md
@@ -48,7 +48,7 @@ The APIs include several settings to customize the functionality of each API:
| `autocommitSeconds` | 60 | int | Number of seconds to wait before autocommiting. Timer is restarted if another value is set. |
| `lmsCommitUrl` | false | url | The URL endpoint of the LMS where data should be sent upon commit. If no value is provided, modules will run as usual, but all method calls with just be logged to the console. |
| `dataCommitFormat` | `json` | `json`, `flattened`, `params` | `json` will send a JSON object to the lmsCommitUrl in the format of
`{'cmi': {'core': {...}}`
`flattened` will send the data in the format
`{'cmi.core.exit': 'suspend', 'cmi.core.mode': 'normal'...}`
`params` will send the data as
`?cmi.core.exit=suspend&cmi.core.mode=normal...` |
-| `commitRequestType` | 'application/json;charset=UTF-8' | string | This setting is provided in case your LMS expects a different content type or character set. |
+| `commitRequestDataType` | 'application/json;charset=UTF-8' | string | This setting is provided in case your LMS expects a different content type or character set. |
| `autoProgress` | false | true/false | In case Sequencing is being used, you can tell the API to automatically throw the `SequenceNext` event.|
| `logLevel` | 4 | int
1 => DEBUG
2 => INFO
3 => WARN
4 => ERROR
5 => NONE | By default, the APIs only log error messages. |
| `mastery_override` | false | true/false | (SCORM 1.2) Used to override a module's `cmi.core.lesson_status` so that a pass/fail is determined based on a mastery score and the user's raw score, rather than using whatever status is provided by the module. An example of this would be if a module is published using a `Complete/Incomplete` final status, but the LMS always wants to receive a `Passed/Failed` for quizzes, then we can use this setting to override the given final status. |
diff --git a/gruntfile.js b/gruntfile.js
index 57ef26b..ea5699a 100644
--- a/gruntfile.js
+++ b/gruntfile.js
@@ -12,6 +12,15 @@ module.exports = function(grunt) {
src: ['test/**/*.spec.js'],
},
},
+ watch: {
+ scripts: {
+ files: ['src/**/*.js'],
+ tasks: ['browserify:development'],
+ options: {
+ spawn: false,
+ },
+ },
+ },
browserify: {
development: {
src: [
@@ -69,6 +78,7 @@ module.exports = function(grunt) {
grunt.loadNpmTasks('grunt-browserify');
grunt.loadNpmTasks('grunt-mocha-test');
+ grunt.loadNpmTasks('grunt-contrib-watch');
grunt.registerTask('test', 'mochaTest');
diff --git a/index.html b/index.html
deleted file mode 100644
index 8d8888b..0000000
--- a/index.html
+++ /dev/null
@@ -1,11 +0,0 @@
-
-
-
-
- Test Page
-
-
-
-
-
-
\ No newline at end of file
diff --git a/package.json b/package.json
index 6143582..7233ccd 100644
--- a/package.json
+++ b/package.json
@@ -27,6 +27,7 @@
"grunt": "^1.1.0",
"grunt-browserify": "^5.3.0",
"grunt-cli": "^1.3.2",
+ "grunt-contrib-watch": "^1.1.0",
"grunt-mocha-test": "^0.13.3",
"jsdoc": "^3.6.4",
"jsdoc-babel": "^0.5.0",
@@ -38,7 +39,9 @@
"nyc": "^15.0.1"
},
"scripts": {
- "test": "./node_modules/.bin/mocha --require @babel/register --bdd --recursive --reporter list"
+ "test": "./node_modules/.bin/mocha --require @babel/register --bdd --recursive --reporter list",
+ "compile": "./node_modules/.bin/grunt default",
+ "fix": "./node_modules/.bin/eslint ./src --fix"
},
"repository": {
"type": "git",
@@ -55,5 +58,7 @@
"url": "https://github.com/jcputney/scorm-again/issues"
},
"homepage": "https://github.com/jcputney/scorm-again",
- "dependencies": {}
+ "dependencies": {
+ "@types/mocha": "^7.0.2"
+ }
}
diff --git a/src/BaseAPI.js b/src/BaseAPI.js
index 4bb884f..dddac88 100644
--- a/src/BaseAPI.js
+++ b/src/BaseAPI.js
@@ -17,12 +17,28 @@ export default class BaseAPI {
#error_codes;
#settings = {
autocommit: false,
- autocommitSeconds: 60,
+ autocommitSeconds: 10,
lmsCommitUrl: false,
dataCommitFormat: 'json', // valid formats are 'json' or 'flattened', 'params'
commitRequestDataType: 'application/json;charset=UTF-8',
autoProgress: false,
logLevel: global_constants.LOG_LEVEL_ERROR,
+ responseHandler: function(xhr) {
+ let result;
+ if (typeof xhr !== 'undefined') {
+ result = JSON.parse(xhr.responseText);
+ if (result === null || !{}.hasOwnProperty.call(result, 'result')) {
+ result = {};
+ if (xhr.status === 200) {
+ result.result = global_constants.SCORM_TRUE;
+ } else {
+ result.result = global_constants.SCORM_FALSE;
+ result.errorCode = 101;
+ }
+ }
+ }
+ return result;
+ },
};
cmi;
startingData: {};
@@ -112,11 +128,11 @@ export default class BaseAPI {
this.currentState = global_constants.STATE_TERMINATED;
const result = this.storeData(true);
- if (result.errorCode && result.errorCode > 0) {
+ if (typeof result.errorCode !== 'undefined' && result.errorCode > 0) {
this.throwSCORMError(result.errorCode);
}
returnValue = result.result ?
- result.result : global_constants.SCORM_FALSE;
+ result.result : global_constants.SCORM_FALSE;
if (checkTerminated) this.lastErrorCode = 0;
@@ -240,7 +256,7 @@ export default class BaseAPI {
this.throwSCORMError(result.errorCode);
}
returnValue = result.result ?
- result.result : global_constants.SCORM_FALSE;
+ result.result : global_constants.SCORM_FALSE;
this.apiLog(callbackName, 'HttpRequest', ' Result: ' + returnValue,
global_constants.LOG_LEVEL_DEBUG);
@@ -436,9 +452,9 @@ export default class BaseAPI {
*/
_checkObjectHasProperty(refObject, attribute: String) {
return Object.hasOwnProperty.call(refObject, attribute) ||
- Object.getOwnPropertyDescriptor(
- Object.getPrototypeOf(refObject), attribute) ||
- (attribute in refObject);
+ Object.getOwnPropertyDescriptor(
+ Object.getPrototypeOf(refObject), attribute) ||
+ (attribute in refObject);
}
/**
@@ -502,15 +518,15 @@ export default class BaseAPI {
const invalidErrorMessage = `The data model element passed to ${methodName} (${CMIElement}) is not a valid SCORM data model element.`;
const invalidErrorCode = scorm2004 ?
- this.#error_codes.UNDEFINED_DATA_MODEL :
- this.#error_codes.GENERAL;
+ this.#error_codes.UNDEFINED_DATA_MODEL :
+ this.#error_codes.GENERAL;
for (let i = 0; i < structure.length; i++) {
const attribute = structure[i];
if (i === structure.length - 1) {
if (scorm2004 && (attribute.substr(0, 8) === '{target=') &&
- (typeof refObject._isTargetValid == 'function')) {
+ (typeof refObject._isTargetValid == 'function')) {
this.throwSCORMError(this.#error_codes.READ_ONLY_ELEMENT);
} else if (!this._checkObjectHasProperty(refObject, attribute)) {
this.throwSCORMError(invalidErrorCode, invalidErrorMessage);
@@ -616,8 +632,8 @@ export default class BaseAPI {
const uninitializedErrorMessage = `The data model element passed to ${methodName} (${CMIElement}) has not been initialized.`;
const invalidErrorMessage = `The data model element passed to ${methodName} (${CMIElement}) is not a valid SCORM data model element.`;
const invalidErrorCode = scorm2004 ?
- this.#error_codes.UNDEFINED_DATA_MODEL :
- this.#error_codes.GENERAL;
+ this.#error_codes.UNDEFINED_DATA_MODEL :
+ this.#error_codes.GENERAL;
for (let i = 0; i < structure.length; i++) {
attribute = structure[i];
@@ -631,7 +647,7 @@ export default class BaseAPI {
}
} else {
if ((String(attribute).substr(0, 8) === '{target=') &&
- (typeof refObject._isTargetValid == 'function')) {
+ (typeof refObject._isTargetValid == 'function')) {
const target = String(attribute).
substr(8, String(attribute).length - 9);
return refObject._isTargetValid(target);
@@ -745,11 +761,17 @@ export default class BaseAPI {
* @param {*} value
*/
processListeners(functionName: String, CMIElement: String, value: any) {
+ this.apiLog(functionName, CMIElement, value);
for (let i = 0; i < this.listenerArray.length; i++) {
const listener = this.listenerArray[i];
const functionsMatch = listener.functionName === functionName;
const listenerHasCMIElement = !!listener.CMIElement;
- const CMIElementsMatch = listener.CMIElement === CMIElement;
+ let CMIElementsMatch = false;
+ if (CMIElement && listener.CMIElement && listener.CMIElement.substring(listener.CMIElement.length - 1) === '*') {
+ CMIElementsMatch = CMIElement.indexOf(listener.CMIElement.substring(0, listener.CMIElement.length - 1)) === 0;
+ } else {
+ CMIElementsMatch = listener.CMIElement === CMIElement;
+ }
if (functionsMatch && (!listenerHasCMIElement || CMIElementsMatch)) {
listener.callback(CMIElement, value);
@@ -901,17 +923,29 @@ export default class BaseAPI {
} else {
httpReq.setRequestHeader('Content-Type',
this.settings.commitRequestDataType);
+ httpReq.responseType = 'json';
httpReq.send(JSON.stringify(params));
}
} catch (e) {
return genericError;
}
+ let result;
try {
- return JSON.parse(httpReq.responseText);
+ if (typeof this.settings.responseHandler === 'function') {
+ result = this.settings.responseHandler(httpReq);
+ } else {
+ result = JSON.parse(httpReq.responseText);
+ }
} catch (e) {
return genericError;
}
+
+ if (typeof result === 'undefined') {
+ return genericError;
+ }
+
+ return result;
}
/**
diff --git a/src/Scorm2004API.js b/src/Scorm2004API.js
index 6a15b5e..1b49a5b 100644
--- a/src/Scorm2004API.js
+++ b/src/Scorm2004API.js
@@ -554,10 +554,13 @@ export default class Scorm2004API extends BaseAPI {
}
const result = this.processHttpRequest(this.settings.lmsCommitUrl,
commitObject);
+
// check if this is a sequencing call, and then call the necessary JS
- if (navRequest && result.navRequest !== undefined &&
+ {
+ if (navRequest && result.navRequest !== undefined &&
result.navRequest !== '') {
- Function(`"use strict";(() => { ${result.navRequest} })()`)();
+ Function(`"use strict";(() => { ${result.navRequest} })()`)();
+ }
}
return result;
} else {