Initial push, working on test cases
This commit is contained in:
10
.babelrc
Normal file
10
.babelrc
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"presets": [
|
||||||
|
"@babel/preset-env",
|
||||||
|
"@babel/preset-flow"
|
||||||
|
],
|
||||||
|
"plugins": [
|
||||||
|
"@babel/plugin-proposal-class-properties",
|
||||||
|
"@babel/plugin-proposal-private-methods"
|
||||||
|
]
|
||||||
|
}
|
||||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -59,3 +59,6 @@ typings/
|
|||||||
|
|
||||||
# next.js build output
|
# next.js build output
|
||||||
.next
|
.next
|
||||||
|
|
||||||
|
# JetBrains Project Directory
|
||||||
|
.idea/
|
||||||
5508
package-lock.json
generated
Normal file
5508
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
42
package.json
Normal file
42
package.json
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
{
|
||||||
|
"name": "scorm-again",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "A modern SCORM JavaScript runtime library for AICC, Scorm 1.2, and Scorm 2004",
|
||||||
|
"main": "index.js",
|
||||||
|
"directories": {
|
||||||
|
"test": "test"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@babel/cli": "^7.7.0",
|
||||||
|
"@babel/core": "^7.7.2",
|
||||||
|
"@babel/node": "^7.7.0",
|
||||||
|
"@babel/plugin-proposal-class-properties": "^7.7.0",
|
||||||
|
"@babel/plugin-proposal-private-methods": "^7.6.0",
|
||||||
|
"@babel/preset-env": "^7.7.1",
|
||||||
|
"@babel/preset-flow": "^7.0.0",
|
||||||
|
"@babel/register": "^7.7.0",
|
||||||
|
"chai": "^4.2.0",
|
||||||
|
"mocha": "^6.2.2",
|
||||||
|
"nodemon": "^1.19.4",
|
||||||
|
"nyc": "^14.1.1"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"test": "./node_modules/.bin/mocha --compilers js:@babel/register"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git+ssh://git@github.com/jcputney/scorm-again.git"
|
||||||
|
},
|
||||||
|
"keywords": [
|
||||||
|
"scorm",
|
||||||
|
"aicc",
|
||||||
|
"tincan",
|
||||||
|
"cmi"
|
||||||
|
],
|
||||||
|
"author": "Jonathan Putney <jonathan@putney.io>",
|
||||||
|
"license": "GPL-3.0-or-later",
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://github.com/jcputney/scorm-again/issues"
|
||||||
|
},
|
||||||
|
"homepage": "https://github.com/jcputney/scorm-again#readme"
|
||||||
|
}
|
||||||
11
src/.flowconfig
Normal file
11
src/.flowconfig
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
[ignore]
|
||||||
|
|
||||||
|
[include]
|
||||||
|
|
||||||
|
[libs]
|
||||||
|
|
||||||
|
[lints]
|
||||||
|
|
||||||
|
[options]
|
||||||
|
|
||||||
|
[strict]
|
||||||
57
src/AICC.js
Normal file
57
src/AICC.js
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) Noverant, Inc - All Rights Reserved Unauthorized copying of this file, via any
|
||||||
|
* medium is strictly prohibited Proprietary and confidential
|
||||||
|
*/
|
||||||
|
|
||||||
|
// @flow
|
||||||
|
import Scorm12API from './Scorm12API';
|
||||||
|
import {
|
||||||
|
CMIInteractionsCorrectResponsesObject,
|
||||||
|
CMIInteractionsObject,
|
||||||
|
CMIInteractionsObjectivesObject,
|
||||||
|
CMIObjectivesObject
|
||||||
|
} from "./cmi/scorm12_cmi";
|
||||||
|
import {CMIEvaluationCommentsObject, CMITriesObject, NAV} from "./cmi/aicc_cmi";
|
||||||
|
|
||||||
|
class AICC extends Scorm12API {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.nav = new NAV(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets or builds a new child element to add to the array.
|
||||||
|
*
|
||||||
|
* @param CMIElement
|
||||||
|
* @param value
|
||||||
|
*/
|
||||||
|
getChildElement(CMIElement, value) {
|
||||||
|
let newChild;
|
||||||
|
|
||||||
|
if (this.stringContains(CMIElement, "cmi.objectives")) {
|
||||||
|
newChild = new CMIObjectivesObject(this);
|
||||||
|
} else if (this.stringContains(CMIElement, ".correct_responses")) {
|
||||||
|
newChild = new CMIInteractionsCorrectResponsesObject(this);
|
||||||
|
} else if (this.stringContains(CMIElement, ".objectives")) {
|
||||||
|
newChild = new CMIInteractionsObjectivesObject(this);
|
||||||
|
} else if (this.stringContains(CMIElement, "cmi.interactions")) {
|
||||||
|
newChild = new CMIInteractionsObject(this);
|
||||||
|
} else if (this.stringContains(CMIElement, "cmi.evaluation.comments")) {
|
||||||
|
newChild = new CMIEvaluationCommentsObject(this);
|
||||||
|
} else if (this.stringContains(CMIElement, "cmi.student_data.tries")) {
|
||||||
|
newChild = new CMITriesObject(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
return newChild;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replace the whole API with another
|
||||||
|
*/
|
||||||
|
replaceWithAnotherScormAPI(newAPI) {
|
||||||
|
// Data Model
|
||||||
|
this.cmi = newAPI.cmi;
|
||||||
|
this.nav = newAPI.nav;
|
||||||
|
}
|
||||||
|
}
|
||||||
665
src/BaseAPI.js
Normal file
665
src/BaseAPI.js
Normal file
@@ -0,0 +1,665 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) Noverant, Inc - All Rights Reserved Unauthorized copying of this file, via any
|
||||||
|
* medium is strictly prohibited Proprietary and confidential
|
||||||
|
*/
|
||||||
|
|
||||||
|
// @flow
|
||||||
|
import {CMIArray} from "./cmi/common";
|
||||||
|
import {base_error_codes} from "./constants";
|
||||||
|
|
||||||
|
const api_constants = {
|
||||||
|
SCORM_TRUE: "true",
|
||||||
|
SCORM_FALSE: "false",
|
||||||
|
STATE_NOT_INITIALIZED: 0,
|
||||||
|
STATE_INITIALIZED: 1,
|
||||||
|
STATE_TERMINATED: 2,
|
||||||
|
LOG_LEVEL_DEBUG: 1,
|
||||||
|
LOG_LEVEL_INFO: 2,
|
||||||
|
LOG_LEVEL_WARNING: 3,
|
||||||
|
LOG_LEVEL_ERROR: 4,
|
||||||
|
LOG_LEVEL_NONE: 5
|
||||||
|
};
|
||||||
|
|
||||||
|
export default class BaseAPI {
|
||||||
|
#timeout;
|
||||||
|
#error_codes;
|
||||||
|
cmi;
|
||||||
|
|
||||||
|
constructor(error_codes) {
|
||||||
|
this.currentState = api_constants.STATE_NOT_INITIALIZED;
|
||||||
|
this.apiLogLevel = api_constants.LOG_LEVEL_ERROR;
|
||||||
|
this.lastErrorCode = 0;
|
||||||
|
this.listenerArray = [];
|
||||||
|
|
||||||
|
this.#timeout = null;
|
||||||
|
this.#error_codes = error_codes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns {string} bool
|
||||||
|
*/
|
||||||
|
APIInitialize(callbackName: String, initializeMessage?: String, terminationMessage?: String) {
|
||||||
|
let returnValue = api_constants.SCORM_FALSE;
|
||||||
|
|
||||||
|
if (this.isInitialized()) {
|
||||||
|
this.throwSCORMError(this.#error_codes.INITIALIZED, initializeMessage);
|
||||||
|
} else if (this.isTerminated()) {
|
||||||
|
this.throwSCORMError(this.#error_codes.TERMINATED, terminationMessage);
|
||||||
|
} else {
|
||||||
|
this.currentState = api_constants.STATE_INITIALIZED;
|
||||||
|
this.lastErrorCode = 0;
|
||||||
|
returnValue = api_constants.SCORM_TRUE;
|
||||||
|
this.processListeners(callbackName);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.apiLog(callbackName, null, "returned: " + returnValue, api_constants.LOG_LEVEL_INFO);
|
||||||
|
this.clearSCORMError(returnValue);
|
||||||
|
|
||||||
|
return returnValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns {string} bool
|
||||||
|
*/
|
||||||
|
APITerminate(callbackName: String, checkTerminated: boolean) {
|
||||||
|
let returnValue = api_constants.SCORM_FALSE;
|
||||||
|
|
||||||
|
if (this.checkState(checkTerminated, this.#error_codes.TERMINATION_BEFORE_INIT, this.#error_codes.MULTIPLE_TERMINATION)) {
|
||||||
|
if (checkTerminated) this.lastErrorCode = 0;
|
||||||
|
this.currentState = api_constants.STATE_TERMINATED;
|
||||||
|
returnValue = api_constants.SCORM_TRUE;
|
||||||
|
this.processListeners(callbackName);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.apiLog(callbackName, null, "returned: " + returnValue, api_constants.LOG_LEVEL_INFO);
|
||||||
|
this.clearSCORMError(returnValue);
|
||||||
|
|
||||||
|
return returnValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param callbackName
|
||||||
|
* @param checkTerminated
|
||||||
|
* @param CMIElement
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
APIGetValue(callbackName: String, checkTerminated: boolean, CMIElement) {
|
||||||
|
let returnValue = "";
|
||||||
|
|
||||||
|
if (this.checkState(checkTerminated, this.#error_codes.RETRIEVE_BEFORE_INIT, this.#error_codes.RETRIEVE_AFTER_TERM)) {
|
||||||
|
if (checkTerminated) this.lastErrorCode = 0;
|
||||||
|
returnValue = this.getCMIValue(CMIElement);
|
||||||
|
this.processListeners(callbackName, CMIElement);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.apiLog(callbackName, CMIElement, ": returned: " + returnValue, api_constants.LOG_LEVEL_INFO);
|
||||||
|
this.clearSCORMError(returnValue);
|
||||||
|
|
||||||
|
return returnValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param callbackName
|
||||||
|
* @param checkTerminated
|
||||||
|
* @param CMIElement
|
||||||
|
* @param value
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
APISetValue(callbackName: String, checkTerminated: boolean, CMIElement, value) {
|
||||||
|
let returnValue = "";
|
||||||
|
|
||||||
|
if (this.checkState(checkTerminated, this.#error_codes.STORE_BEFORE_INIT, this.#error_codes.STORE_AFTER_TERM)) {
|
||||||
|
if (checkTerminated) this.lastErrorCode = 0;
|
||||||
|
returnValue = this.setCMIValue(CMIElement, value);
|
||||||
|
this.processListeners(callbackName, CMIElement, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.apiLog(callbackName, CMIElement, ": " + value + ": result: " + returnValue, api_constants.LOG_LEVEL_INFO);
|
||||||
|
this.clearSCORMError(returnValue);
|
||||||
|
|
||||||
|
return returnValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Orders LMS to store all content parameters
|
||||||
|
*
|
||||||
|
* @returns {string} bool
|
||||||
|
*/
|
||||||
|
APICommit(callbackName: String, checkTerminated: boolean) {
|
||||||
|
let returnValue = api_constants.SCORM_FALSE;
|
||||||
|
|
||||||
|
if (this.checkState(checkTerminated, this.#error_codes.COMMIT_BEFORE_INIT, this.#error_codes.COMMIT_AFTER_TERM)) {
|
||||||
|
if (checkTerminated) this.lastErrorCode = 0;
|
||||||
|
returnValue = api_constants.SCORM_TRUE;
|
||||||
|
this.processListeners(callbackName);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.apiLog(callbackName, null, "returned: " + returnValue, api_constants.LOG_LEVEL_INFO);
|
||||||
|
this.clearSCORMError(returnValue);
|
||||||
|
|
||||||
|
return returnValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns last error code
|
||||||
|
*
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
APIGetLastError(callbackName: String) {
|
||||||
|
let returnValue = String(this.lastErrorCode);
|
||||||
|
|
||||||
|
this.processListeners(callbackName);
|
||||||
|
|
||||||
|
this.apiLog(callbackName, null, "returned: " + returnValue, api_constants.LOG_LEVEL_INFO);
|
||||||
|
|
||||||
|
return returnValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the errorNumber error description
|
||||||
|
*
|
||||||
|
* @param callbackName
|
||||||
|
* @param CMIErrorCode
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
APIGetErrorString(callbackName: String, CMIErrorCode) {
|
||||||
|
let returnValue = "";
|
||||||
|
|
||||||
|
if (CMIErrorCode !== null && CMIErrorCode !== "") {
|
||||||
|
returnValue = this.getLmsErrorMessageDetails(CMIErrorCode);
|
||||||
|
this.processListeners(callbackName);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.apiLog(callbackName, null, "returned: " + returnValue, api_constants.LOG_LEVEL_INFO);
|
||||||
|
|
||||||
|
return returnValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a comprehensive description of the errorNumber error.
|
||||||
|
*
|
||||||
|
* @param callbackName
|
||||||
|
* @param CMIErrorCode
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
APIGetDiagnostic(callbackName: String, CMIErrorCode) {
|
||||||
|
let returnValue = "";
|
||||||
|
|
||||||
|
if (CMIErrorCode !== null && CMIErrorCode !== "") {
|
||||||
|
returnValue = this.getLmsErrorMessageDetails(CMIErrorCode, true);
|
||||||
|
this.processListeners(callbackName);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.apiLog(callbackName, null, "returned: " + returnValue, api_constants.LOG_LEVEL_INFO);
|
||||||
|
|
||||||
|
return returnValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks the LMS state and ensures it has been initialized
|
||||||
|
*/
|
||||||
|
checkState(checkTerminated: boolean, beforeInitError: number, afterTermError?: number) {
|
||||||
|
if (this.isNotInitialized()) {
|
||||||
|
this.throwSCORMError(beforeInitError);
|
||||||
|
return false;
|
||||||
|
} else if (checkTerminated && this.isTerminated()) {
|
||||||
|
this.throwSCORMError(afterTermError);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logging for all SCORM actions
|
||||||
|
*
|
||||||
|
* @param functionName
|
||||||
|
* @param CMIElement
|
||||||
|
* @param logMessage
|
||||||
|
* @param messageLevel
|
||||||
|
*/
|
||||||
|
apiLog(functionName: String, CMIElement: String, logMessage: String, messageLevel: number) {
|
||||||
|
logMessage = this.formatMessage(functionName, CMIElement, logMessage);
|
||||||
|
|
||||||
|
if (messageLevel >= this.apiLogLevel) {
|
||||||
|
switch (messageLevel) {
|
||||||
|
case api_constants.LOG_LEVEL_ERROR:
|
||||||
|
console.error(logMessage);
|
||||||
|
break;
|
||||||
|
case api_constants.LOG_LEVEL_WARNING:
|
||||||
|
console.warn(logMessage);
|
||||||
|
break;
|
||||||
|
case api_constants.LOG_LEVEL_INFO:
|
||||||
|
console.info(logMessage);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clears the last SCORM error code on success
|
||||||
|
*/
|
||||||
|
clearSCORMError(success: String) {
|
||||||
|
if (success !== api_constants.SCORM_FALSE) {
|
||||||
|
this.lastErrorCode = 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Formats the SCORM messages for easy reading
|
||||||
|
*
|
||||||
|
* @param functionName
|
||||||
|
* @param CMIElement
|
||||||
|
* @param message
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
formatMessage(functionName: String, CMIElement: String, message: String) {
|
||||||
|
let baseLength = 20;
|
||||||
|
let messageString = "";
|
||||||
|
|
||||||
|
messageString += functionName;
|
||||||
|
|
||||||
|
let fillChars = baseLength - messageString.length;
|
||||||
|
|
||||||
|
for (let i = 0; i < fillChars; i++) {
|
||||||
|
messageString += " ";
|
||||||
|
}
|
||||||
|
|
||||||
|
messageString += ": ";
|
||||||
|
|
||||||
|
if (CMIElement) {
|
||||||
|
let CMIElementBaseLength = 70;
|
||||||
|
|
||||||
|
messageString += CMIElement;
|
||||||
|
|
||||||
|
fillChars = CMIElementBaseLength - messageString.length;
|
||||||
|
|
||||||
|
for (let j = 0; j < fillChars; j++) {
|
||||||
|
messageString += " ";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (message) {
|
||||||
|
messageString += message;
|
||||||
|
}
|
||||||
|
|
||||||
|
return messageString;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks to see if {str} contains {tester}
|
||||||
|
*
|
||||||
|
* @param str String to check against
|
||||||
|
* @param tester String to check for
|
||||||
|
*/
|
||||||
|
stringContains(str: String, tester: String) {
|
||||||
|
return str.indexOf(tester) > -1;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the message that corresponds to errorNumber
|
||||||
|
* APIs that inherit BaseAPI should override this function
|
||||||
|
*/
|
||||||
|
getLmsErrorMessageDetails(_errorNumber, _detail) {
|
||||||
|
return "No error";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the value for the specific element.
|
||||||
|
* APIs that inherit BaseAPI should override this function
|
||||||
|
*/
|
||||||
|
getCMIValue(_CMIElement) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the value for the specific element.
|
||||||
|
* APIs that inherit BaseAPI should override this function
|
||||||
|
*/
|
||||||
|
setCMIValue(_CMIElement, _value) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
_commonSetCMIValue(methodName: String, scorm2004: boolean, CMIElement, value) {
|
||||||
|
if (!CMIElement || CMIElement === "") {
|
||||||
|
return api_constants.SCORM_FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
let structure = CMIElement.split(".");
|
||||||
|
let refObject = this;
|
||||||
|
let returnValue = api_constants.SCORM_FALSE;
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
for (let i = 0; i < structure.length; i++) {
|
||||||
|
let attribute = structure[i];
|
||||||
|
|
||||||
|
if (i === structure.length - 1) {
|
||||||
|
if (scorm2004 && (attribute.substr(0, 8) === "{target=") && (typeof refObject._isTargetValid == "function")) {
|
||||||
|
this.throwSCORMError(this.#error_codes.READ_ONLY_ELEMENT);
|
||||||
|
} else if (!refObject.hasOwnProperty(attribute)) {
|
||||||
|
this.throwSCORMError(invalidErrorCode, invalidErrorMessage);
|
||||||
|
} else {
|
||||||
|
if (this.stringContains(CMIElement, ".correct_responses")){
|
||||||
|
this.validateCorrectResponse(CMIElement, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!scorm2004 || this.lastErrorCode === 0) {
|
||||||
|
refObject[attribute] = value;
|
||||||
|
returnValue = api_constants.SCORM_TRUE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
refObject = refObject[attribute];
|
||||||
|
if (!refObject) {
|
||||||
|
this.throwSCORMError(invalidErrorCode, invalidErrorMessage);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (refObject.prototype === CMIArray) {
|
||||||
|
let index = parseInt(structure[i + 1], 10);
|
||||||
|
|
||||||
|
// SCO is trying to set an item on an array
|
||||||
|
if (!isNaN(index)) {
|
||||||
|
let item = refObject.childArray[index];
|
||||||
|
|
||||||
|
if (item) {
|
||||||
|
refObject = item;
|
||||||
|
} else {
|
||||||
|
let newChild = this.getChildElement(CMIElement, value);
|
||||||
|
|
||||||
|
if (!newChild) {
|
||||||
|
this.throwSCORMError(invalidErrorCode, invalidErrorMessage);
|
||||||
|
} else {
|
||||||
|
refObject.childArray.push(newChild);
|
||||||
|
refObject = newChild;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Have to update i value to skip the array position
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (returnValue === api_constants.SCORM_FALSE) {
|
||||||
|
this.apiLog(methodName, null, `There was an error setting the value for: ${CMIElement}, value of: ${value}`, api_constants.LOG_LEVEL_WARNING);
|
||||||
|
}
|
||||||
|
|
||||||
|
return returnValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
validateCorrectResponse(_CMIElement, _value) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets or builds a new child element to add to the array.
|
||||||
|
* APIs that inherit BaseAPI should override this method
|
||||||
|
*/
|
||||||
|
getChildElement(_CMIElement) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a value from the CMI Object
|
||||||
|
*
|
||||||
|
* @param methodName
|
||||||
|
* @param scorm2004
|
||||||
|
* @param CMIElement
|
||||||
|
* @returns {*}
|
||||||
|
*/
|
||||||
|
_commonGetCMIValue(methodName: String, scorm2004: boolean, CMIElement) {
|
||||||
|
if (!CMIElement || CMIElement === "") {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
let structure = CMIElement.split(".");
|
||||||
|
let refObject = this;
|
||||||
|
let attribute = null;
|
||||||
|
|
||||||
|
for (let i = 0; i < structure.length; i++) {
|
||||||
|
attribute = structure[i];
|
||||||
|
|
||||||
|
if(!scorm2004) {
|
||||||
|
if (i === structure.length - 1) {
|
||||||
|
if (!refObject.hasOwnProperty(attribute)) {
|
||||||
|
this.throwSCORMError(101, "getCMIValue did not find a value for: " + CMIElement);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if ((String(attribute).substr(0, 8) === "{target=") && (typeof refObject._isTargetValid == "function")) {
|
||||||
|
let target = String(attribute).substr(8, String(attribute).length - 9);
|
||||||
|
return refObject._isTargetValid(target);
|
||||||
|
} else if (!refObject.hasOwnProperty(attribute)) {
|
||||||
|
this.throwSCORMError(401, "The data model element passed to GetValue (" + CMIElement + ") is not a valid SCORM data model element.");
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
refObject = refObject[attribute];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (refObject === null || refObject === undefined) {
|
||||||
|
if(!scorm2004) {
|
||||||
|
if (attribute === "_children") {
|
||||||
|
this.throwSCORMError(202);
|
||||||
|
} else if (attribute === "_count") {
|
||||||
|
this.throwSCORMError(203);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
} else {
|
||||||
|
return refObject;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the API's current state is STATE_INITIALIZED
|
||||||
|
*/
|
||||||
|
isInitialized() {
|
||||||
|
return this.currentState === api_constants.STATE_INITIALIZED;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the API's current state is STATE_NOT_INITIALIZED
|
||||||
|
*/
|
||||||
|
isNotInitialized() {
|
||||||
|
return this.currentState === api_constants.STATE_NOT_INITIALIZED;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the API's current state is STATE_TERMINATED
|
||||||
|
*/
|
||||||
|
isTerminated() {
|
||||||
|
return this.currentState === api_constants.STATE_TERMINATED;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides a mechanism for attaching to a specific SCORM event
|
||||||
|
*
|
||||||
|
* @param listenerName
|
||||||
|
* @param callback
|
||||||
|
*/
|
||||||
|
on(listenerName: String, callback: function) {
|
||||||
|
if (!callback) return;
|
||||||
|
|
||||||
|
let listenerFunctions = listenerName.split(" ");
|
||||||
|
for (let i = 0; i < listenerFunctions.length; i++) {
|
||||||
|
let listenerSplit = listenerFunctions[i].split(".");
|
||||||
|
if (listenerSplit.length === 0) return;
|
||||||
|
|
||||||
|
let functionName = listenerSplit[0];
|
||||||
|
|
||||||
|
let CMIElement = null;
|
||||||
|
if (listenerSplit.length > 1) {
|
||||||
|
CMIElement = listenerName.replace(functionName + ".", "");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.listenerArray.push({
|
||||||
|
functionName: functionName,
|
||||||
|
CMIElement: CMIElement,
|
||||||
|
callback: callback
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Processes any 'on' listeners that have been created
|
||||||
|
*
|
||||||
|
* @param functionName
|
||||||
|
* @param CMIElement
|
||||||
|
* @param value
|
||||||
|
*/
|
||||||
|
processListeners(functionName: String, CMIElement: String, value: any) {
|
||||||
|
for (let i = 0; i < this.listenerArray.length; i++) {
|
||||||
|
let listener = this.listenerArray[i];
|
||||||
|
let functionsMatch = listener.functionName === functionName;
|
||||||
|
let listenerHasCMIElement = !!listener.CMIElement;
|
||||||
|
let CMIElementsMatch = listener.CMIElement === CMIElement;
|
||||||
|
|
||||||
|
if (functionsMatch && (!listenerHasCMIElement || CMIElementsMatch)) {
|
||||||
|
listener.callback(CMIElement, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Throws a SCORM error
|
||||||
|
*
|
||||||
|
* @param errorNumber
|
||||||
|
* @param message
|
||||||
|
*/
|
||||||
|
throwSCORMError(errorNumber: number, message: String) {
|
||||||
|
if (!message) {
|
||||||
|
message = this.getLmsErrorMessageDetails(errorNumber);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.apiLog("throwSCORMError", null, errorNumber + ": " + message, api_constants.LOG_LEVEL_ERROR);
|
||||||
|
|
||||||
|
this.lastErrorCode = String(errorNumber);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads CMI data from a JSON object.
|
||||||
|
*/
|
||||||
|
loadFromJSON(json, CMIElement) {
|
||||||
|
if (!this.isNotInitialized()) {
|
||||||
|
console.error("loadFromJSON can only be called before the call to LMSInitialize.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
CMIElement = CMIElement || "cmi";
|
||||||
|
|
||||||
|
for (let key in json) {
|
||||||
|
if (json.hasOwnProperty(key) && json[key]) {
|
||||||
|
let currentCMIElement = CMIElement + "." + key;
|
||||||
|
let value = json[key];
|
||||||
|
|
||||||
|
if (value["childArray"]) {
|
||||||
|
for (let i = 0; i < value["childArray"].length; i++) {
|
||||||
|
this.loadFromJSON(value["childArray"][i], currentCMIElement + "." + i);
|
||||||
|
}
|
||||||
|
} else if (value.constructor === Object) {
|
||||||
|
this.loadFromJSON(value, currentCMIElement);
|
||||||
|
} else {
|
||||||
|
this.setCMIValue(currentCMIElement, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
renderCMIToJSON() {
|
||||||
|
let cmi = this.cmi;
|
||||||
|
// Do we want/need to return fields that have no set value?
|
||||||
|
// return JSON.stringify({ cmi }, (k, v) => v === undefined ? null : v, 2);
|
||||||
|
return JSON.stringify({ cmi });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the value matches the proper format. If not, throw proper error code.
|
||||||
|
*
|
||||||
|
* @param value
|
||||||
|
* @param regexPattern
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
checkValidFormat(value: String, regexPattern: String) {
|
||||||
|
const formatRegex = new RegExp(regexPattern);
|
||||||
|
if(!value || !value.match(formatRegex)) {
|
||||||
|
this.throwSCORMError(this.#error_codes.TYPE_MISMATCH);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the value matches the proper range. If not, throw proper error code.
|
||||||
|
*
|
||||||
|
* @param value
|
||||||
|
* @param rangePattern
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
checkValidRange(value: any, rangePattern: String) {
|
||||||
|
const ranges = rangePattern.split('#');
|
||||||
|
value = value * 1.0;
|
||||||
|
if(value >= ranges[0]) {
|
||||||
|
if((ranges[1] === '*') || (value <= ranges[1])) {
|
||||||
|
this.clearSCORMError(api_constants.SCORM_TRUE);
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
this.throwSCORMError(this.#error_codes.VALUE_OUT_OF_RANGE);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.throwSCORMError(this.#error_codes.VALUE_OUT_OF_RANGE);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Throws a SCORM error
|
||||||
|
*
|
||||||
|
* @param when the number of milliseconds to wait before committing
|
||||||
|
*/
|
||||||
|
scheduleCommit(when: number) {
|
||||||
|
this.#timeout = new ScheduledCommit(this, when);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clears and cancels any currently scheduled commits
|
||||||
|
*/
|
||||||
|
clearScheduledCommit() {
|
||||||
|
if (this.#timeout) {
|
||||||
|
this.#timeout.cancel();
|
||||||
|
this.#timeout = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ScheduledCommit {
|
||||||
|
#API;
|
||||||
|
#cancelled: false;
|
||||||
|
#timeout;
|
||||||
|
|
||||||
|
constructor(API: any, when: number) {
|
||||||
|
this.#API = API;
|
||||||
|
this.#timeout = setTimeout(this.#wrapper, when);
|
||||||
|
}
|
||||||
|
|
||||||
|
cancel() {
|
||||||
|
this.#cancelled = true;
|
||||||
|
if (this.#timeout) {
|
||||||
|
clearTimeout(this.#timeout);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#wrapper() {
|
||||||
|
if (!this.#cancelled) {
|
||||||
|
this.#API.LMSCommit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
181
src/Scorm12API.js
Normal file
181
src/Scorm12API.js
Normal file
@@ -0,0 +1,181 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) Noverant, Inc - All Rights Reserved Unauthorized copying of this file, via any
|
||||||
|
* medium is strictly prohibited Proprietary and confidential
|
||||||
|
*/
|
||||||
|
|
||||||
|
// @flow
|
||||||
|
import BaseAPI from './BaseAPI';
|
||||||
|
import {
|
||||||
|
CMI,
|
||||||
|
CMIInteractionsCorrectResponsesObject,
|
||||||
|
CMIInteractionsObject, CMIInteractionsObjectivesObject,
|
||||||
|
CMIObjectivesObject
|
||||||
|
} from "./cmi/scorm12_cmi";
|
||||||
|
import * as Utilities from './utilities';
|
||||||
|
import {scorm12_constants, scorm12_error_codes} from "./constants";
|
||||||
|
import {scorm12_regex} from "./regex";
|
||||||
|
|
||||||
|
const constants = scorm12_constants;
|
||||||
|
|
||||||
|
export default class Scorm12API extends BaseAPI {
|
||||||
|
constructor() {
|
||||||
|
super(scorm12_error_codes);
|
||||||
|
|
||||||
|
this.cmi = new CMI(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns {string} bool
|
||||||
|
*/
|
||||||
|
LMSInitialize() {
|
||||||
|
return this.APIInitialize("LMSInitialize", "LMS was already initialized!", "LMS is already finished!");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns {string} bool
|
||||||
|
*/
|
||||||
|
LMSFinish() {
|
||||||
|
return this.APITerminate("LMSFinish", false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param CMIElement
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
LMSGetValue(CMIElement) {
|
||||||
|
return this.APIGetValue("LMSGetValue", false, CMIElement);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param CMIElement
|
||||||
|
* @param value
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
LMSSetValue(CMIElement, value) {
|
||||||
|
return this.APISetValue("LMSSetValue", false, CMIElement, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Orders LMS to store all content parameters
|
||||||
|
*
|
||||||
|
* @returns {string} bool
|
||||||
|
*/
|
||||||
|
LMSCommit() {
|
||||||
|
return this.APICommit("LMSCommit", false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns last error code
|
||||||
|
*
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
LMSGetLastError() {
|
||||||
|
return this.APIGetLastError("LMSGetLastError");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the errorNumber error description
|
||||||
|
*
|
||||||
|
* @param CMIErrorCode
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
LMSGetErrorString(CMIErrorCode) {
|
||||||
|
return this.APIGetErrorString("LMSGetErrorString", CMIErrorCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a comprehensive description of the errorNumber error.
|
||||||
|
*
|
||||||
|
* @param CMIErrorCode
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
LMSGetDiagnostic(CMIErrorCode) {
|
||||||
|
return this.APIGetDiagnostic("LMSGetDiagnostic", CMIErrorCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets a value on the CMI Object
|
||||||
|
*
|
||||||
|
* @param CMIElement
|
||||||
|
* @param value
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
setCMIValue(CMIElement, value) {
|
||||||
|
this._commonSetCMIValue("LMSSetValue", false, CMIElement, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a value from the CMI Object
|
||||||
|
*
|
||||||
|
* @param CMIElement
|
||||||
|
* @returns {*}
|
||||||
|
*/
|
||||||
|
getCMIValue(CMIElement) {
|
||||||
|
return this._commonGetCMIValue("getCMIValue", false, CMIElement);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets or builds a new child element to add to the array.
|
||||||
|
*
|
||||||
|
* @param CMIElement
|
||||||
|
*/
|
||||||
|
getChildElement(CMIElement, value) {
|
||||||
|
let newChild;
|
||||||
|
|
||||||
|
if (this.stringContains(CMIElement, "cmi.objectives")) {
|
||||||
|
newChild = new CMIObjectivesObject(this);
|
||||||
|
} else if (this.stringContains(CMIElement, ".correct_responses")) {
|
||||||
|
newChild = new CMIInteractionsCorrectResponsesObject(this);
|
||||||
|
} else if (this.stringContains(CMIElement, ".objectives")) {
|
||||||
|
newChild = new CMIInteractionsObjectivesObject(this);
|
||||||
|
} else if (this.stringContains(CMIElement, "cmi.interactions")) {
|
||||||
|
newChild = new CMIInteractionsObject(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
return newChild;
|
||||||
|
}
|
||||||
|
|
||||||
|
validateCorrectResponse(CMIElement, value) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the message that corresponds to errorNumber.
|
||||||
|
*/
|
||||||
|
getLmsErrorMessageDetails(errorNumber, detail) {
|
||||||
|
let basicMessage = "No Error";
|
||||||
|
let detailMessage = "No Error";
|
||||||
|
|
||||||
|
// Set error number to string since inconsistent from modules if string or number
|
||||||
|
errorNumber = String(errorNumber);
|
||||||
|
if(constants.error_descriptions[errorNumber]) {
|
||||||
|
basicMessage = constants.error_descriptions[errorNumber].basicMessage;
|
||||||
|
detailMessage = constants.error_descriptions[errorNumber].detailMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
return detail ? detailMessage : basicMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds the current session time to the existing total time.
|
||||||
|
*/
|
||||||
|
getCurrentTotalTime() {
|
||||||
|
const timeRegex = new RegExp(scorm12_regex.CMITime);
|
||||||
|
|
||||||
|
const totalTime = this.cmi.core.total_time;
|
||||||
|
const sessionTime = this.cmi.core.session_time;
|
||||||
|
|
||||||
|
const totalSeconds = Utilities.getTimeAsSeconds(totalTime, timeRegex);
|
||||||
|
const sessionSeconds = Utilities.getTimeAsSeconds(sessionTime, timeRegex);
|
||||||
|
|
||||||
|
return Utilities.getSecondsAsHHMMSS(totalSeconds + sessionSeconds);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replace the whole API with another
|
||||||
|
*/
|
||||||
|
replaceWithAnotherScormAPI(newAPI) {
|
||||||
|
// Data Model
|
||||||
|
this.cmi = newAPI.cmi;
|
||||||
|
}
|
||||||
|
}
|
||||||
382
src/Scorm2004API.js
Normal file
382
src/Scorm2004API.js
Normal file
@@ -0,0 +1,382 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) Noverant, Inc - All Rights Reserved Unauthorized copying of this file, via any
|
||||||
|
* medium is strictly prohibited Proprietary and confidential
|
||||||
|
*/
|
||||||
|
|
||||||
|
// @flow
|
||||||
|
import BaseAPI from './BaseAPI';
|
||||||
|
import {
|
||||||
|
CMI,
|
||||||
|
ADL,
|
||||||
|
CMIObjectivesObject,
|
||||||
|
CMIInteractionsObject,
|
||||||
|
CMICommentsFromLearnerObject,
|
||||||
|
CMICommentsFromLMSObject, CMIInteractionsCorrectResponsesObject, CMIInteractionsObjectivesObject
|
||||||
|
} from './cmi/scorm2004_cmi';
|
||||||
|
import * as Utilities from "./utilities";
|
||||||
|
import {scorm2004_constants, correct_responses, scorm2004_error_codes} from "./constants";
|
||||||
|
import {scorm2004_regex} from "./regex";
|
||||||
|
|
||||||
|
const constants = scorm2004_constants;
|
||||||
|
const valid_languages = constants.valid_languages;
|
||||||
|
|
||||||
|
class Scorm2004API extends BaseAPI {
|
||||||
|
version: "1.0";
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super(scorm2004_error_codes);
|
||||||
|
|
||||||
|
this.cmi = new CMI(this);
|
||||||
|
this.adl = new ADL(this);
|
||||||
|
|
||||||
|
// Rename functions to match 2004 Spec
|
||||||
|
this.Initialize = this.LMSInitialize;
|
||||||
|
this.Terminate = this.LMSTerminate;
|
||||||
|
this.GetValue = this.LMSGetValue;
|
||||||
|
this.SetValue = this.LMSSetValue;
|
||||||
|
this.Commit = this.LMSCommit;
|
||||||
|
this.GetLastError = this.LMSGetLastError;
|
||||||
|
this.GetErrorString = this.LMSGetErrorString;
|
||||||
|
this.GetDiagnostic = this.LMSGetDiagnostic;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns {string} bool
|
||||||
|
*/
|
||||||
|
LMSInitialize() {
|
||||||
|
return this.APIInitialize("Initialize");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns {string} bool
|
||||||
|
*/
|
||||||
|
LMSTerminate() {
|
||||||
|
return this.APITerminate("Terminate", true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param CMIElement
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
LMSGetValue(CMIElement) {
|
||||||
|
return this.APIGetValue("GetValue", true, CMIElement);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param CMIElement
|
||||||
|
* @param value
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
LMSSetValue(CMIElement, value) {
|
||||||
|
return this.APISetValue("SetValue", true, CMIElement, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Orders LMS to store all content parameters
|
||||||
|
*
|
||||||
|
* @returns {string} bool
|
||||||
|
*/
|
||||||
|
LMSCommit() {
|
||||||
|
return this.APICommit("Commit");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns last error code
|
||||||
|
*
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
LMSGetLastError() {
|
||||||
|
return this.APIGetLastError("GetLastError");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the errorNumber error description
|
||||||
|
*
|
||||||
|
* @param CMIErrorCode
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
LMSGetErrorString(CMIErrorCode) {
|
||||||
|
return this.APIGetErrorString("GetErrorString", CMIErrorCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a comprehensive description of the errorNumber error.
|
||||||
|
*
|
||||||
|
* @param CMIErrorCode
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
LMSGetDiagnostic(CMIErrorCode) {
|
||||||
|
return this.APIGetDiagnostic("GetDiagnostic", CMIErrorCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets a value on the CMI Object
|
||||||
|
*
|
||||||
|
* @param CMIElement
|
||||||
|
* @param value
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
setCMIValue(CMIElement, value) {
|
||||||
|
this._commonSetCMIValue("SetValue", true, CMIElement, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets or builds a new child element to add to the array.
|
||||||
|
*
|
||||||
|
* @param CMIElement
|
||||||
|
* @param value
|
||||||
|
*/
|
||||||
|
getChildElement(CMIElement, value) {
|
||||||
|
let newChild;
|
||||||
|
|
||||||
|
if (this.stringContains(CMIElement, "cmi.objectives")) {
|
||||||
|
newChild = new CMIObjectivesObject(this);
|
||||||
|
} else if (this.stringContains(CMIElement, ".correct_responses")) {
|
||||||
|
const parts = CMIElement.split('.');
|
||||||
|
const index = Number(parts[2]);
|
||||||
|
let interaction = this.cmi.interactions.childArray[index];
|
||||||
|
if(typeof interaction.type === 'undefined') {
|
||||||
|
this.throwSCORMError(scorm2004_error_codes.DEPENDENCY_NOT_ESTABLISHED);
|
||||||
|
} else {
|
||||||
|
let interaction_type = interaction.type;
|
||||||
|
let interaction_count = interaction.correct_responses._count;
|
||||||
|
if(interaction_type === 'choice') {
|
||||||
|
for(let i = 0; i < interaction_count && this.lastErrorCode === 0; i++) {
|
||||||
|
const response = interaction.correct_responses.childArray[i];
|
||||||
|
if(response.pattern === value) {
|
||||||
|
this.throwSCORMError(scorm2004_error_codes.GENERAL_SET_FAILURE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const response_type = correct_responses[interaction_type];
|
||||||
|
let nodes = [];
|
||||||
|
if(response_type.delimiter !== '') {
|
||||||
|
nodes = value.split(response_type.delimiter);
|
||||||
|
} else {
|
||||||
|
nodes[0] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(nodes.length > 0 && nodes.length <= response_type.max) {
|
||||||
|
this.#checkCorrectResponseValue(interaction_type, nodes, value);
|
||||||
|
} else if (nodes.length > response_type.max) {
|
||||||
|
this.throwSCORMError(scorm2004_error_codes.GENERAL_SET_FAILURE, "Data Model Element Pattern Too Long");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(this.lastErrorCode === 0) {
|
||||||
|
newChild = new CMIInteractionsCorrectResponsesObject(this);
|
||||||
|
}
|
||||||
|
} else if (this.stringContains(CMIElement, ".objectives")) {
|
||||||
|
newChild = new CMIInteractionsObjectivesObject(this);
|
||||||
|
} else if (this.stringContains(CMIElement, "cmi.interactions")) {
|
||||||
|
newChild = new CMIInteractionsObject(this);
|
||||||
|
} else if (this.stringContains(CMIElement, "cmi.comments_from_learner")) {
|
||||||
|
newChild = new CMICommentsFromLearnerObject(this);
|
||||||
|
} else if (this.stringContains(CMIElement, "cmi.comments_from_lms")) {
|
||||||
|
newChild = new CMICommentsFromLMSObject(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
return newChild;
|
||||||
|
}
|
||||||
|
|
||||||
|
validateCorrectResponse(CMIElement, value) {
|
||||||
|
const parts = CMIElement.split('.');
|
||||||
|
const index = Number(parts[2]);
|
||||||
|
const pattern_index = Number(parts[4]);
|
||||||
|
let interaction = this.cmi.interactions.childArray[index];
|
||||||
|
|
||||||
|
let interaction_type = interaction.type;
|
||||||
|
let interaction_count = interaction.correct_responses._count;
|
||||||
|
if(interaction_type === 'choice') {
|
||||||
|
for(let i = 0; i < interaction_count && this.lastErrorCode === 0; i++) {
|
||||||
|
const response = interaction.correct_responses.childArray[i];
|
||||||
|
if(response.pattern === value) {
|
||||||
|
this.throwSCORMError(scorm2004_error_codes.GENERAL_SET_FAILURE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const response_type = scorm2004_constants.correct_responses[interaction_type];
|
||||||
|
if(typeof response_type.limit !== 'undefined' || interaction_count < response_type.limit) {
|
||||||
|
let nodes = [];
|
||||||
|
if (response_type.delimiter !== '') {
|
||||||
|
nodes = value.split(response_type.delimiter);
|
||||||
|
} else {
|
||||||
|
nodes[0] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(nodes.length > 0 && nodes.length <= response_type.max) {
|
||||||
|
this.#checkCorrectResponseValue(interaction_type, nodes, value);
|
||||||
|
} else if (nodes.length > response_type.max) {
|
||||||
|
this.throwSCORMError(scorm2004_error_codes.GENERAL_SET_FAILURE, "Data Model Element Pattern Too Long");
|
||||||
|
}
|
||||||
|
|
||||||
|
if(this.lastErrorCode === 0
|
||||||
|
&& (!response_type.duplicate || !this.#checkDuplicatedPattern(interaction.correct_responses, pattern_index, value))
|
||||||
|
|| (this.lastErrorCode === 0 && value === '')) {
|
||||||
|
// do nothing, we want the inverse
|
||||||
|
} else {
|
||||||
|
if(this.lastErrorCode === 0) {
|
||||||
|
this.throwSCORMError(scorm2004_error_codes.GENERAL_SET_FAILURE, "Data Model Element Pattern Already Exists");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.throwSCORMError(scorm2004_error_codes.GENERAL_SET_FAILURE, "Data Model Element Collection Limit Reached");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a value from the CMI Object
|
||||||
|
*
|
||||||
|
* @param CMIElement
|
||||||
|
* @returns {*}
|
||||||
|
*/
|
||||||
|
getCMIValue(CMIElement) {
|
||||||
|
return this._commonGetCMIValue("GetValue", true, CMIElement);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the message that corresponds to errorNumber.
|
||||||
|
*/
|
||||||
|
getLmsErrorMessageDetails(errorNumber, detail) {
|
||||||
|
let basicMessage = "";
|
||||||
|
let detailMessage = "";
|
||||||
|
|
||||||
|
// Set error number to string since inconsistent from modules if string or number
|
||||||
|
errorNumber = String(errorNumber);
|
||||||
|
if(constants.error_descriptions[errorNumber]) {
|
||||||
|
basicMessage = constants.error_descriptions[errorNumber].basicMessage;
|
||||||
|
detailMessage = constants.error_descriptions[errorNumber].detailMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
return detail ? detailMessage : basicMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
#checkDuplicatedPattern(correct_response, current_index, value) {
|
||||||
|
let found = false;
|
||||||
|
let count = correct_response._count;
|
||||||
|
for(let i = 0; i < count && !found; i++) {
|
||||||
|
if(i !== current_index && correct_response.childArray[i] === value) {
|
||||||
|
found = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return found;
|
||||||
|
}
|
||||||
|
|
||||||
|
#checkCorrectResponseValue(interaction_type, nodes, value) {
|
||||||
|
const response = correct_responses[interaction_type];
|
||||||
|
const formatRegex = new RegExp(response.format);
|
||||||
|
for(let i = 0; i < nodes.length && this.lastErrorCode === 0; i++) {
|
||||||
|
if(interaction_type.match('^(fill-in|long-fill-in|matching|performance|sequencing)$')) {
|
||||||
|
nodes[i] = #removeCorrectResponsePrefixes(nodes[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(response.delimiter2 !== undefined) {
|
||||||
|
let values = nodes[i].split(response.delimiter2);
|
||||||
|
if(values.length === 2) {
|
||||||
|
let matches = values[0].match(formatRegex);
|
||||||
|
if(!matches) {
|
||||||
|
this.throwSCORMError(scorm2004_error_codes.TYPE_MISMATCH);
|
||||||
|
} else {
|
||||||
|
if(!values[1].match(new RegExp(response.format2))) {
|
||||||
|
this.throwSCORMError(scorm2004_error_codes.TYPE_MISMATCH);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.throwSCORMError(scorm2004_error_codes.TYPE_MISMATCH);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let matches = nodes[i].match(formatRegex);
|
||||||
|
if((!matches && value !== '') || (!matches && interaction_type === 'true-false')) {
|
||||||
|
this.throwSCORMError(scorm2004_error_codes.TYPE_MISMATCH);
|
||||||
|
} else {
|
||||||
|
if (interaction_type === 'numeric' && nodes.length > 1) {
|
||||||
|
if(Number(nodes[0]) > Number(nodes[1])) {
|
||||||
|
this.throwSCORMError(scorm2004_error_codes.TYPE_MISMATCH);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if(nodes[i] !== '' && response.unique) {
|
||||||
|
for(let j = 0; j < i && this.lastErrorCode === 0; j++) {
|
||||||
|
if(nodes[i] === nodes[j]) {
|
||||||
|
this.throwSCORMError(scorm2004_error_codes.TYPE_MISMATCH);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#removeCorrectResponsePrefixes(node) {
|
||||||
|
let seenOrder = false;
|
||||||
|
let seenCase = false;
|
||||||
|
let seenLang = false;
|
||||||
|
|
||||||
|
let prefixRegex = '^(\{(lang|case_matters|order_matters)=([^\}]+)\})';
|
||||||
|
let matches = node.match(prefixRegex);
|
||||||
|
while(matches) {
|
||||||
|
switch (matches[2]) {
|
||||||
|
case 'lang':
|
||||||
|
let langMatches = node.match(scorm2004_regex.CMILangcr);
|
||||||
|
if(langMatches) {
|
||||||
|
let lang = langMatches[3];
|
||||||
|
if (lang !== undefined && lang.length > 0) {
|
||||||
|
if(valid_languages[lang.toLowerCase()] === undefined) {
|
||||||
|
this.throwSCORMError(scorm2004_error_codes.TYPE_MISMATCH);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
seenLang = true;
|
||||||
|
break;
|
||||||
|
case 'case_matters':
|
||||||
|
if(!seenLang && !seenOrder && !seenCase) {
|
||||||
|
if(matches[3] !== 'true' && matches[3] !== 'false') {
|
||||||
|
this.throwSCORMError(scorm2004_error_codes.TYPE_MISMATCH);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
seenCase = true;
|
||||||
|
break;
|
||||||
|
case 'order_matters':
|
||||||
|
if(!seenCase && !seenLang && !seenOrder) {
|
||||||
|
if(matches[3] !== 'true' && matches[3] !== 'false') {
|
||||||
|
this.throwSCORMError(scorm2004_error_codes.TYPE_MISMATCH);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
seenOrder = true;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
node = node.substr(matches[1].length);
|
||||||
|
matches = node.match(prefixRegex);
|
||||||
|
}
|
||||||
|
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replace the whole API with another
|
||||||
|
*/
|
||||||
|
replaceWithAnotherScormAPI(newAPI) {
|
||||||
|
// Data Model
|
||||||
|
this.cmi = newAPI.cmi;
|
||||||
|
this.adl = newAPI.adl;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds the current session time to the existing total time.
|
||||||
|
*/
|
||||||
|
getCurrentTotalTime() {
|
||||||
|
const totalTime = this.cmi.total_time;
|
||||||
|
const sessionTime = this.cmi.session_time;
|
||||||
|
|
||||||
|
const durationRegex = scorm2004_regex.CMITimespan;
|
||||||
|
const totalSeconds = Utilities.getDurationAsSeconds(totalTime, durationRegex);
|
||||||
|
const sessionSeconds = Utilities.getDurationAsSeconds(sessionTime, durationRegex);
|
||||||
|
|
||||||
|
return Utilities.getSecondsAsISODuration(totalSeconds + sessionSeconds);
|
||||||
|
}
|
||||||
|
}
|
||||||
120
src/cmi/aicc_cmi.js
Normal file
120
src/cmi/aicc_cmi.js
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) Noverant, Inc - All Rights Reserved Unauthorized copying of this file, via any
|
||||||
|
* medium is strictly prohibited Proprietary and confidential
|
||||||
|
*/
|
||||||
|
|
||||||
|
import * as Scorm12CMI from './scorm12_cmi';
|
||||||
|
import {BaseCMI, CMIArray, CMIScore} from "./common";
|
||||||
|
import {aicc_constants} from "../constants";
|
||||||
|
import {aicc_regex} from "../regex";
|
||||||
|
|
||||||
|
const constants = aicc_constants;
|
||||||
|
const regex = aicc_regex;
|
||||||
|
|
||||||
|
export class CMI extends Scorm12CMI.CMI {
|
||||||
|
constructor(API) {
|
||||||
|
super(API, constants.cmi_children, new AICCCMIStudentData(API));
|
||||||
|
|
||||||
|
this.evaluation = new CMIEvaluation(API);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class CMIEvaluation extends BaseCMI {
|
||||||
|
constructor(API) {
|
||||||
|
super(API);
|
||||||
|
}
|
||||||
|
|
||||||
|
comments = new class extends CMIArray {
|
||||||
|
constructor(API) {
|
||||||
|
super(API, constants.comments_children, 402);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
class AICCCMIStudentData extends Scorm12CMI.CMIStudentData {
|
||||||
|
constructor(API) {
|
||||||
|
super(API, constants.student_data_children);
|
||||||
|
}
|
||||||
|
|
||||||
|
#tries_during_lesson = "";
|
||||||
|
|
||||||
|
get tries_during_lesson() { return this.#tries_during_lesson; }
|
||||||
|
set tries_during_lesson(tries_during_lesson) { this.API.isNotInitialized() ? this.#tries_during_lesson = tries_during_lesson : this.throwReadOnlyError() }
|
||||||
|
|
||||||
|
tries = new class extends CMIArray {
|
||||||
|
constructor(API) {
|
||||||
|
super(API, aicc_constants.tries_children);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export class CMITriesObject extends BaseCMI {
|
||||||
|
constructor(API) {
|
||||||
|
super(API);
|
||||||
|
}
|
||||||
|
|
||||||
|
#status = "";
|
||||||
|
#time = "";
|
||||||
|
|
||||||
|
get status() { return this.#status; }
|
||||||
|
set status(status) {
|
||||||
|
if(this.API.checkValidFormat(status, regex.CMIStatus2)) {
|
||||||
|
this.#status = status;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get time() { return this.#time; }
|
||||||
|
set time(time) {
|
||||||
|
if(this.API.checkValidFormat(time, regex.CMITime)) {
|
||||||
|
this.#time = time;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
score = new CMIScore(API);
|
||||||
|
}
|
||||||
|
|
||||||
|
export class CMIEvaluationCommentsObject extends BaseCMI {
|
||||||
|
constructor(API) {
|
||||||
|
super(API);
|
||||||
|
}
|
||||||
|
|
||||||
|
#content = "";
|
||||||
|
#location = "";
|
||||||
|
#time = "";
|
||||||
|
|
||||||
|
get content() { return this.#content; }
|
||||||
|
set content(content) {
|
||||||
|
if(this.API.checkValidFormat(content, regex.CMIString256)) {
|
||||||
|
this.#content = content;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get location() { return this.#location; }
|
||||||
|
set location(location) {
|
||||||
|
if(this.API.checkValidFormat(location, regex.CMIString256)) {
|
||||||
|
this.#location = location;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get time() { return this.#time; }
|
||||||
|
set time(time) {
|
||||||
|
if(this.API.checkValidFormat(time, regex.CMITime)) {
|
||||||
|
this.#time = time;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class NAV extends BaseCMI {
|
||||||
|
constructor(API) {
|
||||||
|
super(API);
|
||||||
|
}
|
||||||
|
|
||||||
|
#event = "";
|
||||||
|
|
||||||
|
get event() { return (!this.jsonString) ? this.API.throwSCORMError(404) : this.#event; }
|
||||||
|
set event(event) {
|
||||||
|
if(this.API.checkValidFormat(event, regex.NAVEvent)) {
|
||||||
|
this.#event = event;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
95
src/cmi/common.js
Normal file
95
src/cmi/common.js
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) Noverant, Inc - All Rights Reserved Unauthorized copying of this file, via any
|
||||||
|
* medium is strictly prohibited Proprietary and confidential
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {scorm12_constants, scorm12_error_codes} from "../constants";
|
||||||
|
|
||||||
|
export class BaseCMI {
|
||||||
|
jsonString = false;
|
||||||
|
API;
|
||||||
|
|
||||||
|
constructor(API: any) {
|
||||||
|
this.API = API;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class CMIScore extends BaseCMI {
|
||||||
|
constructor(API, score_children?, score_range?, invalidErrorCode) {
|
||||||
|
super(API);
|
||||||
|
|
||||||
|
this.#_children = score_children? score_children : scorm12_constants.score_children;
|
||||||
|
this.#_score_range = score_range? score_range : false;
|
||||||
|
this.#_invalid_error_code = invalidErrorCode ? invalidErrorCode : scorm12_error_codes.INVALID_SET_VALUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
#_children;
|
||||||
|
#_score_range;
|
||||||
|
#_invalid_error_code;
|
||||||
|
#raw = "";
|
||||||
|
#min = "";
|
||||||
|
#max = "100";
|
||||||
|
|
||||||
|
get _children() { return this.#_children; }
|
||||||
|
set _children(_children) { this.API.throwSCORMError(this.#_invalid_error_code); }
|
||||||
|
|
||||||
|
get raw() { return this.#raw; }
|
||||||
|
set raw(raw) {
|
||||||
|
if(this.API.checkValidFormat(raw, scorm12_constants.CMIDecimal)
|
||||||
|
&& (!this.#_score_range || this.API.checkValidRange(raw, this.#_score_range))) {
|
||||||
|
this.#raw = raw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get min() { return this.#min; }
|
||||||
|
set min(min) {
|
||||||
|
if(this.API.checkValidFormat(min, scorm12_constants.CMIDecimal)
|
||||||
|
&& (!this.#_score_range || this.API.checkValidRange(min, this.#_score_range))) {
|
||||||
|
this.#min = min;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get max() { return this.#max; }
|
||||||
|
set max(max) {
|
||||||
|
if(this.API.checkValidFormat(max, scorm12_constants.CMIDecimal)
|
||||||
|
&& (!this.#_score_range || this.API.checkValidRange(max, this.#_score_range))) {
|
||||||
|
this.#max = max;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
toJSON = () => {
|
||||||
|
return {
|
||||||
|
'raw': this.raw,
|
||||||
|
'min': this.min,
|
||||||
|
'max': this.max
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class CMIArray extends BaseCMI {
|
||||||
|
constructor({API, children, errorCode}) {
|
||||||
|
super(API);
|
||||||
|
this.#_children = children;
|
||||||
|
this.#errorCode = errorCode;
|
||||||
|
this.childArray = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
#errorCode;
|
||||||
|
#_children;
|
||||||
|
|
||||||
|
get _children() { return this.#_children; }
|
||||||
|
set _children(_children) { this.API.throwSCORMError(this.#errorCode); }
|
||||||
|
|
||||||
|
get _count() { return this.childArray.length; }
|
||||||
|
set _count(_count) { this.API.throwSCORMError(this.#errorCode); }
|
||||||
|
|
||||||
|
toJSON = () => {
|
||||||
|
this.jsonString = true;
|
||||||
|
let result = {};
|
||||||
|
for(let i = 0; i < this.childArray.length; i++) {
|
||||||
|
result[i + ''] = this.childArray[i];
|
||||||
|
}
|
||||||
|
delete this.jsonString;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
467
src/cmi/scorm12_cmi.js
Normal file
467
src/cmi/scorm12_cmi.js
Normal file
@@ -0,0 +1,467 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) Noverant, Inc - All Rights Reserved Unauthorized copying of this file, via any
|
||||||
|
* medium is strictly prohibited Proprietary and confidential
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {BaseCMI, CMIArray, CMIScore} from './common';
|
||||||
|
import {scorm12_constants, scorm12_error_codes} from "../constants";
|
||||||
|
import {scorm12_regex} from "../regex";
|
||||||
|
|
||||||
|
const constants = scorm12_constants;
|
||||||
|
const regex = scorm12_regex;
|
||||||
|
|
||||||
|
function throwReadOnlyError(API) {
|
||||||
|
API.throwSCORMError(scorm12_error_codes.READ_ONLY_ELEMENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
function throwWriteOnlyError(API) {
|
||||||
|
API.throwSCORMError(scorm12_error_codes.WRITE_ONLY_ELEMENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
function throwInvalidValueError(API) {
|
||||||
|
API.throwSCORMError(scorm12_error_codes.INVALID_SET_VALUE);
|
||||||
|
}
|
||||||
|
|
||||||
|
export class CMI extends BaseCMI {
|
||||||
|
#_children = "";
|
||||||
|
#_version = "3.4";
|
||||||
|
#suspend_data = "";
|
||||||
|
#launch_data = "";
|
||||||
|
#comments = "";
|
||||||
|
#comments_from_lms = "";
|
||||||
|
|
||||||
|
student_data = null;
|
||||||
|
|
||||||
|
constructor(API, cmi_children, student_data) {
|
||||||
|
super(API);
|
||||||
|
|
||||||
|
this.#_children = cmi_children ? cmi_children : constants.cmi_children;
|
||||||
|
this.core = new CMICore(API);
|
||||||
|
this.objectives = new CMIObjectives(API);
|
||||||
|
this.student_data = student_data ? student_data : new CMIStudentData(API);
|
||||||
|
this.student_preference = new CMIStudentPreference(API);
|
||||||
|
this.interactions = new CMIInteractions(API);
|
||||||
|
}
|
||||||
|
|
||||||
|
toJSON = () => {
|
||||||
|
this.jsonString = true;
|
||||||
|
const result = {
|
||||||
|
'suspend_data': this.suspend_data,
|
||||||
|
'launch_data': this.launch_data,
|
||||||
|
'comments': this.comments,
|
||||||
|
'comments_from_lms': this.comments_from_lms,
|
||||||
|
'core': this.core,
|
||||||
|
'objectives': this.objectives,
|
||||||
|
'student_data': this.student_data,
|
||||||
|
'student_preference': this.student_preference,
|
||||||
|
'interactions': this.interactions
|
||||||
|
};
|
||||||
|
delete this.jsonString;
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
get _version() { return this.#_version; }
|
||||||
|
set _version(_version) { throwInvalidValueError(this.API); }
|
||||||
|
|
||||||
|
get _children() { return this.#_children; }
|
||||||
|
set _children(_children) { throwInvalidValueError(this.API); }
|
||||||
|
|
||||||
|
get suspend_data() { return this.#suspend_data; }
|
||||||
|
set suspend_data(suspend_data) {
|
||||||
|
if(this.API.checkValidFormat(suspend_data, regex.CMIString4096)) {
|
||||||
|
this.#suspend_data = suspend_data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get launch_data() { return this.#launch_data; }
|
||||||
|
set launch_data(launch_data) { this.API.isNotInitialized() ? this.#launch_data = launch_data : throwReadOnlyError(this.API); }
|
||||||
|
|
||||||
|
get comments() { return this.#comments; }
|
||||||
|
set comments(comments) {
|
||||||
|
if(this.API.checkValidFormat(comments, regex.CMIString4096)) {
|
||||||
|
this.#comments = comments;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get comments_from_lms() { return this.#comments_from_lms; }
|
||||||
|
set comments_from_lms(comments_from_lms) { this.API.isNotInitialized() ? this.#comments_from_lms = comments_from_lms : throwReadOnlyError(this.API); }
|
||||||
|
}
|
||||||
|
|
||||||
|
class CMICore extends BaseCMI {
|
||||||
|
constructor(API) {
|
||||||
|
super(API);
|
||||||
|
|
||||||
|
this.score = new CMIScore(API, constants.score_children, regex.score_range);
|
||||||
|
}
|
||||||
|
|
||||||
|
#_children = constants.core_children;
|
||||||
|
#student_id = "";
|
||||||
|
#student_name = "";
|
||||||
|
#lesson_location = "";
|
||||||
|
#credit = "";
|
||||||
|
#lesson_status = "";
|
||||||
|
#entry = "";
|
||||||
|
#total_time = "";
|
||||||
|
#lesson_mode = "normal";
|
||||||
|
#exit = "";
|
||||||
|
#session_time = "00:00:00";
|
||||||
|
|
||||||
|
get _children() { return this.#_children; }
|
||||||
|
set _children(_children) { throwInvalidValueError(this.API); }
|
||||||
|
|
||||||
|
get student_id() { return this.#student_id; }
|
||||||
|
set student_id(student_id) { this.API.isNotInitialized() ? this.#student_id = student_id : throwReadOnlyError(this.API); }
|
||||||
|
|
||||||
|
get student_name() { return this.#student_name; }
|
||||||
|
set student_name(student_name) { this.API.isNotInitialized() ? this.#student_name = student_name : throwReadOnlyError(this.API); }
|
||||||
|
|
||||||
|
get lesson_location() { return this.#lesson_location; }
|
||||||
|
set lesson_location(lesson_location) {
|
||||||
|
if(this.API.checkValidFormat(lesson_location, regex.CMIString256)) {
|
||||||
|
this.#lesson_location = lesson_location;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get credit() { return this.#credit; }
|
||||||
|
set credit(credit) { this.API.isNotInitialized() ? this.#credit = credit : throwReadOnlyError(this.API); }
|
||||||
|
|
||||||
|
get lesson_status() { return this.#lesson_status; }
|
||||||
|
set lesson_status(lesson_status) {
|
||||||
|
if(this.API.checkValidFormat(lesson_status, regex.CMIStatus)) {
|
||||||
|
this.#lesson_status = lesson_status;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get entry() { return this.#entry; }
|
||||||
|
set entry(entry) { this.API.isNotInitialized() ? this.#entry = entry : throwReadOnlyError(this.API); }
|
||||||
|
|
||||||
|
get total_time() { return this.#total_time; }
|
||||||
|
set total_time(total_time) { this.API.isNotInitialized() ? this.#total_time = total_time : throwReadOnlyError(this.API); }
|
||||||
|
|
||||||
|
get lesson_mode() { return this.#lesson_mode; }
|
||||||
|
set lesson_mode(lesson_mode) { this.API.isNotInitialized() ? this.#lesson_mode = lesson_mode : throwReadOnlyError(this.API); }
|
||||||
|
|
||||||
|
get exit() { return (!this.jsonString) ? throwWriteOnlyError(this.API) : this.#exit; }
|
||||||
|
set exit(exit) {
|
||||||
|
if(this.API.checkValidFormat(exit, regex.CMIExit)) {
|
||||||
|
this.#exit = exit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get session_time() { return (!this.jsonString) ? throwWriteOnlyError(this.API) : this.#session_time; }
|
||||||
|
set session_time(session_time) {
|
||||||
|
if(this.API.checkValidFormat(session_time, regex.CMITimespan)) {
|
||||||
|
this.#session_time = session_time;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
toJSON = () => {
|
||||||
|
this.jsonString = true;
|
||||||
|
const result = {
|
||||||
|
'student_id': this.student_id,
|
||||||
|
'student_name': this.student_name,
|
||||||
|
'lesson_location': this.lesson_location,
|
||||||
|
'credit': this.credit,
|
||||||
|
'lesson_status': this.lesson_status,
|
||||||
|
'entry': this.entry,
|
||||||
|
'total_time': this.total_time,
|
||||||
|
'lesson_mode': this.lesson_mode,
|
||||||
|
'exit': this.exit,
|
||||||
|
'session_time': this.session_time,
|
||||||
|
'score': this.score
|
||||||
|
};
|
||||||
|
delete this.jsonString;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class CMIObjectives extends CMIArray {
|
||||||
|
constructor(API) {
|
||||||
|
super({
|
||||||
|
API: API,
|
||||||
|
children: constants.objectives_children,
|
||||||
|
errorCode: scorm12_error_codes.INVALID_SET_VALUE
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export class CMIStudentData extends BaseCMI {
|
||||||
|
#_children;
|
||||||
|
#mastery_score = "";
|
||||||
|
#max_time_allowed = "";
|
||||||
|
#time_limit_action = "";
|
||||||
|
|
||||||
|
constructor(API, student_data_children) {
|
||||||
|
super(API);
|
||||||
|
|
||||||
|
this.#_children = student_data_children? student_data_children : constants.student_data_children;
|
||||||
|
}
|
||||||
|
|
||||||
|
get _children() { return this.#_children; }
|
||||||
|
set _children(_children) { throwInvalidValueError(this.API); }
|
||||||
|
|
||||||
|
get mastery_score() { return this.#mastery_score; }
|
||||||
|
set mastery_score(mastery_score) { this.API.isNotInitialized() ? this.#mastery_score = mastery_score : throwReadOnlyError(this.API); }
|
||||||
|
|
||||||
|
get max_time_allowed() { return this.#max_time_allowed; }
|
||||||
|
set max_time_allowed(max_time_allowed) { this.API.isNotInitialized() ? this.#max_time_allowed = max_time_allowed : throwReadOnlyError(this.API); }
|
||||||
|
|
||||||
|
get time_limit_action() { return this.#time_limit_action; }
|
||||||
|
set time_limit_action(time_limit_action) { this.API.isNotInitialized() ? this.#time_limit_action = time_limit_action : throwReadOnlyError(this.API); }
|
||||||
|
|
||||||
|
toJSON = () => {
|
||||||
|
this.jsonString = true;
|
||||||
|
const result = {
|
||||||
|
'mastery_score': this.mastery_score,
|
||||||
|
'max_time_allowed': this.max_time_allowed,
|
||||||
|
'time_limit_action': this.time_limit_action
|
||||||
|
};
|
||||||
|
delete this.jsonString;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class CMIStudentPreference extends BaseCMI {
|
||||||
|
constructor(API) {
|
||||||
|
super(API);
|
||||||
|
}
|
||||||
|
|
||||||
|
#_children = constants.student_preference_children;
|
||||||
|
#audio = "";
|
||||||
|
#language = "";
|
||||||
|
#speed = "";
|
||||||
|
#text = "";
|
||||||
|
|
||||||
|
get _children() { return this.#_children; }
|
||||||
|
set _children(_children) { throwInvalidValueError(this.API); }
|
||||||
|
|
||||||
|
get audio() { return this.#audio; }
|
||||||
|
set audio(audio) {
|
||||||
|
if(this.API.checkValidFormat(audio, regex.CMISInteger)
|
||||||
|
&& this.API.checkValidRange(audio, regex.audio_range)) {
|
||||||
|
this.#audio = audio;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get language() { return this.#language; }
|
||||||
|
set language(language) {
|
||||||
|
if(this.API.checkValidFormat(language, regex.CMIString256)) {
|
||||||
|
this.#language = language;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get speed() { return this.#speed; }
|
||||||
|
set speed(speed) {
|
||||||
|
if(this.API.checkValidFormat(speed, regex.CMISInteger)
|
||||||
|
&& this.API.checkValidRange(speed, regex.speed_range)) {
|
||||||
|
this.#speed = speed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get text() { return this.#text; }
|
||||||
|
set text(text) {
|
||||||
|
if(this.API.checkValidFormat(text, regex.CMISInteger)
|
||||||
|
&& this.API.checkValidRange(text, regex.text_range)) {
|
||||||
|
this.#text = text;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
toJSON = () => {
|
||||||
|
this.jsonString = true;
|
||||||
|
const result = {
|
||||||
|
'audio': this.audio,
|
||||||
|
'language': this.language,
|
||||||
|
'speed': this.speed,
|
||||||
|
'text': this.text
|
||||||
|
};
|
||||||
|
delete this.jsonString;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class CMIInteractions extends CMIArray {
|
||||||
|
constructor(API) {
|
||||||
|
super({
|
||||||
|
API: API,
|
||||||
|
children: constants.interactions_children,
|
||||||
|
errorCode: scorm12_error_codes.INVALID_SET_VALUE
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class CMIInteractionsObject extends BaseCMI {
|
||||||
|
constructor(API) {
|
||||||
|
super(API);
|
||||||
|
|
||||||
|
this.objectives = new CMIArray({
|
||||||
|
API: API,
|
||||||
|
errorCode: 402,
|
||||||
|
children: constants.objectives_children
|
||||||
|
});
|
||||||
|
this.correct_responses = new CMIArray({
|
||||||
|
API: API,
|
||||||
|
errorCode: 402,
|
||||||
|
children: constants.correct_responses_children
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#id: "";
|
||||||
|
#time: "";
|
||||||
|
#type: "";
|
||||||
|
#weighting: "";
|
||||||
|
#student_response: "";
|
||||||
|
#result: "";
|
||||||
|
#latency: "";
|
||||||
|
|
||||||
|
get id() { return (!this.jsonString) ? throwWriteOnlyError(this.API) : this.#id; }
|
||||||
|
set id(id) {
|
||||||
|
if(this.API.checkValidFormat(id, regex.CMIIdentifier)) {
|
||||||
|
this.#id = id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get time() { return (!this.jsonString) ? throwWriteOnlyError(this.API) : this.#time; }
|
||||||
|
set time(time) {
|
||||||
|
if(this.API.checkValidFormat(time, regex.CMITime)) {
|
||||||
|
this.#time = time;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get type() { return (!this.jsonString) ? throwWriteOnlyError(this.API) : this.#type; }
|
||||||
|
set type(type) {
|
||||||
|
if(this.API.checkValidFormat(type, regex.CMIType)) {
|
||||||
|
this.#type = type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get weighting() { return (!this.jsonString) ? throwWriteOnlyError(this.API) : this.#weighting; }
|
||||||
|
set weighting(weighting) {
|
||||||
|
if(this.API.checkValidFormat(weighting, regex.CMIDecimal)
|
||||||
|
&& this.API.checkValidRange(weighting, regex.weighting_range)) {
|
||||||
|
this.#weighting = weighting;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get student_response() { return (!this.jsonString) ? throwWriteOnlyError(this.API) : this.#student_response; }
|
||||||
|
set student_response(student_response) {
|
||||||
|
if(this.API.checkValidFormat(student_response, regex.CMIFeedback)) {
|
||||||
|
this.#student_response = student_response;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get result() { return (!this.jsonString) ? throwWriteOnlyError(this.API) : this.#result; }
|
||||||
|
set result(result) {
|
||||||
|
if(this.API.checkValidFormat(result, regex.CMIResult)) {
|
||||||
|
this.#result = result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get latency() { return (!this.jsonString) ? throwWriteOnlyError(this.API) : this.#latency; }
|
||||||
|
set latency(latency) {
|
||||||
|
if(this.API.checkValidFormat(latency, regex.CMITimespan)) {
|
||||||
|
this.#latency = latency;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
toJSON = () => {
|
||||||
|
this.jsonString = true;
|
||||||
|
const result = {
|
||||||
|
'id': this.id,
|
||||||
|
'time': this.time,
|
||||||
|
'type': this.type,
|
||||||
|
'weighting': this.weighting,
|
||||||
|
'student_response': this.student_response,
|
||||||
|
'result': this.result,
|
||||||
|
'latency': this.latency,
|
||||||
|
'objectives': this.objectives,
|
||||||
|
'correct_responses': this.correct_responses
|
||||||
|
};
|
||||||
|
delete this.jsonString;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class CMIObjectivesObject extends BaseCMI {
|
||||||
|
constructor(API) {
|
||||||
|
super(API);
|
||||||
|
|
||||||
|
this.score = new CMIScore(API);
|
||||||
|
}
|
||||||
|
|
||||||
|
#id: "";
|
||||||
|
#status: "";
|
||||||
|
|
||||||
|
get id() { return this.#id; }
|
||||||
|
set id(id) {
|
||||||
|
if(this.API.checkValidFormat(id, regex.CMIIdentifier)) {
|
||||||
|
this.#id = id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get status() { return this.#status; }
|
||||||
|
set status(status) {
|
||||||
|
if(this.API.checkValidFormat(status, regex.CMIStatus2)) {
|
||||||
|
this.#status = status;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
toJSON = () => {
|
||||||
|
this.jsonString = true;
|
||||||
|
const result = {
|
||||||
|
'id': this.id,
|
||||||
|
'status': this.status,
|
||||||
|
'score': this.score
|
||||||
|
};
|
||||||
|
delete this.jsonString;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class CMIInteractionsObjectivesObject extends BaseCMI {
|
||||||
|
constructor(API) {
|
||||||
|
super(API);
|
||||||
|
}
|
||||||
|
|
||||||
|
#id: "";
|
||||||
|
|
||||||
|
get id() { return this.#id; }
|
||||||
|
set id(id) {
|
||||||
|
if(this.API.checkValidFormat(id, regex.CMIIdentifier)) {
|
||||||
|
this.#id = id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
toJSON = () => {
|
||||||
|
this.jsonString = true;
|
||||||
|
const result = {
|
||||||
|
'id': this.id,
|
||||||
|
};
|
||||||
|
delete this.jsonString;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class CMIInteractionsCorrectResponsesObject extends BaseCMI {
|
||||||
|
constructor(API) {
|
||||||
|
super(API);
|
||||||
|
}
|
||||||
|
|
||||||
|
#pattern: "";
|
||||||
|
|
||||||
|
get pattern() { return this.#pattern; }
|
||||||
|
set pattern(pattern) {
|
||||||
|
if(this.API.checkValidFormat(pattern, regex.CMIFeedback)) {
|
||||||
|
this.#pattern = pattern;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
toJSON = () => {
|
||||||
|
this.jsonString = true;
|
||||||
|
const result = {
|
||||||
|
'pattern': this.pattern,
|
||||||
|
};
|
||||||
|
delete this.jsonString;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
522
src/cmi/scorm2004_cmi.js
Normal file
522
src/cmi/scorm2004_cmi.js
Normal file
@@ -0,0 +1,522 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) Noverant, Inc - All Rights Reserved Unauthorized copying of this file, via any
|
||||||
|
* medium is strictly prohibited Proprietary and confidential
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {BaseCMI, CMIScore, CMIArray} from './common';
|
||||||
|
import {scorm2004_constants, learner_responses} from "../constants";
|
||||||
|
import {scorm2004_regex} from "../regex";
|
||||||
|
|
||||||
|
const constants = scorm2004_constants;
|
||||||
|
const regex = scorm2004_regex;
|
||||||
|
|
||||||
|
export class CMI extends BaseCMI {
|
||||||
|
constructor(API) {
|
||||||
|
super(API);
|
||||||
|
|
||||||
|
this.learner_preference = new CMILearnerPreference(API);
|
||||||
|
}
|
||||||
|
|
||||||
|
#_version: "1.0";
|
||||||
|
#_children = constants.cmi_children;
|
||||||
|
#completion_status: "unknown";
|
||||||
|
#completion_threshold: "";
|
||||||
|
#credit: "credit";
|
||||||
|
#entry: "";
|
||||||
|
#exit: "";
|
||||||
|
#launch_data: "";
|
||||||
|
#learner_id: "";
|
||||||
|
#learner_name: "";
|
||||||
|
#location: "";
|
||||||
|
#max_time_allowed: "";
|
||||||
|
#mode: "normal";
|
||||||
|
#progress_measure: "";
|
||||||
|
#scaled_passing_score: "";
|
||||||
|
#session_time: "PT0H0M0S";
|
||||||
|
#success_status: "unknown";
|
||||||
|
#suspend_data: "";
|
||||||
|
#time_limit_action: "continue,no message";
|
||||||
|
#total_time: "0";
|
||||||
|
|
||||||
|
get _version() { return this.#_version; }
|
||||||
|
set _version(_version) { this.throwReadOnlyError(); }
|
||||||
|
|
||||||
|
get _children() { return this.#_children; }
|
||||||
|
set _children(_children) { this.throwReadOnlyError(); }
|
||||||
|
|
||||||
|
get completion_status() { return this.#completion_status; }
|
||||||
|
set completion_status(completion_status) {
|
||||||
|
if(this.API.checkValidFormat(completion_status, regex.CMICStatus)) {
|
||||||
|
this.#completion_status = completion_status;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get completion_threshold() { return this.#completion_threshold; }
|
||||||
|
set completion_threshold(completion_threshold) { this.API.isNotInitialized() ? this.#completion_threshold = completion_threshold : this.throwReadOnlyError(); }
|
||||||
|
|
||||||
|
get credit() { return this.#credit; }
|
||||||
|
set credit(credit) { this.API.isNotInitialized() ? this.#credit = credit : this.throwReadOnlyError(); }
|
||||||
|
|
||||||
|
get entry() { return this.#entry; }
|
||||||
|
set entry(entry) { this.API.isNotInitialized() ? this.#entry = entry : this.throwReadOnlyError(); }
|
||||||
|
|
||||||
|
get exit() { return (!this.jsonString) ? this.throwWriteOnlyError() : this.#exit; }
|
||||||
|
set exit(exit) {
|
||||||
|
if(this.API.checkValidFormat(exit, regex.CMIExit)) {
|
||||||
|
this.#exit = exit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get launch_data() { return this.#launch_data; }
|
||||||
|
set launch_data(launch_data) { this.API.isNotInitialized() ? this.#launch_data = launch_data : this.throwReadOnlyError(); }
|
||||||
|
|
||||||
|
get learner_id() { return this.#learner_id; }
|
||||||
|
set learner_id(learner_id) { this.API.isNotInitialized() ? this.#learner_id = learner_id : this.throwReadOnlyError(); }
|
||||||
|
|
||||||
|
get learner_name() { return this.#learner_name; }
|
||||||
|
set learner_name(learner_name) { this.API.isNotInitialized() ? this.#learner_name = learner_name : this.throwReadOnlyError(); }
|
||||||
|
|
||||||
|
get location() { return this.#location; }
|
||||||
|
set location(location) {
|
||||||
|
if(this.API.checkValidFormat(location, regex.CMIString1000)) {
|
||||||
|
this.#location = location;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get max_time_allowed() { return this.#max_time_allowed; }
|
||||||
|
set max_time_allowed(max_time_allowed) { this.API.isNotInitialized() ? this.#max_time_allowed = max_time_allowed : this.throwReadOnlyError(); }
|
||||||
|
|
||||||
|
get mode() { return this.#mode; }
|
||||||
|
set mode(mode) { this.API.isNotInitialized() ? this.#mode = mode : this.throwReadOnlyError(); }
|
||||||
|
|
||||||
|
get progress_measure() { return this.#progress_measure; }
|
||||||
|
set progress_measure(progress_measure) {
|
||||||
|
if(this.API.checkValidFormat(progress_measure, regex.CMIDecimal)
|
||||||
|
&& this.API.checkValidRange(progress_measure, regex.progress_range)) {
|
||||||
|
this.#progress_measure = progress_measure;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get scaled_passing_score() { return this.#scaled_passing_score; }
|
||||||
|
set scaled_passing_score(scaled_passing_score) { this.API.isNotInitialized() ? this.#scaled_passing_score = scaled_passing_score : this.throwReadOnlyError(); }
|
||||||
|
|
||||||
|
get session_time() { return (!this.jsonString) ? this.API.throwSCORMError(405) : this.#session_time; }
|
||||||
|
set session_time(session_time) {
|
||||||
|
if(this.API.checkValidFormat(session_time, regex.CMITimespan)) {
|
||||||
|
this.#session_time = session_time;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get success_status() { return this.#success_status; }
|
||||||
|
set success_status(success_status) {
|
||||||
|
if(this.API.checkValidFormat(success_status, regex.CMISStatus)) {
|
||||||
|
this.#success_status = success_status;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get suspend_data() { return this.#suspend_data; }
|
||||||
|
set suspend_data(suspend_data) {
|
||||||
|
if(this.API.checkValidFormat(suspend_data, regex.CMIString64000)) {
|
||||||
|
this.#suspend_data = suspend_data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get time_limit_action() { return this.#time_limit_action; }
|
||||||
|
set time_limit_action(time_limit_action) { this.API.isNotInitialized() ? this.#time_limit_action = time_limit_action : this.throwReadOnlyError(); }
|
||||||
|
|
||||||
|
get total_time() { return this.#total_time; }
|
||||||
|
set total_time(total_time) { this.API.isNotInitialized() ? this.#total_time = total_time : this.throwReadOnlyError(); }
|
||||||
|
|
||||||
|
comments_from_learner = new class extends CMIArray {
|
||||||
|
constructor(API) {
|
||||||
|
super({
|
||||||
|
API: API,
|
||||||
|
children: constants.comments_children,
|
||||||
|
errorCode: 404
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
comments_from_lms = new class extends CMIArray {
|
||||||
|
constructor(API) {
|
||||||
|
super({
|
||||||
|
API: API,
|
||||||
|
children: constants.comments_children,
|
||||||
|
errorCode: 404
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
interactions = new class extends CMIArray {
|
||||||
|
constructor(API) {
|
||||||
|
super({
|
||||||
|
API: API,
|
||||||
|
children: constants.interactions_children,
|
||||||
|
errorCode: 404
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
objectives = new class extends CMIArray {
|
||||||
|
constructor(API) {
|
||||||
|
super({
|
||||||
|
API: API,
|
||||||
|
children: constants.objectives_children,
|
||||||
|
errorCode: 404
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
class CMILearnerPreference extends BaseCMI {
|
||||||
|
constructor(API) {
|
||||||
|
super(API);
|
||||||
|
}
|
||||||
|
|
||||||
|
#_children: constants.student_preference_children;
|
||||||
|
#audio_level: "1";
|
||||||
|
#language: "";
|
||||||
|
#delivery_speed: "1";
|
||||||
|
#audio_captioning: "0";
|
||||||
|
|
||||||
|
get _children() { return this.#_children; }
|
||||||
|
set _children(_children) { this.throwReadOnlyError(); }
|
||||||
|
|
||||||
|
get audio_level() { return this.#audio_level; }
|
||||||
|
set audio_level(audio_level) {
|
||||||
|
if(this.API.checkValidFormat(audio_level, regex.CMIDecimal)
|
||||||
|
&& this.API.checkValidRange(audio_level, regex.audio_range)) {
|
||||||
|
this.#audio_level = audio_level;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get language() { return this.#language; }
|
||||||
|
set language(language) {
|
||||||
|
if(this.API.checkValidFormat(language, regex.CMILang)) {
|
||||||
|
this.#language = language;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get delivery_speed() { return this.#delivery_speed; }
|
||||||
|
set delivery_speed(delivery_speed) {
|
||||||
|
if(this.API.checkValidFormat(delivery_speed, regex.CMIDecimal)
|
||||||
|
&& this.API.checkValidRange(delivery_speed, regex.speed_range)) {
|
||||||
|
this.#delivery_speed = delivery_speed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get audio_captioning() { return this.#audio_captioning; }
|
||||||
|
set audio_captioning(audio_captioning) {
|
||||||
|
if(this.API.checkValidFormat(audio_captioning, regex.CMISInteger)
|
||||||
|
&& this.API.checkValidRange(audio_captioning, regex.text_range)) {
|
||||||
|
this.#audio_captioning = audio_captioning;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class CMIInteractionsObject extends BaseCMI {
|
||||||
|
constructor(API) {
|
||||||
|
super(API);
|
||||||
|
|
||||||
|
this.objectives = new CMIArray({
|
||||||
|
API: API,
|
||||||
|
errorCode: 404,
|
||||||
|
children: constants.objectives_children
|
||||||
|
});
|
||||||
|
this.correct_responses = new CMIArray({
|
||||||
|
API: API,
|
||||||
|
errorCode: 404,
|
||||||
|
children: constants.correct_responses_children
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#id: "";
|
||||||
|
#type: "";
|
||||||
|
#timestamp: "";
|
||||||
|
#weighting: "";
|
||||||
|
#learner_response: "";
|
||||||
|
#result: "";
|
||||||
|
#latency: "";
|
||||||
|
#description: "";
|
||||||
|
|
||||||
|
get id() { return this.#id; }
|
||||||
|
set id(id) {
|
||||||
|
if(this.API.checkValidFormat(id, regex.CMILongIdentifier)) {
|
||||||
|
this.#id = id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get type() { return this.#type; }
|
||||||
|
set type(type) {
|
||||||
|
if(this.API.checkValidFormat(type, regex.CMIType)) {
|
||||||
|
this.#type = type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get timestamp() { return this.#timestamp; }
|
||||||
|
set timestamp(timestamp) {
|
||||||
|
if(this.API.checkValidFormat(timestamp, regex.CMITime)) {
|
||||||
|
this.#timestamp = timestamp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get weighting() { return this.#weighting; }
|
||||||
|
set weighting(weighting) {
|
||||||
|
if (this.API.checkValidFormat(weighting, regex.CMIDecimal)) {
|
||||||
|
this.#weighting = weighting;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get learner_response() { return this.#learner_response; }
|
||||||
|
set learner_response(learner_response) {
|
||||||
|
if(typeof this.type === 'undefined') {
|
||||||
|
this.API.throwSCORMError(this.API.error.DEPENDENCY_NOT_ESTABLISHED);
|
||||||
|
} else {
|
||||||
|
let nodes = [];
|
||||||
|
let response_type = learner_responses[this.type];
|
||||||
|
if(response_type.delimiter !== '') {
|
||||||
|
nodes = learner_response.split(response_type.delimiter);
|
||||||
|
} else {
|
||||||
|
nodes[0] = learner_response;
|
||||||
|
}
|
||||||
|
|
||||||
|
if((nodes.length > 0) && (nodes.length <= response_type.max)) {
|
||||||
|
const formatRegex = new RegExp(response_type.format);
|
||||||
|
for(let i = 0; (i < nodes.length) && (this.API.lastErrorCode === 0); i++) {
|
||||||
|
if(typeof response_type.delimiter2 !== 'undefined') {
|
||||||
|
let values = nodes[i].split(response_type.delimiter2);
|
||||||
|
if(values.length === 2) {
|
||||||
|
if(!values[0].match(formatRegex)) {
|
||||||
|
this.API.throwSCORMError(this.API.error.TYPE_MISMATCH);
|
||||||
|
} else {
|
||||||
|
if(!values[1].match(new RegExp(response_type.format2))) {
|
||||||
|
this.API.throwSCORMError(this.API.error.TYPE_MISMATCH);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.API.throwSCORMError(this.API.error.TYPE_MISMATCH);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if(!nodes[i].match(formatRegex)) {
|
||||||
|
this.API.throwSCORMError(this.API.error.TYPE_MISMATCH);
|
||||||
|
} else {
|
||||||
|
if(nodes[i] !== '' && response_type.unique) {
|
||||||
|
for(let j = 0; (j < i) && this.API.lastErrorCode === 0; j++) {
|
||||||
|
if(nodes[i] === nodes[j]) {
|
||||||
|
this.API.throwSCORMError(this.API.error.TYPE_MISMATCH);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.API.throwSCORMError(this.API.error.GENERAL_SET_FAILURE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get result() { return this.#result; }
|
||||||
|
set result(result) {
|
||||||
|
if(this.API.checkValidFormat(result, regex.CMIResult)) {
|
||||||
|
this.#result = result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get latency() { return this.#latency; }
|
||||||
|
set latency(latency) {
|
||||||
|
if(this.API.checkValidFormat(latency, regex.CMITimespan)) {
|
||||||
|
this.#latency = latency;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get description() { return this.#description; }
|
||||||
|
set description(description) {
|
||||||
|
if(this.API.checkValidFormat(description, regex.CMILangString250)) {
|
||||||
|
this.#description = description;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class CMIObjectivesObject extends BaseCMI {
|
||||||
|
constructor(API) {
|
||||||
|
super(API);
|
||||||
|
}
|
||||||
|
|
||||||
|
#id: "";
|
||||||
|
#success_status: "unknown"; // Allowed values: "passed", "failed", "unknown"
|
||||||
|
#completion_status: "unknown"; // Allowed values: "completed", "incomplete", "not attempted", "unknown"
|
||||||
|
#progress_measure: ""; // Data type: real (10,7). Range: 0.0 to 1.0
|
||||||
|
#description: ""; // SPM 250 characters
|
||||||
|
|
||||||
|
get id() { return this.#id; }
|
||||||
|
set id(id) {
|
||||||
|
if(this.API.checkValidFormat(id, regex.CMILongIdentifier)) {
|
||||||
|
this.#id = id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get success_status() { return this.#success_status; }
|
||||||
|
set success_status(success_status) {
|
||||||
|
if(this.API.checkValidFormat(success_status, regex.CMISStatus)) {
|
||||||
|
this.#success_status = success_status;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get completion_status() { return this.#completion_status; }
|
||||||
|
set completion_status(completion_status) {
|
||||||
|
if(this.API.checkValidFormat(completion_status, regex.CMICStatus)) {
|
||||||
|
this.#completion_status = completion_status;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get progress_measure() { return this.#progress_measure; }
|
||||||
|
set progress_measure(progress_measure) {
|
||||||
|
if(this.API.checkValidFormat(progress_measure, regex.CMIDecimal)
|
||||||
|
&& this.API.checkValidRange(progress_measure, regex.progress_range)) {
|
||||||
|
this.#progress_measure = progress_measure;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get description() { return this.#description; }
|
||||||
|
set description(description) {
|
||||||
|
if(this.API.checkValidFormat(description, regex.CMILangString250)) {
|
||||||
|
this.#description = description;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
score = new Scorm2004CMIScore(this.API);
|
||||||
|
}
|
||||||
|
|
||||||
|
class Scorm2004CMIScore extends CMIScore {
|
||||||
|
constructor(API) {
|
||||||
|
super(API, constants.score_children);
|
||||||
|
this.max = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
#scaled: "";
|
||||||
|
|
||||||
|
get scaled() { return this.#scaled; }
|
||||||
|
set scaled(scaled) {
|
||||||
|
if(this.API.checkValidFormat(scaled, regex.CMIDecimal)
|
||||||
|
&& this.API.checkValidRange(scaled, regex.scaled_range)) {
|
||||||
|
this.#scaled = scaled;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class CMICommentsFromLearnerObject extends BaseCMI {
|
||||||
|
constructor(API) {
|
||||||
|
super(API);
|
||||||
|
}
|
||||||
|
#comment = "";
|
||||||
|
#location = "";
|
||||||
|
#timestamp = "";
|
||||||
|
|
||||||
|
get comment() { return this.#comment; }
|
||||||
|
set comment(comment) {
|
||||||
|
if(this.API.checkValidFormat(comment, regex.CMILangString4000)) {
|
||||||
|
this.#comment = comment;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get location() { return this.#location; }
|
||||||
|
set location(location) {
|
||||||
|
if(this.API.checkValidFormat(location, regex.CMIString250)) {
|
||||||
|
this.#location = location;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get timestamp() { return this.#timestamp; }
|
||||||
|
set timestamp(timestamp) {
|
||||||
|
if(this.API.checkValidFormat(timestamp, regex.CMITime)) {
|
||||||
|
this.#timestamp = timestamp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class CMICommentsFromLMSObject extends CMICommentsFromLearnerObject {
|
||||||
|
constructor(API) {
|
||||||
|
super(API);
|
||||||
|
}
|
||||||
|
|
||||||
|
set comment(comment) { this.API.isNotInitialized() ? this.comment = comment : this.throwReadOnlyError(); }
|
||||||
|
|
||||||
|
set location(location) { this.API.isNotInitialized() ? this.location = location : this.throwReadOnlyError(); }
|
||||||
|
|
||||||
|
set timestamp(timestamp) { this.API.isNotInitialized() ? this.timestamp = timestamp : this.throwReadOnlyError(); }
|
||||||
|
}
|
||||||
|
|
||||||
|
export class CMIInteractionsObjectivesObject extends BaseCMI {
|
||||||
|
constructor(API) {
|
||||||
|
super(API);
|
||||||
|
}
|
||||||
|
|
||||||
|
#id: "";
|
||||||
|
|
||||||
|
get id() { return this.#id; }
|
||||||
|
set id(id) {
|
||||||
|
if(this.API.checkValidFormat(id, regex.CMILongIdentifier)) {
|
||||||
|
this.#id = id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class CMIInteractionsCorrectResponsesObject extends BaseCMI {
|
||||||
|
constructor(API) {
|
||||||
|
super(API);
|
||||||
|
}
|
||||||
|
|
||||||
|
#pattern: "";
|
||||||
|
|
||||||
|
get pattern() { return this.#pattern; }
|
||||||
|
set pattern(pattern) {
|
||||||
|
if(this.API.checkValidFormat(pattern, regex.CMIFeedback)) {
|
||||||
|
this.#pattern = pattern;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ADL extends BaseCMI {
|
||||||
|
constructor(API) {
|
||||||
|
super(API);
|
||||||
|
}
|
||||||
|
|
||||||
|
nav = new class extends BaseCMI {
|
||||||
|
constructor(API) {
|
||||||
|
super(API);
|
||||||
|
}
|
||||||
|
|
||||||
|
#request = "_none_"; // Allowed values: "continue", "previous", "choice", "jump", "exit", "exitAll", "abandon", "abandonAll", "_none_"
|
||||||
|
|
||||||
|
get request() { return this.#request; }
|
||||||
|
set request(request) {
|
||||||
|
if(this.API.checkValidFormat(request, regex.NAVEvent)) {
|
||||||
|
this.#request = request;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
request_valid = new class extends BaseCMI {
|
||||||
|
constructor(API) {
|
||||||
|
super(API);
|
||||||
|
}
|
||||||
|
|
||||||
|
#continue = "unknown"; // Allowed values: "true", "false", "unknown"
|
||||||
|
#previous = "unknown"; // Allowed values: "true", "false", "unknown"
|
||||||
|
|
||||||
|
get continue() { return this.#continue; }
|
||||||
|
set continue(_) { API.throwSCORMError(404); }
|
||||||
|
|
||||||
|
get previous() { return this.#previous; }
|
||||||
|
set previous(_) { API.throwSCORMError(404); }
|
||||||
|
|
||||||
|
choice = class {
|
||||||
|
_isTargetValid = (_target) => "unknown";
|
||||||
|
};
|
||||||
|
|
||||||
|
jump = class {
|
||||||
|
_isTargetValid = (_target) => "unknown";
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
814
src/constants.js
Normal file
814
src/constants.js
Normal file
@@ -0,0 +1,814 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) Noverant, Inc - All Rights Reserved Unauthorized copying of this file, via any
|
||||||
|
* medium is strictly prohibited Proprietary and confidential
|
||||||
|
*/
|
||||||
|
|
||||||
|
// @flow
|
||||||
|
|
||||||
|
|
||||||
|
import {scorm2004_regex} from "./regex";
|
||||||
|
|
||||||
|
export const base_error_codes = {
|
||||||
|
GENERAL: 101,
|
||||||
|
INITIALIZATION_FAILED: 101,
|
||||||
|
INITIALIZED: 101,
|
||||||
|
TERMINATED: 101,
|
||||||
|
TERMINATION_FAILURE: 101,
|
||||||
|
TERMINATION_BEFORE_INIT: 101,
|
||||||
|
MULTIPLE_TERMINATION: 101,
|
||||||
|
RETRIEVE_BEFORE_INIT: 101,
|
||||||
|
RETRIEVE_AFTER_TERM: 101,
|
||||||
|
STORE_BEFORE_INIT: 101,
|
||||||
|
STORE_AFTER_TERM: 101,
|
||||||
|
COMMIT_BEFORE_INIT: 101,
|
||||||
|
COMMIT_AFTER_TERM: 101,
|
||||||
|
ARGUMENT_ERROR: 101,
|
||||||
|
CHILDREN_ERROR: 101,
|
||||||
|
COUNT_ERROR: 101,
|
||||||
|
GENERAL_GET_FAILURE: 101,
|
||||||
|
GENERAL_SET_FAILURE: 101,
|
||||||
|
GENERAL_COMMIT_FAILURE: 101,
|
||||||
|
UNDEFINED_DATA_MODEL: 101,
|
||||||
|
UNIMPLEMENTED_ELEMENT: 101,
|
||||||
|
VALUE_NOT_INITIALIZED: 101,
|
||||||
|
INVALID_SET_VALUE: 101,
|
||||||
|
READ_ONLY_ELEMENT: 101,
|
||||||
|
WRITE_ONLY_ELEMENT: 101,
|
||||||
|
TYPE_MISMATCH: 101,
|
||||||
|
VALUE_OUT_OF_RANGE: 101,
|
||||||
|
DEPENDENCY_NOT_ESTABLISHED: 101
|
||||||
|
};
|
||||||
|
|
||||||
|
export const scorm12_error_codes = {
|
||||||
|
...base_error_codes, ...{
|
||||||
|
RETRIEVE_BEFORE_INIT: 301,
|
||||||
|
STORE_BEFORE_INIT: 301,
|
||||||
|
COMMIT_BEFORE_INIT: 301,
|
||||||
|
ARGUMENT_ERROR: 201,
|
||||||
|
CHILDREN_ERROR: 202,
|
||||||
|
COUNT_ERROR: 203,
|
||||||
|
UNDEFINED_DATA_MODEL: 401,
|
||||||
|
UNIMPLEMENTED_ELEMENT: 401,
|
||||||
|
VALUE_NOT_INITIALIZED: 301,
|
||||||
|
INVALID_SET_VALUE: 402,
|
||||||
|
READ_ONLY_ELEMENT: 403,
|
||||||
|
WRITE_ONLY_ELEMENT: 404,
|
||||||
|
TYPE_MISMATCH: 405,
|
||||||
|
VALUE_OUT_OF_RANGE: 407,
|
||||||
|
DEPENDENCY_NOT_ESTABLISHED: 408
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const scorm2004_error_codes = {
|
||||||
|
...base_error_codes, ...{
|
||||||
|
INITIALIZATION_FAILED: 102,
|
||||||
|
INITIALIZED: 103,
|
||||||
|
TERMINATED: 104,
|
||||||
|
TERMINATION_FAILURE: 111,
|
||||||
|
TERMINATION_BEFORE_INIT: 112,
|
||||||
|
MULTIPLE_TERMINATIONS: 113,
|
||||||
|
RETRIEVE_BEFORE_INIT: 122,
|
||||||
|
RETRIEVE_AFTER_TERM: 123,
|
||||||
|
STORE_BEFORE_INIT: 132,
|
||||||
|
STORE_AFTER_TERM: 133,
|
||||||
|
COMMIT_BEFORE_INIT: 142,
|
||||||
|
COMMIT_AFTER_TERM: 143,
|
||||||
|
ARGUMENT_ERROR: 201,
|
||||||
|
GENERAL_GET_FAILURE: 301,
|
||||||
|
GENERAL_SET_FAILURE: 351,
|
||||||
|
GENERAL_COMMIT_FAILURE: 391,
|
||||||
|
UNDEFINED_DATA_MODEL: 401,
|
||||||
|
UNIMPLEMENTED_ELEMENT: 402,
|
||||||
|
VALUE_NOT_INITIALIZED: 403,
|
||||||
|
READ_ONLY_ELEMENT: 404,
|
||||||
|
WRITE_ONLY_ELEMENT: 405,
|
||||||
|
TYPE_MISMATCH: 406,
|
||||||
|
VALUE_OUT_OF_RANGE: 407,
|
||||||
|
DEPENDENCY_NOT_ESTABLISHED: 408
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const scorm12_constants = {
|
||||||
|
// Children lists
|
||||||
|
cmi_children: 'core,suspend_data,launch_data,comments,objectives,student_data,student_preference,interactions',
|
||||||
|
core_children: 'student_id,student_name,lesson_location,credit,lesson_status,entry,score,total_time,lesson_mode,exit,session_time',
|
||||||
|
score_children: 'raw,min,max',
|
||||||
|
comments_children: 'content,location,time',
|
||||||
|
objectives_children: 'id,score,status',
|
||||||
|
correct_responses_children: 'pattern',
|
||||||
|
student_data_children: 'mastery_score,max_time_allowed,time_limit_action',
|
||||||
|
student_preference_children: 'audio,language,speed,text',
|
||||||
|
interactions_children: 'id,objectives,time,type,correct_responses,weighting,student_response,result,latency',
|
||||||
|
|
||||||
|
error_descriptions: {
|
||||||
|
"101": {
|
||||||
|
basicMessage: "General Exception",
|
||||||
|
detailMessage: "No specific error code exists to describe the error. Use LMSGetDiagnostic for more information",
|
||||||
|
},
|
||||||
|
"201": {
|
||||||
|
basicMessage: "Invalid argument error",
|
||||||
|
detailMessage: "Indicates that an argument represents an invalid data model element or is otherwise incorrect.",
|
||||||
|
},
|
||||||
|
"202": {
|
||||||
|
basicMessage: "Element cannot have children",
|
||||||
|
detailMessage: "Indicates that LMSGetValue was called with a data model element name that ends in \"_children\" for a data model element that does not support the \"_children\" suffix.",
|
||||||
|
},
|
||||||
|
"203": {
|
||||||
|
basicMessage: "Element not an array - cannot have count",
|
||||||
|
detailMessage: "Indicates that LMSGetValue was called with a data model element name that ends in \"_count\" for a data model element that does not support the \"_count\" suffix.",
|
||||||
|
},
|
||||||
|
"301": {
|
||||||
|
basicMessage: "Not initialized",
|
||||||
|
detailMessage: "Indicates that an API call was made before the call to LMSInitialize.",
|
||||||
|
},
|
||||||
|
"401": {
|
||||||
|
basicMessage: "Not implemented error",
|
||||||
|
detailMessage: "The data model element indicated in a call to LMSGetValue or LMSSetValue is valid, but was not implemented by this LMS. SCORM 1.2 defines a set of data model elements as being optional for an LMS to implement.",
|
||||||
|
},
|
||||||
|
"402": {
|
||||||
|
basicMessage: "Invalid set value, element is a keyword",
|
||||||
|
detailMessage: "Indicates that LMSSetValue was called on a data model element that represents a keyword (elements that end in \"_children\" and \"_count\").",
|
||||||
|
},
|
||||||
|
"403": {
|
||||||
|
basicMessage: "Element is read only",
|
||||||
|
detailMessage: "LMSSetValue was called with a data model element that can only be read.",
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
basicMessage: "Element is write only",
|
||||||
|
detailMessage: "LMSGetValue was called on a data model element that can only be written to.",
|
||||||
|
},
|
||||||
|
"405": {
|
||||||
|
basicMessage: "Incorrect Data Type",
|
||||||
|
detailMessage: "LMSSetValue was called with a value that is not consistent with the data format of the supplied data model element.",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const aicc_constants = {
|
||||||
|
...scorm12_constants, ...{
|
||||||
|
cmi_children: 'core,suspend_data,launch_data,comments,objectives,student_data,student_preference,interactions,evaluation',
|
||||||
|
student_data_children: 'attempt_number,tries,mastery_score,max_time_allowed,time_limit_action',
|
||||||
|
tries_children: "time,status,score"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const scorm2004_constants = {
|
||||||
|
// Children lists
|
||||||
|
cmi_children: '_version,comments_from_learner,comments_from_lms,completion_status,credit,entry,exit,interactions,launch_data,learner_id,learner_name,learner_preference,location,max_time_allowed,mode,objectives,progress_measure,scaled_passing_score,score,session_time,success_status,suspend_data,time_limit_action,total_time',
|
||||||
|
comments_children: 'comment,timestamp,location',
|
||||||
|
score_children: 'max,raw,scaled,min',
|
||||||
|
objectives_children: 'progress_measure,completion_status,success_status,description,score,id',
|
||||||
|
correct_responses_children: 'pattern',
|
||||||
|
student_data_children: 'mastery_score,max_time_allowed,time_limit_action',
|
||||||
|
student_preference_children: 'audio_level,audio_captioning,delivery_speed,language',
|
||||||
|
interactions_children: 'id,type,objectives,timestamp,correct_responses,weighting,learner_response,result,latency,description',
|
||||||
|
|
||||||
|
error_descriptions: {
|
||||||
|
"0": {
|
||||||
|
basicMessage: "No Error",
|
||||||
|
detailMessage: "No error occurred, the previous API call was successful.",
|
||||||
|
},
|
||||||
|
"101": {
|
||||||
|
basicMessage: "General Exception",
|
||||||
|
detailMessage: "No specific error code exists to describe the error. Use GetDiagnostic for more information.",
|
||||||
|
},
|
||||||
|
"102": {
|
||||||
|
basicMessage: "General Initialization Failure",
|
||||||
|
detailMessage: "Call to Initialize failed for an unknown reason.",
|
||||||
|
},
|
||||||
|
"103": {
|
||||||
|
basicMessage: "Already Initialized",
|
||||||
|
detailMessage: "Call to Initialize failed because Initialize was already called.",
|
||||||
|
},
|
||||||
|
"104": {
|
||||||
|
basicMessage: "Content Instance Terminated",
|
||||||
|
detailMessage: "Call to Initialize failed because Terminate was already called.",
|
||||||
|
},
|
||||||
|
"111": {
|
||||||
|
basicMessage: "General Termination Failure",
|
||||||
|
detailMessage: "Call to Terminate failed for an unknown reason.",
|
||||||
|
},
|
||||||
|
"112": {
|
||||||
|
basicMessage: "Termination Before Initialization",
|
||||||
|
detailMessage: "Call to Terminate failed because it was made before the call to Initialize.",
|
||||||
|
},
|
||||||
|
"113": {
|
||||||
|
basicMessage: "Termination After Termination",
|
||||||
|
detailMessage: "Call to Terminate failed because Terminate was already called.",
|
||||||
|
},
|
||||||
|
"122": {
|
||||||
|
basicMessage: "Retrieve Data Before Initialization",
|
||||||
|
detailMessage: "Call to GetValue failed because it was made before the call to Initialize.",
|
||||||
|
},
|
||||||
|
"123": {
|
||||||
|
basicMessage: "Retrieve Data After Termination",
|
||||||
|
detailMessage: "Call to GetValue failed because it was made after the call to Terminate.",
|
||||||
|
},
|
||||||
|
"132": {
|
||||||
|
basicMessage: "Store Data Before Initialization",
|
||||||
|
detailMessage: "Call to SetValue failed because it was made before the call to Initialize.",
|
||||||
|
},
|
||||||
|
"133": {
|
||||||
|
basicMessage: "Store Data After Termination",
|
||||||
|
detailMessage: "Call to SetValue failed because it was made after the call to Terminate.",
|
||||||
|
},
|
||||||
|
"142": {
|
||||||
|
basicMessage: "Commit Before Initialization",
|
||||||
|
detailMessage: "Call to Commit failed because it was made before the call to Initialize.",
|
||||||
|
},
|
||||||
|
"143": {
|
||||||
|
basicMessage: "Commit After Termination",
|
||||||
|
detailMessage: "Call to Commit failed because it was made after the call to Terminate.",
|
||||||
|
},
|
||||||
|
"201": {
|
||||||
|
basicMessage: "General Argument Error",
|
||||||
|
detailMessage: "An invalid argument was passed to an API method (usually indicates that Initialize, Commit or Terminate did not receive the expected empty string argument.",
|
||||||
|
},
|
||||||
|
"301": {
|
||||||
|
basicMessage: "General Get Failure",
|
||||||
|
detailMessage: "Indicates a failed GetValue call where no other specific error code is applicable. Use GetDiagnostic for more information.",
|
||||||
|
},
|
||||||
|
"351": {
|
||||||
|
basicMessage: "General Set Failure",
|
||||||
|
detailMessage: "Indicates a failed SetValue call where no other specific error code is applicable. Use GetDiagnostic for more information.",
|
||||||
|
},
|
||||||
|
"391": {
|
||||||
|
basicMessage: "General Commit Failure",
|
||||||
|
detailMessage: "Indicates a failed Commit call where no other specific error code is applicable. Use GetDiagnostic for more information.",
|
||||||
|
},
|
||||||
|
"401": {
|
||||||
|
basicMessage: "Undefined Data Model Element",
|
||||||
|
detailMessage: "The data model element name passed to GetValue or SetValue is not a valid SCORM data model element.",
|
||||||
|
},
|
||||||
|
"402": {
|
||||||
|
basicMessage: "Unimplemented Data Model Element",
|
||||||
|
detailMessage: "The data model element indicated in a call to GetValue or SetValue is valid, but was not implemented by this LMS. In SCORM 2004, this error would indicate an LMS that is not fully SCORM conformant.",
|
||||||
|
},
|
||||||
|
"403": {
|
||||||
|
basicMessage: "Data Model Element Value Not Initialized",
|
||||||
|
detailMessage: "Attempt to read a data model element that has not been initialized by the LMS or through a SetValue call. This error condition is often reached during normal execution of a SCO.",
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
basicMessage: "Data Model Element Is Read Only",
|
||||||
|
detailMessage: "SetValue was called with a data model element that can only be read.",
|
||||||
|
},
|
||||||
|
"405": {
|
||||||
|
basicMessage: "Data Model Element Is Write Only",
|
||||||
|
detailMessage: "GetValue was called on a data model element that can only be written to.",
|
||||||
|
},
|
||||||
|
"406": {
|
||||||
|
basicMessage: "Data Model Element Type Mismatch",
|
||||||
|
detailMessage: "SetValue was called with a value that is not consistent with the data format of the supplied data model element.",
|
||||||
|
},
|
||||||
|
"407": {
|
||||||
|
basicMessage: "Data Model Element Value Out Of Range",
|
||||||
|
detailMessage: "The numeric value supplied to a SetValue call is outside of the numeric range allowed for the supplied data model element.",
|
||||||
|
},
|
||||||
|
"408": {
|
||||||
|
basicMessage: "Data Model Dependency Not Established",
|
||||||
|
detailMessage: "Some data model elements cannot be set until another data model element was set. This error condition indicates that the prerequisite element was not set before the dependent element.",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
valid_languages: {
|
||||||
|
'aa': 'aa',
|
||||||
|
'ab': 'ab',
|
||||||
|
'ae': 'ae',
|
||||||
|
'af': 'af',
|
||||||
|
'ak': 'ak',
|
||||||
|
'am': 'am',
|
||||||
|
'an': 'an',
|
||||||
|
'ar': 'ar',
|
||||||
|
'as': 'as',
|
||||||
|
'av': 'av',
|
||||||
|
'ay': 'ay',
|
||||||
|
'az': 'az',
|
||||||
|
'ba': 'ba',
|
||||||
|
'be': 'be',
|
||||||
|
'bg': 'bg',
|
||||||
|
'bh': 'bh',
|
||||||
|
'bi': 'bi',
|
||||||
|
'bm': 'bm',
|
||||||
|
'bn': 'bn',
|
||||||
|
'bo': 'bo',
|
||||||
|
'br': 'br',
|
||||||
|
'bs': 'bs',
|
||||||
|
'ca': 'ca',
|
||||||
|
'ce': 'ce',
|
||||||
|
'ch': 'ch',
|
||||||
|
'co': 'co',
|
||||||
|
'cr': 'cr',
|
||||||
|
'cs': 'cs',
|
||||||
|
'cu': 'cu',
|
||||||
|
'cv': 'cv',
|
||||||
|
'cy': 'cy',
|
||||||
|
'da': 'da',
|
||||||
|
'de': 'de',
|
||||||
|
'dv': 'dv',
|
||||||
|
'dz': 'dz',
|
||||||
|
'ee': 'ee',
|
||||||
|
'el': 'el',
|
||||||
|
'en': 'en',
|
||||||
|
'eo': 'eo',
|
||||||
|
'es': 'es',
|
||||||
|
'et': 'et',
|
||||||
|
'eu': 'eu',
|
||||||
|
'fa': 'fa',
|
||||||
|
'ff': 'ff',
|
||||||
|
'fi': 'fi',
|
||||||
|
'fj': 'fj',
|
||||||
|
'fo': 'fo',
|
||||||
|
'fr': 'fr',
|
||||||
|
'fy': 'fy',
|
||||||
|
'ga': 'ga',
|
||||||
|
'gd': 'gd',
|
||||||
|
'gl': 'gl',
|
||||||
|
'gn': 'gn',
|
||||||
|
'gu': 'gu',
|
||||||
|
'gv': 'gv',
|
||||||
|
'ha': 'ha',
|
||||||
|
'he': 'he',
|
||||||
|
'hi': 'hi',
|
||||||
|
'ho': 'ho',
|
||||||
|
'hr': 'hr',
|
||||||
|
'ht': 'ht',
|
||||||
|
'hu': 'hu',
|
||||||
|
'hy': 'hy',
|
||||||
|
'hz': 'hz',
|
||||||
|
'ia': 'ia',
|
||||||
|
'id': 'id',
|
||||||
|
'ie': 'ie',
|
||||||
|
'ig': 'ig',
|
||||||
|
'ii': 'ii',
|
||||||
|
'ik': 'ik',
|
||||||
|
'io': 'io',
|
||||||
|
'is': 'is',
|
||||||
|
'it': 'it',
|
||||||
|
'iu': 'iu',
|
||||||
|
'ja': 'ja',
|
||||||
|
'jv': 'jv',
|
||||||
|
'ka': 'ka',
|
||||||
|
'kg': 'kg',
|
||||||
|
'ki': 'ki',
|
||||||
|
'kj': 'kj',
|
||||||
|
'kk': 'kk',
|
||||||
|
'kl': 'kl',
|
||||||
|
'km': 'km',
|
||||||
|
'kn': 'kn',
|
||||||
|
'ko': 'ko',
|
||||||
|
'kr': 'kr',
|
||||||
|
'ks': 'ks',
|
||||||
|
'ku': 'ku',
|
||||||
|
'kv': 'kv',
|
||||||
|
'kw': 'kw',
|
||||||
|
'ky': 'ky',
|
||||||
|
'la': 'la',
|
||||||
|
'lb': 'lb',
|
||||||
|
'lg': 'lg',
|
||||||
|
'li': 'li',
|
||||||
|
'ln': 'ln',
|
||||||
|
'lo': 'lo',
|
||||||
|
'lt': 'lt',
|
||||||
|
'lu': 'lu',
|
||||||
|
'lv': 'lv',
|
||||||
|
'mg': 'mg',
|
||||||
|
'mh': 'mh',
|
||||||
|
'mi': 'mi',
|
||||||
|
'mk': 'mk',
|
||||||
|
'ml': 'ml',
|
||||||
|
'mn': 'mn',
|
||||||
|
'mo': 'mo',
|
||||||
|
'mr': 'mr',
|
||||||
|
'ms': 'ms',
|
||||||
|
'mt': 'mt',
|
||||||
|
'my': 'my',
|
||||||
|
'na': 'na',
|
||||||
|
'nb': 'nb',
|
||||||
|
'nd': 'nd',
|
||||||
|
'ne': 'ne',
|
||||||
|
'ng': 'ng',
|
||||||
|
'nl': 'nl',
|
||||||
|
'nn': 'nn',
|
||||||
|
'no': 'no',
|
||||||
|
'nr': 'nr',
|
||||||
|
'nv': 'nv',
|
||||||
|
'ny': 'ny',
|
||||||
|
'oc': 'oc',
|
||||||
|
'oj': 'oj',
|
||||||
|
'om': 'om',
|
||||||
|
'or': 'or',
|
||||||
|
'os': 'os',
|
||||||
|
'pa': 'pa',
|
||||||
|
'pi': 'pi',
|
||||||
|
'pl': 'pl',
|
||||||
|
'ps': 'ps',
|
||||||
|
'pt': 'pt',
|
||||||
|
'qu': 'qu',
|
||||||
|
'rm': 'rm',
|
||||||
|
'rn': 'rn',
|
||||||
|
'ro': 'ro',
|
||||||
|
'ru': 'ru',
|
||||||
|
'rw': 'rw',
|
||||||
|
'sa': 'sa',
|
||||||
|
'sc': 'sc',
|
||||||
|
'sd': 'sd',
|
||||||
|
'se': 'se',
|
||||||
|
'sg': 'sg',
|
||||||
|
'sh': 'sh',
|
||||||
|
'si': 'si',
|
||||||
|
'sk': 'sk',
|
||||||
|
'sl': 'sl',
|
||||||
|
'sm': 'sm',
|
||||||
|
'sn': 'sn',
|
||||||
|
'so': 'so',
|
||||||
|
'sq': 'sq',
|
||||||
|
'sr': 'sr',
|
||||||
|
'ss': 'ss',
|
||||||
|
'st': 'st',
|
||||||
|
'su': 'su',
|
||||||
|
'sv': 'sv',
|
||||||
|
'sw': 'sw',
|
||||||
|
'ta': 'ta',
|
||||||
|
'te': 'te',
|
||||||
|
'tg': 'tg',
|
||||||
|
'th': 'th',
|
||||||
|
'ti': 'ti',
|
||||||
|
'tk': 'tk',
|
||||||
|
'tl': 'tl',
|
||||||
|
'tn': 'tn',
|
||||||
|
'to': 'to',
|
||||||
|
'tr': 'tr',
|
||||||
|
'ts': 'ts',
|
||||||
|
'tt': 'tt',
|
||||||
|
'tw': 'tw',
|
||||||
|
'ty': 'ty',
|
||||||
|
'ug': 'ug',
|
||||||
|
'uk': 'uk',
|
||||||
|
'ur': 'ur',
|
||||||
|
'uz': 'uz',
|
||||||
|
've': 've',
|
||||||
|
'vi': 'vi',
|
||||||
|
'vo': 'vo',
|
||||||
|
'wa': 'wa',
|
||||||
|
'wo': 'wo',
|
||||||
|
'xh': 'xh',
|
||||||
|
'yi': 'yi',
|
||||||
|
'yo': 'yo',
|
||||||
|
'za': 'za',
|
||||||
|
'zh': 'zh',
|
||||||
|
'zu': 'zu',
|
||||||
|
'aar': 'aar',
|
||||||
|
'abk': 'abk',
|
||||||
|
'ave': 'ave',
|
||||||
|
'afr': 'afr',
|
||||||
|
'aka': 'aka',
|
||||||
|
'amh': 'amh',
|
||||||
|
'arg': 'arg',
|
||||||
|
'ara': 'ara',
|
||||||
|
'asm': 'asm',
|
||||||
|
'ava': 'ava',
|
||||||
|
'aym': 'aym',
|
||||||
|
'aze': 'aze',
|
||||||
|
'bak': 'bak',
|
||||||
|
'bel': 'bel',
|
||||||
|
'bul': 'bul',
|
||||||
|
'bih': 'bih',
|
||||||
|
'bis': 'bis',
|
||||||
|
'bam': 'bam',
|
||||||
|
'ben': 'ben',
|
||||||
|
'tib': 'tib',
|
||||||
|
'bod': 'bod',
|
||||||
|
'bre': 'bre',
|
||||||
|
'bos': 'bos',
|
||||||
|
'cat': 'cat',
|
||||||
|
'che': 'che',
|
||||||
|
'cha': 'cha',
|
||||||
|
'cos': 'cos',
|
||||||
|
'cre': 'cre',
|
||||||
|
'cze': 'cze',
|
||||||
|
'ces': 'ces',
|
||||||
|
'chu': 'chu',
|
||||||
|
'chv': 'chv',
|
||||||
|
'wel': 'wel',
|
||||||
|
'cym': 'cym',
|
||||||
|
'dan': 'dan',
|
||||||
|
'ger': 'ger',
|
||||||
|
'deu': 'deu',
|
||||||
|
'div': 'div',
|
||||||
|
'dzo': 'dzo',
|
||||||
|
'ewe': 'ewe',
|
||||||
|
'gre': 'gre',
|
||||||
|
'ell': 'ell',
|
||||||
|
'eng': 'eng',
|
||||||
|
'epo': 'epo',
|
||||||
|
'spa': 'spa',
|
||||||
|
'est': 'est',
|
||||||
|
'baq': 'baq',
|
||||||
|
'eus': 'eus',
|
||||||
|
'per': 'per',
|
||||||
|
'fas': 'fas',
|
||||||
|
'ful': 'ful',
|
||||||
|
'fin': 'fin',
|
||||||
|
'fij': 'fij',
|
||||||
|
'fao': 'fao',
|
||||||
|
'fre': 'fre',
|
||||||
|
'fra': 'fra',
|
||||||
|
'fry': 'fry',
|
||||||
|
'gle': 'gle',
|
||||||
|
'gla': 'gla',
|
||||||
|
'glg': 'glg',
|
||||||
|
'grn': 'grn',
|
||||||
|
'guj': 'guj',
|
||||||
|
'glv': 'glv',
|
||||||
|
'hau': 'hau',
|
||||||
|
'heb': 'heb',
|
||||||
|
'hin': 'hin',
|
||||||
|
'hmo': 'hmo',
|
||||||
|
'hrv': 'hrv',
|
||||||
|
'hat': 'hat',
|
||||||
|
'hun': 'hun',
|
||||||
|
'arm': 'arm',
|
||||||
|
'hye': 'hye',
|
||||||
|
'her': 'her',
|
||||||
|
'ina': 'ina',
|
||||||
|
'ind': 'ind',
|
||||||
|
'ile': 'ile',
|
||||||
|
'ibo': 'ibo',
|
||||||
|
'iii': 'iii',
|
||||||
|
'ipk': 'ipk',
|
||||||
|
'ido': 'ido',
|
||||||
|
'ice': 'ice',
|
||||||
|
'isl': 'isl',
|
||||||
|
'ita': 'ita',
|
||||||
|
'iku': 'iku',
|
||||||
|
'jpn': 'jpn',
|
||||||
|
'jav': 'jav',
|
||||||
|
'geo': 'geo',
|
||||||
|
'kat': 'kat',
|
||||||
|
'kon': 'kon',
|
||||||
|
'kik': 'kik',
|
||||||
|
'kua': 'kua',
|
||||||
|
'kaz': 'kaz',
|
||||||
|
'kal': 'kal',
|
||||||
|
'khm': 'khm',
|
||||||
|
'kan': 'kan',
|
||||||
|
'kor': 'kor',
|
||||||
|
'kau': 'kau',
|
||||||
|
'kas': 'kas',
|
||||||
|
'kur': 'kur',
|
||||||
|
'kom': 'kom',
|
||||||
|
'cor': 'cor',
|
||||||
|
'kir': 'kir',
|
||||||
|
'lat': 'lat',
|
||||||
|
'ltz': 'ltz',
|
||||||
|
'lug': 'lug',
|
||||||
|
'lim': 'lim',
|
||||||
|
'lin': 'lin',
|
||||||
|
'lao': 'lao',
|
||||||
|
'lit': 'lit',
|
||||||
|
'lub': 'lub',
|
||||||
|
'lav': 'lav',
|
||||||
|
'mlg': 'mlg',
|
||||||
|
'mah': 'mah',
|
||||||
|
'mao': 'mao',
|
||||||
|
'mri': 'mri',
|
||||||
|
'mac': 'mac',
|
||||||
|
'mkd': 'mkd',
|
||||||
|
'mal': 'mal',
|
||||||
|
'mon': 'mon',
|
||||||
|
'mol': 'mol',
|
||||||
|
'mar': 'mar',
|
||||||
|
'may': 'may',
|
||||||
|
'msa': 'msa',
|
||||||
|
'mlt': 'mlt',
|
||||||
|
'bur': 'bur',
|
||||||
|
'mya': 'mya',
|
||||||
|
'nau': 'nau',
|
||||||
|
'nob': 'nob',
|
||||||
|
'nde': 'nde',
|
||||||
|
'nep': 'nep',
|
||||||
|
'ndo': 'ndo',
|
||||||
|
'dut': 'dut',
|
||||||
|
'nld': 'nld',
|
||||||
|
'nno': 'nno',
|
||||||
|
'nor': 'nor',
|
||||||
|
'nbl': 'nbl',
|
||||||
|
'nav': 'nav',
|
||||||
|
'nya': 'nya',
|
||||||
|
'oci': 'oci',
|
||||||
|
'oji': 'oji',
|
||||||
|
'orm': 'orm',
|
||||||
|
'ori': 'ori',
|
||||||
|
'oss': 'oss',
|
||||||
|
'pan': 'pan',
|
||||||
|
'pli': 'pli',
|
||||||
|
'pol': 'pol',
|
||||||
|
'pus': 'pus',
|
||||||
|
'por': 'por',
|
||||||
|
'que': 'que',
|
||||||
|
'roh': 'roh',
|
||||||
|
'run': 'run',
|
||||||
|
'rum': 'rum',
|
||||||
|
'ron': 'ron',
|
||||||
|
'rus': 'rus',
|
||||||
|
'kin': 'kin',
|
||||||
|
'san': 'san',
|
||||||
|
'srd': 'srd',
|
||||||
|
'snd': 'snd',
|
||||||
|
'sme': 'sme',
|
||||||
|
'sag': 'sag',
|
||||||
|
'slo': 'slo',
|
||||||
|
'sin': 'sin',
|
||||||
|
'slk': 'slk',
|
||||||
|
'slv': 'slv',
|
||||||
|
'smo': 'smo',
|
||||||
|
'sna': 'sna',
|
||||||
|
'som': 'som',
|
||||||
|
'alb': 'alb',
|
||||||
|
'sqi': 'sqi',
|
||||||
|
'srp': 'srp',
|
||||||
|
'ssw': 'ssw',
|
||||||
|
'sot': 'sot',
|
||||||
|
'sun': 'sun',
|
||||||
|
'swe': 'swe',
|
||||||
|
'swa': 'swa',
|
||||||
|
'tam': 'tam',
|
||||||
|
'tel': 'tel',
|
||||||
|
'tgk': 'tgk',
|
||||||
|
'tha': 'tha',
|
||||||
|
'tir': 'tir',
|
||||||
|
'tuk': 'tuk',
|
||||||
|
'tgl': 'tgl',
|
||||||
|
'tsn': 'tsn',
|
||||||
|
'ton': 'ton',
|
||||||
|
'tur': 'tur',
|
||||||
|
'tso': 'tso',
|
||||||
|
'tat': 'tat',
|
||||||
|
'twi': 'twi',
|
||||||
|
'tah': 'tah',
|
||||||
|
'uig': 'uig',
|
||||||
|
'ukr': 'ukr',
|
||||||
|
'urd': 'urd',
|
||||||
|
'uzb': 'uzb',
|
||||||
|
'ven': 'ven',
|
||||||
|
'vie': 'vie',
|
||||||
|
'vol': 'vol',
|
||||||
|
'wln': 'wln',
|
||||||
|
'wol': 'wol',
|
||||||
|
'xho': 'xho',
|
||||||
|
'yid': 'yid',
|
||||||
|
'yor': 'yor',
|
||||||
|
'zha': 'zha',
|
||||||
|
'chi': 'chi',
|
||||||
|
'zho': 'zho',
|
||||||
|
'zul': 'zul'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const learner_responses = {
|
||||||
|
'true-false': {
|
||||||
|
format: '^true$|^false$',
|
||||||
|
max: 1,
|
||||||
|
delimiter: '',
|
||||||
|
unique: false
|
||||||
|
},
|
||||||
|
'choice': {
|
||||||
|
format: scorm2004_regex.CMIShortIdentifier,
|
||||||
|
max: 36,
|
||||||
|
delimiter: '[,]',
|
||||||
|
unique: true
|
||||||
|
},
|
||||||
|
'fill-in': {
|
||||||
|
format: scorm2004_regex.CMILangString250,
|
||||||
|
max: 10,
|
||||||
|
delimiter: '[,]',
|
||||||
|
unique: false
|
||||||
|
},
|
||||||
|
'long-fill-in': {
|
||||||
|
format: scorm2004_regex.CMILangString4000,
|
||||||
|
max: 1,
|
||||||
|
delimiter: '',
|
||||||
|
unique: false
|
||||||
|
},
|
||||||
|
'matching': {
|
||||||
|
format: scorm2004_regex.CMIShortIdentifier,
|
||||||
|
format2: scorm2004_regex.CMIShortIdentifier,
|
||||||
|
max: 36,
|
||||||
|
delimiter: '[,]',
|
||||||
|
delimiter2: '[.]',
|
||||||
|
unique: false
|
||||||
|
},
|
||||||
|
'performance': {
|
||||||
|
format: '^$|' + scorm2004_regex.CMIShortIdentifier,
|
||||||
|
format2: scorm2004_regex.CMIDecimal + '|^$|' + scorm2004_regex.CMIShortIdentifier,
|
||||||
|
max: 250,
|
||||||
|
delimiter: '[,]',
|
||||||
|
delimiter2: '[.]',
|
||||||
|
unique: false
|
||||||
|
},
|
||||||
|
'sequencing': {
|
||||||
|
format: scorm2004_regex.CMIShortIdentifier,
|
||||||
|
max: 36,
|
||||||
|
delimiter: '[,]',
|
||||||
|
unique: false
|
||||||
|
},
|
||||||
|
'likert': {
|
||||||
|
format: scorm2004_regex.CMIShortIdentifier,
|
||||||
|
max: 1,
|
||||||
|
delimiter: '',
|
||||||
|
unique: false
|
||||||
|
},
|
||||||
|
'numeric': {
|
||||||
|
format: scorm2004_regex.CMIDecimal,
|
||||||
|
max: 1,
|
||||||
|
delimiter: '',
|
||||||
|
unique: false
|
||||||
|
},
|
||||||
|
'other': {
|
||||||
|
format: scorm2004_regex.CMIString4000,
|
||||||
|
max: 1,
|
||||||
|
delimiter: '',
|
||||||
|
unique: false
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const correct_responses = {
|
||||||
|
'true-false': {
|
||||||
|
max: 1,
|
||||||
|
delimiter: '',
|
||||||
|
unique: false,
|
||||||
|
duplicate: false,
|
||||||
|
format: '^true$|^false$',
|
||||||
|
limit: 1
|
||||||
|
},
|
||||||
|
'choice': {
|
||||||
|
max: 36,
|
||||||
|
delimiter: '[,]',
|
||||||
|
unique: true,
|
||||||
|
duplicate: false,
|
||||||
|
format: scorm2004_regex.CMIShortIdentifier
|
||||||
|
},
|
||||||
|
'fill-in': {
|
||||||
|
max: 10,
|
||||||
|
delimiter: '[,]',
|
||||||
|
unique: false,
|
||||||
|
duplicate: false,
|
||||||
|
format: scorm2004_regex.CMILangString250cr
|
||||||
|
},
|
||||||
|
'long-fill-in': {
|
||||||
|
max: 1,
|
||||||
|
delimiter: '',
|
||||||
|
unique: false,
|
||||||
|
duplicate: true,
|
||||||
|
format: scorm2004_regex.CMILangString4000
|
||||||
|
},
|
||||||
|
'matching': {
|
||||||
|
max: 36,
|
||||||
|
delimiter: '[,]',
|
||||||
|
delimiter2: '[.]',
|
||||||
|
unique: false,
|
||||||
|
duplicate: false,
|
||||||
|
format: scorm2004_regex.CMIShortIdentifier,
|
||||||
|
format2: scorm2004_regex.CMIShortIdentifier
|
||||||
|
},
|
||||||
|
'performance': {
|
||||||
|
max: 250,
|
||||||
|
delimiter: '[,]',
|
||||||
|
delimiter2: '[.]',
|
||||||
|
unique: false,
|
||||||
|
duplicate: false,
|
||||||
|
format: '^$|' + scorm2004_regex.CMIShortIdentifier,
|
||||||
|
format2: scorm2004_regex.CMIDecimal + '|^$|' + scorm2004_regex.CMIShortIdentifier
|
||||||
|
},
|
||||||
|
'sequencing': {
|
||||||
|
max: 36,
|
||||||
|
delimiter: '[,]',
|
||||||
|
unique: false,
|
||||||
|
duplicate: false,
|
||||||
|
format: scorm2004_regex.CMIShortIdentifier
|
||||||
|
},
|
||||||
|
'likert': {
|
||||||
|
max: 1,
|
||||||
|
delimiter: '',
|
||||||
|
unique: false,
|
||||||
|
duplicate: false,
|
||||||
|
format: scorm2004_regex.CMIShortIdentifier,
|
||||||
|
limit: 1
|
||||||
|
},
|
||||||
|
'numeric': {
|
||||||
|
max: 2,
|
||||||
|
delimiter: '[:]',
|
||||||
|
unique: false,
|
||||||
|
duplicate: false,
|
||||||
|
format: scorm2004_regex.CMIDecimal,
|
||||||
|
limit: 1
|
||||||
|
},
|
||||||
|
'other': {
|
||||||
|
max: 1,
|
||||||
|
delimiter: '',
|
||||||
|
unique: false,
|
||||||
|
duplicate: false,
|
||||||
|
format: scorm2004_regex.CMIString4000,
|
||||||
|
limit: 1
|
||||||
|
}
|
||||||
|
};
|
||||||
76
src/regex.js
Normal file
76
src/regex.js
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
// @flow
|
||||||
|
|
||||||
|
export const scorm12_regex = {
|
||||||
|
CMIString256: '^.{0,255}$',
|
||||||
|
CMIString4096: '^.{0,4096}$',
|
||||||
|
CMITime: '^([0-2]{1}[0-9]{1}):([0-5]{1}[0-9]{1}):([0-5]{1}[0-9]{1})(\.[0-9]{1,6})?$',
|
||||||
|
CMITimespan: '^([0-9]{2,4}):([0-9]{2}):([0-9]{2})(\.[0-9]{1,2})?$',
|
||||||
|
CMIInteger: '^\\d+$',
|
||||||
|
CMISInteger: '^-?([0-9]+)$',
|
||||||
|
CMIDecimal: '^-?([0-9]{0,3})(\.[0-9]*)?$',
|
||||||
|
CMIIdentifier: '^[\\u0021-\\u007E]{0,255}$',
|
||||||
|
CMIFeedback: '^.{0,255}$', // This must be redefined
|
||||||
|
CMIIndex: '[._](\\d+).',
|
||||||
|
|
||||||
|
// Vocabulary Data Type Definition
|
||||||
|
CMIStatus: '^passed$|^completed$|^failed$|^incomplete$|^browsed$',
|
||||||
|
CMIStatus2: '^passed$|^completed$|^failed$|^incomplete$|^browsed$|^not attempted$',
|
||||||
|
CMIExit: '^time-out$|^suspend$|^logout$|^$',
|
||||||
|
CMIType: '^true-false$|^choice$|^fill-in$|^matching$|^performance$|^sequencing$|^likert$|^numeric$',
|
||||||
|
CMIResult: '^correct$|^wrong$|^unanticipated$|^neutral$|^([0-9]{0,3})?(\.[0-9]*)?$',
|
||||||
|
NAVEvent: '^previous$|^continue$',
|
||||||
|
|
||||||
|
// Data ranges
|
||||||
|
score_range: '0#100',
|
||||||
|
audio_range: '-1#100',
|
||||||
|
speed_range: '-100#100',
|
||||||
|
weighting_range: '-100#100',
|
||||||
|
text_range: '-1#1',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const aicc_regex = {
|
||||||
|
...scorm12_regex, ...{
|
||||||
|
CMIIdentifier: '^\\w{1,255}$',
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const scorm2004_regex = {
|
||||||
|
CMIString200: '^[\\u0000-\\uFFFF]{0,200}$',
|
||||||
|
CMIString250: '^[\\u0000-\\uFFFF]{0,250}$',
|
||||||
|
CMIString1000: '^[\\u0000-\\uFFFF]{0,1000}$',
|
||||||
|
CMIString4000: '^[\\u0000-\\uFFFF]{0,4000}$',
|
||||||
|
CMIString64000: '^[\\u0000-\\uFFFF]{0,64000}$',
|
||||||
|
CMILang: '^([a-zA-Z]{2,3}|i|x)(\-[a-zA-Z0-9\-]{2,8})?$|^$',
|
||||||
|
CMILangString250: '^(\{lang=([a-zA-Z]{2,3}|i|x)(\-[a-zA-Z0-9\-]{2,8})?\})?([^\{].{0,250}$)?',
|
||||||
|
CMILangcr: '^((\{lang=([a-zA-Z]{2,3}|i|x)?(\-[a-zA-Z0-9\-]{2,8})?\}))(.*?)$',
|
||||||
|
CMILangString250cr: '^((\{lang=([a-zA-Z]{2,3}|i|x)?(\-[a-zA-Z0-9\-]{2,8})?\})?(.{0,250})?)?$',
|
||||||
|
CMILangString4000: '^(\{lang=([a-zA-Z]{2,3}|i|x)(\-[a-zA-Z0-9\-]{2,8})?\})?([^\{].{0,4000}$)?',
|
||||||
|
CMITime: '^(19[7-9]{1}[0-9]{1}|20[0-2]{1}[0-9]{1}|203[0-8]{1})((-(0[1-9]{1}|1[0-2]{1}))((-(0[1-9]{1}|[1-2]{1}[0-9]{1}|3[0-1]{1}))(T([0-1]{1}[0-9]{1}|2[0-3]{1})((:[0-5]{1}[0-9]{1})((:[0-5]{1}[0-9]{1})((\\.[0-9]{1,2})((Z|([+|-]([0-1]{1}[0-9]{1}|2[0-3]{1})))(:[0-5]{1}[0-9]{1})?)?)?)?)?)?)?)?$',
|
||||||
|
CMITimespan: '^P(?:([.,\\d]+)Y)?(?:([.,\\d]+)M)?(?:([.,\\d]+)W)?(?:([.,\\d]+)D)?(?:T?(?:([.,\\d]+)H)?(?:([.,\\d]+)M)?(?:([.,\\d]+)S)?)?$',
|
||||||
|
CMIInteger: '^\\d+$',
|
||||||
|
CMISInteger: '^-?([0-9]+)$',
|
||||||
|
CMIDecimal: '^-?([0-9]{1,5})(\\.[0-9]{1,18})?$',
|
||||||
|
CMIIdentifier: '^\\S{1,250}[a-zA-Z0-9]$',
|
||||||
|
CMIShortIdentifier: '^[\\w\.]{1,250}$',
|
||||||
|
CMILongIdentifier: '^(?:(?!urn:)\\S{1,4000}|urn:[A-Za-z0-9-]{1,31}:\\S{1,4000})$',
|
||||||
|
CMIFeedback: '^.*$', // This must be redefined
|
||||||
|
CMIIndex: '[._](\\d+).',
|
||||||
|
CMIIndexStore: '.N(\\d+).',
|
||||||
|
|
||||||
|
// Vocabulary Data Type Definition
|
||||||
|
CMICStatus: '^completed$|^incomplete$|^not attempted$|^unknown$',
|
||||||
|
CMISStatus: '^passed$|^failed$|^unknown$',
|
||||||
|
CMIExit: '^time-out$|^suspend$|^logout$|^normal$|^$',
|
||||||
|
CMIType: '^true-false$|^choice$|^(long-)?fill-in$|^matching$|^performance$|^sequencing$|^likert$|^numeric$|^other$',
|
||||||
|
CMIResult: '^correct$|^incorrect$|^unanticipated$|^neutral$|^-?([0-9]{1,4})(\\.[0-9]{1,18})?$',
|
||||||
|
NAVEvent: '^previous$|^continue$|^exit$|^exitAll$|^abandon$|^abandonAll$|^suspendAll$|^\{target=\\S{0,200}[a-zA-Z0-9]\}choice|jump$',
|
||||||
|
NAVBoolean: '^unknown$|^true$|^false$',
|
||||||
|
NAVTarget: '^previous$|^continue$|^choice.{target=\\S{0,200}[a-zA-Z0-9]}$',
|
||||||
|
|
||||||
|
// Data ranges
|
||||||
|
scaled_range: '-1#1',
|
||||||
|
audio_range: '0#*',
|
||||||
|
speed_range: '0#*',
|
||||||
|
text_range: '-1#1',
|
||||||
|
progress_range: '0#1',
|
||||||
|
};
|
||||||
116
src/utilities.js
Normal file
116
src/utilities.js
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) Noverant, Inc - All Rights Reserved Unauthorized copying of this file, via any
|
||||||
|
* medium is strictly prohibited Proprietary and confidential
|
||||||
|
*/
|
||||||
|
|
||||||
|
// @flow
|
||||||
|
export const SECONDS_PER_SECOND = 1;
|
||||||
|
export const SECONDS_PER_MINUTE = 60;
|
||||||
|
export const SECONDS_PER_HOUR = 60 * SECONDS_PER_MINUTE;
|
||||||
|
export const SECONDS_PER_DAY = 24 * SECONDS_PER_HOUR;
|
||||||
|
|
||||||
|
const designations = [
|
||||||
|
['D', SECONDS_PER_DAY],
|
||||||
|
['H', SECONDS_PER_HOUR],
|
||||||
|
['M', SECONDS_PER_MINUTE],
|
||||||
|
['S', SECONDS_PER_SECOND],
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts a Number to a String of HH:MM:SS
|
||||||
|
*
|
||||||
|
* @param totalSeconds
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
export function getSecondsAsHHMMSS(totalSeconds: Number) {
|
||||||
|
// SCORM spec does not deal with negative durations, give zero back
|
||||||
|
if(!totalSeconds || totalSeconds <= 0) {
|
||||||
|
return '00:00:00';
|
||||||
|
}
|
||||||
|
|
||||||
|
let hours = Math.floor(totalSeconds / SECONDS_PER_HOUR);
|
||||||
|
|
||||||
|
let dateObj = new Date(totalSeconds * 1000);
|
||||||
|
let minutes = dateObj.getUTCMinutes();
|
||||||
|
let seconds = dateObj.getSeconds();
|
||||||
|
|
||||||
|
return hours.toString().padStart(2, '0') + ':' +
|
||||||
|
minutes.toString().padStart(2, '0') + ':' +
|
||||||
|
seconds.toString().padStart(2, '0');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate the number of seconds from ISO 8601 Duration
|
||||||
|
*
|
||||||
|
* @param seconds
|
||||||
|
* @returns {String}
|
||||||
|
*/
|
||||||
|
export function getSecondsAsISODuration(seconds: Number) {
|
||||||
|
// SCORM spec does not deal with negative durations, give zero back
|
||||||
|
if(!seconds || seconds <= 0) {
|
||||||
|
return 'P0S';
|
||||||
|
}
|
||||||
|
|
||||||
|
let duration = 'P';
|
||||||
|
let remainder = seconds;
|
||||||
|
|
||||||
|
designations.forEach(([sign, current_seconds]) => {
|
||||||
|
const value = Math.floor(remainder / current_seconds);
|
||||||
|
|
||||||
|
remainder = remainder % current_seconds;
|
||||||
|
|
||||||
|
if (value) {
|
||||||
|
duration += `${value}${sign}`;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return duration;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate the number of seconds from HH:MM:SS.DDDDDD
|
||||||
|
*
|
||||||
|
* @param timeString
|
||||||
|
* @param timeRegex
|
||||||
|
* @returns {number}
|
||||||
|
*/
|
||||||
|
export function getTimeAsSeconds(timeString: String, timeRegex: RegExp) {
|
||||||
|
if(!timeString || typeof timeString !== 'string' || !timeString.match(timeRegex)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
const parts = timeString.split(':');
|
||||||
|
const hours = Number(parts[0]);
|
||||||
|
const minutes = Number(parts[1]);
|
||||||
|
const seconds = Number(parts[2]);
|
||||||
|
return (hours * 3600) + (minutes * 60) + seconds;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate the number of seconds from ISO 8601 Duration
|
||||||
|
*
|
||||||
|
* @param duration
|
||||||
|
* @param durationRegex
|
||||||
|
* @returns {number}
|
||||||
|
*/
|
||||||
|
export function getDurationAsSeconds(duration: String, durationRegex: RegExp) {
|
||||||
|
if(!duration || !duration.match(durationRegex)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
let [,years,months,,days,hours,minutes,seconds] = new RegExp(durationRegex).exec(duration) || [];
|
||||||
|
|
||||||
|
let now = new Date();
|
||||||
|
let anchor = new Date(now);
|
||||||
|
anchor.setFullYear(anchor.getFullYear() + Number(years || 0));
|
||||||
|
anchor.setMonth(anchor.getMonth() + Number(months || 0));
|
||||||
|
anchor.setDate(anchor.getDate() + Number(days || 0));
|
||||||
|
anchor.setHours(anchor.getHours() + Number(hours || 0));
|
||||||
|
anchor.setMinutes(anchor.getMinutes() + Number(minutes || 0));
|
||||||
|
anchor.setSeconds(anchor.getSeconds() + Number(seconds || 0));
|
||||||
|
if(seconds) {
|
||||||
|
let milliseconds = Number(Number(seconds) % 1).toFixed(6) * 1000.0;
|
||||||
|
anchor.setMilliseconds(anchor.getMilliseconds() + milliseconds);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (anchor - now) / 1000.0;
|
||||||
|
}
|
||||||
282
test/Scorm12API.spec.js
Normal file
282
test/Scorm12API.spec.js
Normal file
@@ -0,0 +1,282 @@
|
|||||||
|
import { expect, assert } from 'chai';
|
||||||
|
import {describe, it, before, beforeEach, after, afterEach, } from "mocha";
|
||||||
|
import Scorm12API from "../src/Scorm12API";
|
||||||
|
import {scorm12_constants, scorm12_error_codes} from "../src/constants";
|
||||||
|
|
||||||
|
let API;
|
||||||
|
|
||||||
|
const checkFieldConstraintSize = (fieldName: String, limit: Number, expectedValue: String = '') => {
|
||||||
|
describe(`Field: ${fieldName}`, () => {
|
||||||
|
it(`Should be able to read from ${fieldName}`, () => {
|
||||||
|
expect(eval(`API.${fieldName}`)).to.equal(expectedValue);
|
||||||
|
});
|
||||||
|
|
||||||
|
it(`Should be able to write upto ${limit} characters to ${fieldName}`, () => {
|
||||||
|
eval(`API.${fieldName} = 'x'.repeat(${limit})`);
|
||||||
|
expect(0).to.equal(API.lastErrorCode);
|
||||||
|
});
|
||||||
|
|
||||||
|
it(`Should fail to write more than ${limit} characters to ${fieldName}`, () => {
|
||||||
|
eval(`API.${fieldName} = 'x'.repeat(${limit + 1})`);
|
||||||
|
expect(scorm12_error_codes.TYPE_MISMATCH + '').to.equal(API.lastErrorCode);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const checkInvalidSet = (fieldName: String, expectedValue: String = '') => {
|
||||||
|
describe(`Field: ${fieldName}`, () => {
|
||||||
|
it(`Should be able to read from ${fieldName}`, () => {
|
||||||
|
expect(eval(`API.${fieldName}`)).to.equal(expectedValue);
|
||||||
|
});
|
||||||
|
|
||||||
|
it(`Should fail to write to ${fieldName}`, () => {
|
||||||
|
eval(`API.${fieldName} = 'xxx'`);
|
||||||
|
expect(API.lastErrorCode).to.equal(scorm12_error_codes.INVALID_SET_VALUE + '');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const checkReadOnly = (fieldName: String, expectedValue: String = '') => {
|
||||||
|
describe(`Field: ${fieldName}`, () => {
|
||||||
|
it(`Should be able to read from ${fieldName}`, () => {
|
||||||
|
expect(eval(`API.${fieldName}`)).to.equal(expectedValue);
|
||||||
|
});
|
||||||
|
|
||||||
|
it(`Should fail to write to ${fieldName}`, () => {
|
||||||
|
eval(`API.${fieldName} = 'xxx'`);
|
||||||
|
expect(API.lastErrorCode).to.equal(scorm12_error_codes.READ_ONLY_ELEMENT + '');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const checkRead = (fieldName: String, expectedValue: String = '') => {
|
||||||
|
describe(`Field: ${fieldName}`, () => {
|
||||||
|
it(`Should be able to read from ${fieldName}`, () => {
|
||||||
|
expect(eval(`API.${fieldName}`)).to.equal(expectedValue);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const checkWriteOnly = (fieldName: String, valueToTest: String = 'xxx') => {
|
||||||
|
describe(`Field: ${fieldName}`, () => {
|
||||||
|
it(`Should fail to read from ${fieldName}`, () => {
|
||||||
|
eval(`API.${fieldName}`);
|
||||||
|
expect(API.lastErrorCode).to.equal(scorm12_error_codes.WRITE_ONLY_ELEMENT + '');
|
||||||
|
});
|
||||||
|
|
||||||
|
it(`Should successfully write to ${fieldName}`, () => {
|
||||||
|
eval(`API.${fieldName} = '${valueToTest}'`);
|
||||||
|
expect(API.lastErrorCode).to.equal(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const checkWrite = (fieldName: String, valueToTest: String = 'xxx') => {
|
||||||
|
describe(`Field: ${fieldName}`, () => {
|
||||||
|
it(`Should successfully write to ${fieldName}`, () => {
|
||||||
|
eval(`API.${fieldName} = '${valueToTest}'`);
|
||||||
|
expect(API.lastErrorCode).to.equal(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const checkValidValues = (fieldName: String, expectedError: Number, validValues: Array, invalidValues: Array) => {
|
||||||
|
describe(`Field: ${fieldName}`, () => {
|
||||||
|
for(let idx in validValues) {
|
||||||
|
it(`Should successfully write '${validValues[idx]}' to ${fieldName}`, () => {
|
||||||
|
eval(`API.${fieldName} = '${validValues[idx]}'`);
|
||||||
|
expect(API.lastErrorCode).to.equal(0);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
for(let idx in invalidValues) {
|
||||||
|
it(`Should fail to write '${invalidValues[idx]}' to ${fieldName}`, () => {
|
||||||
|
eval(`API.${fieldName} = '${invalidValues[idx]}'`);
|
||||||
|
expect(API.lastErrorCode).to.equal(expectedError + '');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('SCORM 1.2 API Tests', () => {
|
||||||
|
describe('CMI Spec Tests', () => {
|
||||||
|
describe('Post-LMSInitialize Tests', () => {
|
||||||
|
beforeEach('Create the API object', () => {
|
||||||
|
API = new Scorm12API();
|
||||||
|
API.LMSInitialize();
|
||||||
|
});
|
||||||
|
afterEach('Destroy API object', () => {
|
||||||
|
API = null;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('LMSInitialize should create CMI object', () => {
|
||||||
|
assert(API.cmi !== undefined, 'CMI object is created');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Exporting CMI to JSON produces proper Object', () => {
|
||||||
|
expect(
|
||||||
|
JSON.parse(API.renderCMIToJSON())
|
||||||
|
).to.hasOwnProperty('core')
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base CMI Properties
|
||||||
|
*/
|
||||||
|
checkInvalidSet('cmi._version', '3.4');
|
||||||
|
checkInvalidSet('cmi._children', scorm12_constants.cmi_children);
|
||||||
|
checkFieldConstraintSize('cmi.suspend_data', 4096);
|
||||||
|
checkReadOnly('cmi.launch_data');
|
||||||
|
checkFieldConstraintSize('cmi.comments', 4096);
|
||||||
|
checkReadOnly('cmi.comments_from_lms');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* cmi.core Properties
|
||||||
|
*/
|
||||||
|
checkInvalidSet('cmi.core._children', scorm12_constants.core_children);
|
||||||
|
checkReadOnly('cmi.core.student_id');
|
||||||
|
checkReadOnly('cmi.core.student_name');
|
||||||
|
checkFieldConstraintSize('cmi.core.lesson_location', 255);
|
||||||
|
checkReadOnly('cmi.core.credit');
|
||||||
|
checkRead('cmi.core.lesson_status');
|
||||||
|
checkValidValues('cmi.core.lesson_status', scorm12_error_codes.TYPE_MISMATCH, [
|
||||||
|
'passed',
|
||||||
|
'completed',
|
||||||
|
'failed',
|
||||||
|
'incomplete',
|
||||||
|
'browsed'
|
||||||
|
], [
|
||||||
|
'Passed',
|
||||||
|
'P',
|
||||||
|
'F',
|
||||||
|
'p',
|
||||||
|
'true',
|
||||||
|
'false',
|
||||||
|
'complete'
|
||||||
|
]);
|
||||||
|
checkReadOnly('cmi.core.entry');
|
||||||
|
checkReadOnly('cmi.core.total_time');
|
||||||
|
checkReadOnly('cmi.core.lesson_mode', 'normal');
|
||||||
|
checkWrite('cmi.core.exit', 'suspend');
|
||||||
|
checkValidValues('cmi.core.exit', scorm12_error_codes.TYPE_MISMATCH, [
|
||||||
|
'time-out',
|
||||||
|
'suspend',
|
||||||
|
'logout',
|
||||||
|
], [
|
||||||
|
'complete',
|
||||||
|
'exit'
|
||||||
|
]);
|
||||||
|
checkWriteOnly('cmi.core.session_time', '00:00:00');
|
||||||
|
checkValidValues('cmi.core.session_time', scorm12_error_codes.TYPE_MISMATCH, [
|
||||||
|
'10:06:57',
|
||||||
|
'00:00:01.56',
|
||||||
|
'23:59:59',
|
||||||
|
'47:59:59',
|
||||||
|
], [
|
||||||
|
'06:5:13',
|
||||||
|
'23:59:59.123',
|
||||||
|
'P1DT23H59M59S'
|
||||||
|
]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* cmi.core.score Properties
|
||||||
|
*/
|
||||||
|
checkInvalidSet('cmi.core.score._children', scorm12_constants.score_children);
|
||||||
|
checkValidValues('cmi.core.score.raw', scorm12_error_codes.VALUE_OUT_OF_RANGE, [
|
||||||
|
'0',
|
||||||
|
'25.1',
|
||||||
|
'50.5',
|
||||||
|
'75',
|
||||||
|
'100',
|
||||||
|
], [
|
||||||
|
'-1',
|
||||||
|
'101'
|
||||||
|
]);
|
||||||
|
checkValidValues('cmi.core.score.min', scorm12_error_codes.VALUE_OUT_OF_RANGE, [
|
||||||
|
'0',
|
||||||
|
'25.1',
|
||||||
|
'50.5',
|
||||||
|
'75',
|
||||||
|
'100',
|
||||||
|
], [
|
||||||
|
'-1',
|
||||||
|
'101'
|
||||||
|
]);
|
||||||
|
checkValidValues('cmi.core.score.max', scorm12_error_codes.VALUE_OUT_OF_RANGE, [
|
||||||
|
'0',
|
||||||
|
'25.1',
|
||||||
|
'50.5',
|
||||||
|
'75',
|
||||||
|
'100',
|
||||||
|
], [
|
||||||
|
'-1',
|
||||||
|
'101'
|
||||||
|
]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* cmi.objectives Properties
|
||||||
|
*/
|
||||||
|
checkInvalidSet('cmi.objectives._children', scorm12_constants.objectives_children);
|
||||||
|
checkInvalidSet('cmi.objectives._count', 0);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* cmi.student_data Properties
|
||||||
|
*/
|
||||||
|
checkInvalidSet('cmi.student_data._children', scorm12_constants.student_data_children);
|
||||||
|
checkReadOnly('cmi.student_data.mastery_score');
|
||||||
|
checkReadOnly('cmi.student_data.max_time_allowed');
|
||||||
|
checkReadOnly('cmi.student_data.time_limit_action');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* cmi.student_preference Properties
|
||||||
|
*/
|
||||||
|
checkInvalidSet('cmi.student_preference._children', scorm12_constants.student_preference_children);
|
||||||
|
checkValidValues('cmi.student_preference.audio', scorm12_error_codes.TYPE_MISMATCH, [
|
||||||
|
'1',
|
||||||
|
'-1',
|
||||||
|
'50',
|
||||||
|
'100',
|
||||||
|
], [
|
||||||
|
'invalid',
|
||||||
|
'a100'
|
||||||
|
]);
|
||||||
|
checkValidValues('cmi.student_preference.audio', scorm12_error_codes.VALUE_OUT_OF_RANGE, [], [
|
||||||
|
'101',
|
||||||
|
'5000000',
|
||||||
|
'-500'
|
||||||
|
]);
|
||||||
|
checkFieldConstraintSize('cmi.student_preference.language', 255);
|
||||||
|
checkValidValues('cmi.student_preference.speed', scorm12_error_codes.TYPE_MISMATCH, [
|
||||||
|
'1',
|
||||||
|
'-100',
|
||||||
|
'50',
|
||||||
|
'100',
|
||||||
|
], [
|
||||||
|
'invalid',
|
||||||
|
'a100'
|
||||||
|
]);
|
||||||
|
checkValidValues('cmi.student_preference.speed', scorm12_error_codes.VALUE_OUT_OF_RANGE, [], [
|
||||||
|
'101',
|
||||||
|
'-101',
|
||||||
|
'5000000',
|
||||||
|
'-500'
|
||||||
|
]);
|
||||||
|
checkValidValues('cmi.student_preference.text', scorm12_error_codes.TYPE_MISMATCH, [
|
||||||
|
'1',
|
||||||
|
'-1'
|
||||||
|
], [
|
||||||
|
'invalid',
|
||||||
|
'a100'
|
||||||
|
]);
|
||||||
|
checkValidValues('cmi.student_preference.text', scorm12_error_codes.VALUE_OUT_OF_RANGE, [], [
|
||||||
|
'2',
|
||||||
|
'-2'
|
||||||
|
]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* cmi.interactions Properties
|
||||||
|
*/
|
||||||
|
checkInvalidSet('cmi.interactions._children', scorm12_constants.interactions_children);
|
||||||
|
checkInvalidSet('cmi.interactions._count', 0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
220
test/utilities.spec.js
Normal file
220
test/utilities.spec.js
Normal file
@@ -0,0 +1,220 @@
|
|||||||
|
import { expect } from 'chai';
|
||||||
|
import {describe, it} from "mocha";
|
||||||
|
import * as Utilities from '../src/utilities';
|
||||||
|
import {scorm12_regex, scorm2004_regex} from "../src/regex";
|
||||||
|
|
||||||
|
describe('Utility Tests', () => {
|
||||||
|
describe('function getSecondsAsHHMMSS()', () => {
|
||||||
|
it('10 returns 00:00:10', () => {
|
||||||
|
expect(
|
||||||
|
Utilities.getSecondsAsHHMMSS(10)
|
||||||
|
).to.equal('00:00:10');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('60 returns 00:01:00', () => {
|
||||||
|
expect(
|
||||||
|
Utilities.getSecondsAsHHMMSS(60)
|
||||||
|
).to.equal('00:01:00');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('3600 returns 01:00:00', () => {
|
||||||
|
expect(
|
||||||
|
Utilities.getSecondsAsHHMMSS(3600)
|
||||||
|
).to.equal('01:00:00');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('70 returns 00:01:10', () => {
|
||||||
|
expect(
|
||||||
|
Utilities.getSecondsAsHHMMSS(70)
|
||||||
|
).to.equal('00:01:10');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('3670 returns 01:01:10', () => {
|
||||||
|
expect(
|
||||||
|
Utilities.getSecondsAsHHMMSS(3670)
|
||||||
|
).to.equal('01:01:10');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('90000 returns 25:00:00, check for hours greater than 24', () => {
|
||||||
|
expect(
|
||||||
|
Utilities.getSecondsAsHHMMSS(90000)
|
||||||
|
).to.equal('25:00:00');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('-3600 returns 00:00:00, negative time not allowed in SCORM session times', () => {
|
||||||
|
expect(
|
||||||
|
Utilities.getSecondsAsHHMMSS(-3600)
|
||||||
|
).to.equal('00:00:00');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Empty seconds returns 00:00:00', () => {
|
||||||
|
expect(
|
||||||
|
Utilities.getSecondsAsHHMMSS(null)
|
||||||
|
).to.equal('00:00:00');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('function getSecondsAsISODuration()', () => {
|
||||||
|
it('10 returns P10S', () => {
|
||||||
|
expect(
|
||||||
|
Utilities.getSecondsAsISODuration(10)
|
||||||
|
).to.equal('P10S');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('60 returns P1M', () => {
|
||||||
|
expect(
|
||||||
|
Utilities.getSecondsAsISODuration(60)
|
||||||
|
).to.equal('P1M');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('3600 returns P1H', () => {
|
||||||
|
expect(
|
||||||
|
Utilities.getSecondsAsISODuration(3600)
|
||||||
|
).to.equal('P1H');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('70 returns P1M10S', () => {
|
||||||
|
expect(
|
||||||
|
Utilities.getSecondsAsISODuration(70)
|
||||||
|
).to.equal('P1M10S');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('3670 returns P1H1M10S', () => {
|
||||||
|
expect(
|
||||||
|
Utilities.getSecondsAsISODuration(3670)
|
||||||
|
).to.equal('P1H1M10S');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('90000 returns P1D1H', () => {
|
||||||
|
expect(
|
||||||
|
Utilities.getSecondsAsISODuration(90000)
|
||||||
|
).to.equal('P1D1H');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('90061 returns P1D1H1M1S', () => {
|
||||||
|
expect(
|
||||||
|
Utilities.getSecondsAsISODuration(90061)
|
||||||
|
).to.equal('P1D1H1M1S');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('-3600 returns P0S, negative time not allowed in SCORM session times', () => {
|
||||||
|
expect(
|
||||||
|
Utilities.getSecondsAsISODuration(-3600)
|
||||||
|
).to.equal('P0S');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Empty seconds returns P0S', () => {
|
||||||
|
expect(
|
||||||
|
Utilities.getSecondsAsISODuration(null)
|
||||||
|
).to.equal('P0S');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('function getTimeAsSeconds()', () => {
|
||||||
|
it('00:00:10 returns 10', () => {
|
||||||
|
expect(
|
||||||
|
Utilities.getTimeAsSeconds('00:00:10', scorm12_regex.CMITimespan)
|
||||||
|
).to.equal(10);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('00:01:10 returns 70', () => {
|
||||||
|
expect(
|
||||||
|
Utilities.getTimeAsSeconds('00:01:10', scorm12_regex.CMITimespan)
|
||||||
|
).to.equal(70);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('01:01:10 returns 3670', () => {
|
||||||
|
expect(
|
||||||
|
Utilities.getTimeAsSeconds('01:01:10', scorm12_regex.CMITimespan)
|
||||||
|
).to.equal(3670);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('100:00:00 returns 3670', () => {
|
||||||
|
expect(
|
||||||
|
Utilities.getTimeAsSeconds('100:00:00', scorm12_regex.CMITimespan)
|
||||||
|
).to.equal(360000);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('-01:00:00 returns 0', () => {
|
||||||
|
expect(
|
||||||
|
Utilities.getTimeAsSeconds('-01:00:00', scorm12_regex.CMITimespan)
|
||||||
|
).to.equal(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Number value returns 0', () => {
|
||||||
|
expect(
|
||||||
|
Utilities.getTimeAsSeconds(999, scorm12_regex.CMITimespan)
|
||||||
|
).to.equal(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('boolean value returns 0', () => {
|
||||||
|
expect(
|
||||||
|
Utilities.getTimeAsSeconds(true, scorm12_regex.CMITimespan)
|
||||||
|
).to.equal(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Empty value returns 0', () => {
|
||||||
|
expect(
|
||||||
|
Utilities.getTimeAsSeconds(null, scorm12_regex.CMITimespan)
|
||||||
|
).to.equal(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('function getDurationAsSeconds()', () => {
|
||||||
|
it('P0S returns 0', () => {
|
||||||
|
expect(
|
||||||
|
Utilities.getDurationAsSeconds('P0S', scorm2004_regex.CMITimespan)
|
||||||
|
).to.equal(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('P70S returns 70', () => {
|
||||||
|
expect(
|
||||||
|
Utilities.getDurationAsSeconds('P70S', scorm2004_regex.CMITimespan)
|
||||||
|
).to.equal(70);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('PT1M10S returns 70', () => {
|
||||||
|
expect(
|
||||||
|
Utilities.getDurationAsSeconds('PT1M10S', scorm2004_regex.CMITimespan)
|
||||||
|
).to.equal(70);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('P1D returns 86400', () => {
|
||||||
|
expect(
|
||||||
|
Utilities.getDurationAsSeconds('P1D', scorm2004_regex.CMITimespan)
|
||||||
|
).to.equal(86400);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('P1M returns number of seconds for one month from now', () => {
|
||||||
|
const now = new Date();
|
||||||
|
let oneMonthFromNow = new Date(now);
|
||||||
|
oneMonthFromNow.setMonth(oneMonthFromNow.getMonth() + 1);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
Utilities.getDurationAsSeconds('P1M', scorm2004_regex.CMITimespan)
|
||||||
|
).to.equal((oneMonthFromNow - now) / 1000.0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('P1Y returns number of seconds for one year from now', () => {
|
||||||
|
const now = new Date();
|
||||||
|
let oneYearFromNow = new Date(now);
|
||||||
|
oneYearFromNow.setFullYear(oneYearFromNow.getFullYear() + 1);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
Utilities.getDurationAsSeconds('P1Y', scorm2004_regex.CMITimespan)
|
||||||
|
).to.equal((oneYearFromNow - now) / 1000.0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Invalid duration returns 0', () => {
|
||||||
|
expect(
|
||||||
|
Utilities.getDurationAsSeconds('T1M10S', scorm2004_regex.CMITimespan)
|
||||||
|
).to.equal(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Empty duration returns 0', () => {
|
||||||
|
expect(
|
||||||
|
Utilities.getDurationAsSeconds(null, scorm2004_regex.CMITimespan)
|
||||||
|
).to.equal(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user