From eae2a688f6d82f91c70b47f76c4d7544f1ddb300 Mon Sep 17 00:00:00 2001 From: Jonathan Putney Date: Sun, 10 Nov 2019 20:52:23 -0500 Subject: [PATCH] Disabling eslint in build for now --- .circleci/config.yml | 10 +- .eslintrc.js | 4 + package-lock.json | 58 +- package.json | 3 +- src/BaseAPI.js | 1313 +++++++++++++++++++++------------------- src/Scorm12API.js | 211 +++---- src/Scorm2004API.js | 699 +++++++++++---------- src/cmi/aicc_cmi.js | 296 ++++++--- src/cmi/scorm12_cmi.js | 723 +++++++++++++--------- src/utilities.js | 132 ++-- 10 files changed, 1927 insertions(+), 1522 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 6c6551a..f110a3d 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -49,11 +49,11 @@ jobs: when: always # Run eslint - - run: - name: eslint - command: | - ./node_modules/.bin/eslint ./ --format junit --output-file ./reports/eslint/eslint.xml - when: always + # - run: + # name: eslint + # command: | + # ./node_modules/.bin/eslint ./ --format junit --output-file ./reports/eslint/eslint.xml + # when: always # Run coverage report for Code Climate - run: diff --git a/.eslintrc.js b/.eslintrc.js index 2b056b8..2b236b8 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -4,6 +4,7 @@ module.exports = { browser: true, es6: true, }, + extends: ['eslint:recommended', 'google'], globals: { Atomics: 'readonly', SharedArrayBuffer: 'readonly', @@ -11,6 +12,7 @@ module.exports = { parserOptions: { sourceType: "module", allowImportExportEverywhere: false, + classPrivateMethods: true, ecmaFeatures: { globalReturn: false, }, @@ -19,5 +21,7 @@ module.exports = { }, }, rules: { + camelcase: 'off', + 'max-len': 'off', }, }; diff --git a/package-lock.json b/package-lock.json index 069ce95..0203cba 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1133,17 +1133,26 @@ "optional": true }, "babel-eslint": { - "version": "10.0.3", - "resolved": "https://registry.npmjs.org/babel-eslint/-/babel-eslint-10.0.3.tgz", - "integrity": "sha512-z3U7eMY6r/3f3/JB9mTsLjyxrv0Yb1zb8PCWCLpguxfCzBIZUwy23R1t/XKewP+8mEN2Ck8Dtr4q20z6ce6SoA==", + "version": "11.0.0-beta.0", + "resolved": "https://registry.npmjs.org/babel-eslint/-/babel-eslint-11.0.0-beta.0.tgz", + "integrity": "sha512-GJTX0XL22be/A5sFp1/4qQIOnZJ/KJ8YrA0aW07SZoDXTMLB1KQT0rYl4a9Y5DfJGPuVDVf0bMYyvLLXmXPpHw==", "dev": true, "requires": { - "@babel/code-frame": "^7.0.0", - "@babel/parser": "^7.0.0", - "@babel/traverse": "^7.0.0", - "@babel/types": "^7.0.0", + "eslint-scope": "3.7.1", "eslint-visitor-keys": "^1.0.0", - "resolve": "^1.12.0" + "semver": "^5.6.0" + }, + "dependencies": { + "eslint-scope": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-3.7.1.tgz", + "integrity": "sha1-PWPD7f2gLgbgGkUq2IyqzHzctug=", + "dev": true, + "requires": { + "esrecurse": "^4.1.0", + "estraverse": "^4.1.1" + } + } } }, "babel-plugin-dynamic-import-node": { @@ -1530,12 +1539,6 @@ "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", "dev": true }, - "confusing-browser-globals": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/confusing-browser-globals/-/confusing-browser-globals-1.0.9.tgz", - "integrity": "sha512-KbS1Y0jMtyPgIxjO7ZzMAuUpAKMt1SzCL9fsrKsX6b0zJPTaT0SiSPmewwVZg9UAO83HVIlEhZF84LIjZ0lmAw==", - "dev": true - }, "contains-path": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/contains-path/-/contains-path-0.1.0.tgz", @@ -1878,16 +1881,11 @@ } } }, - "eslint-config-airbnb-base": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/eslint-config-airbnb-base/-/eslint-config-airbnb-base-14.0.0.tgz", - "integrity": "sha512-2IDHobw97upExLmsebhtfoD3NAKhV4H0CJWP3Uprd/uk+cHuWYOczPVxQ8PxLFUAw7o3Th1RAU8u1DoUpr+cMA==", - "dev": true, - "requires": { - "confusing-browser-globals": "^1.0.7", - "object.assign": "^4.1.0", - "object.entries": "^1.1.0" - } + "eslint-config-google": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/eslint-config-google/-/eslint-config-google-0.14.0.tgz", + "integrity": "sha512-WsbX4WbjuMvTdeVL6+J3rK1RGhCTqjsFjX7UMSMgZiyxxaNLkoJENbrGExzERFeoTpGw3F3FypTiWAP9ZXzkEw==", + "dev": true }, "eslint-import-resolver-node": { "version": "0.3.2", @@ -4244,18 +4242,6 @@ "object-keys": "^1.0.11" } }, - "object.entries": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.0.tgz", - "integrity": "sha512-l+H6EQ8qzGRxbkHOd5I/aHRhHDKoQXQ8g0BYt4uSweQU1/J6dZUOyWh9a2Vky35YCKjzmgxOzta2hH6kf9HuXA==", - "dev": true, - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.12.0", - "function-bind": "^1.1.1", - "has": "^1.0.3" - } - }, "object.getownpropertydescriptors": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.0.3.tgz", diff --git a/package.json b/package.json index fe40d3a..06ce77a 100644 --- a/package.json +++ b/package.json @@ -15,9 +15,10 @@ "@babel/preset-env": "^7.7.1", "@babel/preset-flow": "^7.0.0", "@babel/register": "^7.7.0", - "babel-eslint": "^10.0.3", + "babel-eslint": "^11.0.0-beta.0", "chai": "^4.2.0", "eslint": "^6.6.0", + "eslint-config-google": "^0.14.0", "eslint-plugin-import": "^2.18.2", "mocha": "^6.2.2", "nyc": "^14.1.1" diff --git a/src/BaseAPI.js b/src/BaseAPI.js index 04b6498..d16add7 100644 --- a/src/BaseAPI.js +++ b/src/BaseAPI.js @@ -1,659 +1,728 @@ // @flow -import {CMIArray} from "./cmi/common"; +import {CMIArray} from './cmi/common'; 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 + 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, }; +let _self; + +/** + * Base API class for AICC, SCORM 1.2, and SCORM 2004. Should be considered + * abstract, and never initialized on it's own. + */ export default class BaseAPI { - #timeout; - #error_codes; - cmi; + #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 = []; + /** + * Constructor for Base API class. Sets some shared API fields, as well as + * sets up options for the API. + * @param {object} error_codes + */ + constructor(error_codes) { + _self = this; + _self.currentState = api_constants.STATE_NOT_INITIALIZED; + _self.apiLogLevel = api_constants.LOG_LEVEL_ERROR; + _self.lastErrorCode = 0; + _self.listenerArray = []; - this.#timeout = null; - this.#error_codes = error_codes; + _self.#timeout = null; + _self.#error_codes = error_codes; + } + + /** + * Initialize the API + * @param {string} callbackName + * @param {string} initializeMessage + * @param {string} terminationMessage + * @return {string} + */ + initialize( + callbackName: String, + initializeMessage?: String, + terminationMessage?: String) { + let returnValue = api_constants.SCORM_FALSE; + + if (_self.isInitialized()) { + _self.throwSCORMError(_self.#error_codes.INITIALIZED, initializeMessage); + } else if (_self.isTerminated()) { + _self.throwSCORMError(_self.#error_codes.TERMINATED, terminationMessage); + } else { + _self.currentState = api_constants.STATE_INITIALIZED; + _self.lastErrorCode = 0; + returnValue = api_constants.SCORM_TRUE; + _self.processListeners(callbackName); } - /** - * @returns {string} bool - */ - APIInitialize(callbackName: String, initializeMessage?: String, terminationMessage?: String) { - let returnValue = api_constants.SCORM_FALSE; + _self.apiLog(callbackName, null, 'returned: ' + returnValue, + api_constants.LOG_LEVEL_INFO); + _self.clearSCORMError(returnValue); - if (this.isInitialized()) { - this.throwSCORMError(this.#error_codes.INITIALIZED, initializeMessage); - } else if (this.isTerminated()) { - this.throwSCORMError(this.#error_codes.TERMINATED, terminationMessage); + return returnValue; + } + + /** + * Terminates the current run of the API + * @param {string} callbackName + * @param {boolean} checkTerminated + * @return {string} + */ + terminate( + callbackName: String, + checkTerminated: boolean) { + let returnValue = api_constants.SCORM_FALSE; + + if (_self.checkState(checkTerminated, + _self.#error_codes.TERMINATION_BEFORE_INIT, + _self.#error_codes.MULTIPLE_TERMINATION)) { + if (checkTerminated) _self.lastErrorCode = 0; + _self.currentState = api_constants.STATE_TERMINATED; + returnValue = api_constants.SCORM_TRUE; + _self.processListeners(callbackName); + } + + _self.apiLog(callbackName, null, 'returned: ' + returnValue, + api_constants.LOG_LEVEL_INFO); + _self.clearSCORMError(returnValue); + + return returnValue; + } + + /** + * Get the value of the CMIElement. + * + * @param {string} callbackName + * @param {boolean} checkTerminated + * @param {string} CMIElement + * @return {string} + */ + getValue( + callbackName: String, + checkTerminated: boolean, + CMIElement: String) { + let returnValue = ''; + + if (_self.checkState(checkTerminated, + _self.#error_codes.RETRIEVE_BEFORE_INIT, + _self.#error_codes.RETRIEVE_AFTER_TERM)) { + if (checkTerminated) _self.lastErrorCode = 0; + returnValue = _self.getCMIValue(CMIElement); + _self.processListeners(callbackName, CMIElement); + } + + _self.apiLog(callbackName, CMIElement, ': returned: ' + returnValue, + api_constants.LOG_LEVEL_INFO); + _self.clearSCORMError(returnValue); + + return returnValue; + } + + /** + * Sets the value of the CMIElement. + * + * @param {string} callbackName + * @param {boolean} checkTerminated + * @param {string} CMIElement + * @param {any} value + * @return {string} + */ + setValue( + callbackName: String, + checkTerminated: boolean, + CMIElement, + value) { + let returnValue = ''; + + if (_self.checkState(checkTerminated, _self.#error_codes.STORE_BEFORE_INIT, + _self.#error_codes.STORE_AFTER_TERM)) { + if (checkTerminated) _self.lastErrorCode = 0; + returnValue = _self.setCMIValue(CMIElement, value); + _self.processListeners(callbackName, CMIElement, value); + } + + _self.apiLog(callbackName, CMIElement, + ': ' + value + ': result: ' + returnValue, + api_constants.LOG_LEVEL_INFO); + _self.clearSCORMError(returnValue); + + return returnValue; + } + + /** + * Orders LMS to store all content parameters + * @param {string} callbackName + * @param {boolean} checkTerminated + * @return {string} + */ + commit( + callbackName: String, + checkTerminated: boolean) { + let returnValue = api_constants.SCORM_FALSE; + + if (_self.checkState(checkTerminated, _self.#error_codes.COMMIT_BEFORE_INIT, + _self.#error_codes.COMMIT_AFTER_TERM)) { + if (checkTerminated) _self.lastErrorCode = 0; + returnValue = api_constants.SCORM_TRUE; + _self.processListeners(callbackName); + } + + _self.apiLog(callbackName, null, 'returned: ' + returnValue, + api_constants.LOG_LEVEL_INFO); + _self.clearSCORMError(returnValue); + + return returnValue; + } + + /** + * Returns last error code + * @param {string} callbackName + * @return {string} + */ + getLastError(callbackName: String) { + const returnValue = String(_self.lastErrorCode); + + _self.processListeners(callbackName); + + _self.apiLog(callbackName, null, 'returned: ' + returnValue, + api_constants.LOG_LEVEL_INFO); + + return returnValue; + } + + /** + * Returns the errorNumber error description + * + * @param {string} callbackName + * @param {number} CMIErrorCode + * @return {string} + */ + getErrorString(callbackName: String, CMIErrorCode) { + let returnValue = ''; + + if (CMIErrorCode !== null && CMIErrorCode !== '') { + returnValue = _self.getLmsErrorMessageDetails(CMIErrorCode); + _self.processListeners(callbackName); + } + + _self.apiLog(callbackName, null, 'returned: ' + returnValue, + api_constants.LOG_LEVEL_INFO); + + return returnValue; + } + + /** + * Returns a comprehensive description of the errorNumber error. + * + * @param callbackName + * @param CMIErrorCode + * @return {string} + */ + getDiagnostic(callbackName: String, CMIErrorCode) { + let returnValue = ''; + + if (CMIErrorCode !== null && CMIErrorCode !== '') { + returnValue = _self.getLmsErrorMessageDetails(CMIErrorCode, true); + _self.processListeners(callbackName); + } + + _self.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 (_self.isNotInitialized()) { + _self.throwSCORMError(beforeInitError); + return false; + } else if (checkTerminated && _self.isTerminated()) { + _self.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 = _self.formatMessage(functionName, CMIElement, logMessage); + + if (messageLevel >= _self.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) { + _self.lastErrorCode = 0; + } + } + + /** + * Formats the SCORM messages for easy reading + * + * @param functionName + * @param CMIElement + * @param message + * @return {string} + */ + formatMessage(functionName: String, CMIElement: String, message: String) { + const baseLength = 20; + let messageString = ''; + + messageString += functionName; + + let fillChars = baseLength - messageString.length; + + for (let i = 0; i < fillChars; i++) { + messageString += ' '; + } + + messageString += ': '; + + if (CMIElement) { + const 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; + } + + const 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 ? + _self.#error_codes.UNDEFINED_DATA_MODEL : + _self.#error_codes.GENERAL; + + for (let i = 0; i < structure.length; i++) { + const attribute = structure[i]; + + if (i === structure.length - 1) { + if (scorm2004 && (attribute.substr(0, 8) === '{target=') && + (typeof refObject._isTargetValid == 'function')) { + _self.throwSCORMError(_self.#error_codes.READ_ONLY_ELEMENT); + } else if (!refObject.hasOwnProperty(attribute)) { + _self.throwSCORMError(invalidErrorCode, invalidErrorMessage); } else { - this.currentState = api_constants.STATE_INITIALIZED; - this.lastErrorCode = 0; + if (_self.stringContains(CMIElement, '.correct_responses')) { + _self.validateCorrectResponse(CMIElement, value); + } + + if (!scorm2004 || _self.lastErrorCode === 0) { + refObject[attribute] = value; returnValue = api_constants.SCORM_TRUE; - this.processListeners(callbackName); + } + } + } else { + refObject = refObject[attribute]; + if (!refObject) { + _self.throwSCORMError(invalidErrorCode, invalidErrorMessage); + break; } - this.apiLog(callbackName, null, "returned: " + returnValue, api_constants.LOG_LEVEL_INFO); - this.clearSCORMError(returnValue); + if (refObject.prototype === CMIArray) { + const index = parseInt(structure[i + 1], 10); - return returnValue; - } + // SCO is trying to set an item on an array + if (!isNaN(index)) { + const item = refObject.childArray[index]; - /** - * @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; - } - } + if (item) { + refObject = item; } else { - refObject = refObject[attribute]; - if (!refObject) { - this.throwSCORMError(invalidErrorCode, invalidErrorMessage); - break; - } + const newChild = _self.getChildElement(CMIElement, value); - 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 (!newChild) { + _self.throwSCORMError(invalidErrorCode, invalidErrorMessage); + } else { + refObject.childArray.push(newChild); + refObject = newChild; + } } - } - 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); + // Have to update i value to skip the array position + i++; + } } - - return returnValue; + } } - validateCorrectResponse(_CMIElement, _value) { + if (returnValue === api_constants.SCORM_FALSE) { + _self.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 + * @return {*} + */ + _commonGetCMIValue(methodName: String, scorm2004: boolean, CMIElement) { + if (!CMIElement || CMIElement === '') { + return ''; + } + + const 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)) { + _self.throwSCORMError(101, + 'getCMIValue did not find a value for: ' + CMIElement); + } + } + } else { + if ((String(attribute).substr(0, 8) === '{target=') && + (typeof refObject._isTargetValid == 'function')) { + const target = String(attribute). + substr(8, String(attribute).length - 9); + return refObject._isTargetValid(target); + } else if (!refObject.hasOwnProperty(attribute)) { + _self.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') { + _self.throwSCORMError(202); + } else if (attribute === '_count') { + _self.throwSCORMError(203); + } + } + return ''; + } else { + return refObject; + } + } + + /** + * Returns true if the API's current state is STATE_INITIALIZED + */ + isInitialized() { + return _self.currentState === api_constants.STATE_INITIALIZED; + } + + /** + * Returns true if the API's current state is STATE_NOT_INITIALIZED + */ + isNotInitialized() { + return _self.currentState === api_constants.STATE_NOT_INITIALIZED; + } + + /** + * Returns true if the API's current state is STATE_TERMINATED + */ + isTerminated() { + return _self.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; + + const listenerFunctions = listenerName.split(' '); + for (let i = 0; i < listenerFunctions.length; i++) { + const listenerSplit = listenerFunctions[i].split('.'); + if (listenerSplit.length === 0) return; + + const functionName = listenerSplit[0]; + + let CMIElement = null; + if (listenerSplit.length > 1) { + CMIElement = listenerName.replace(functionName + '.', ''); + } + + _self.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 < _self.listenerArray.length; i++) { + const listener = _self.listenerArray[i]; + const functionsMatch = listener.functionName === functionName; + const listenerHasCMIElement = !!listener.CMIElement; + const 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 = _self.getLmsErrorMessageDetails(errorNumber); + } + + _self.apiLog('throwSCORMError', null, errorNumber + ': ' + message, + api_constants.LOG_LEVEL_ERROR); + + _self.lastErrorCode = String(errorNumber); + } + + /** + * Loads CMI data from a JSON object. + */ + loadFromJSON(json, CMIElement) { + if (!_self.isNotInitialized()) { + console.error( + 'loadFromJSON can only be called before the call to LMSInitialize.'); + return; + } + + CMIElement = CMIElement || 'cmi'; + + for (const key in json) { + if (json.hasOwnProperty(key) && json[key]) { + const currentCMIElement = CMIElement + '.' + key; + const value = json[key]; + + if (value['childArray']) { + for (let i = 0; i < value['childArray'].length; i++) { + _self.loadFromJSON(value['childArray'][i], + currentCMIElement + '.' + i); + } + } else if (value.constructor === Object) { + _self.loadFromJSON(value, currentCMIElement); + } else { + _self.setCMIValue(currentCMIElement, value); + } + } + } + } + + renderCMIToJSON() { + const cmi = _self.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 + * @return {boolean} + */ + checkValidFormat(value: String, regexPattern: String) { + const formatRegex = new RegExp(regexPattern); + if (!value || !value.match(formatRegex)) { + _self.throwSCORMError(_self.#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 + * @return {boolean} + */ + checkValidRange(value: any, rangePattern: String) { + const ranges = rangePattern.split('#'); + value = value * 1.0; + if (value >= ranges[0]) { + if ((ranges[1] === '*') || (value <= ranges[1])) { + _self.clearSCORMError(api_constants.SCORM_TRUE); + return true; + } else { + _self.throwSCORMError(_self.#error_codes.VALUE_OUT_OF_RANGE); return false; + } + } else { + _self.throwSCORMError(_self.#error_codes.VALUE_OUT_OF_RANGE); + 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; - } + /** + * Throws a SCORM error + * + * @param when the number of milliseconds to wait before committing + */ + scheduleCommit(when: number) { + _self.#timeout = new ScheduledCommit(this, when); + } + + /** + * Clears and cancels any currently scheduled commits + */ + clearScheduledCommit() { + if (_self.#timeout) { + _self.#timeout.cancel(); + _self.#timeout = null; } + } } class ScheduledCommit { - #API; - #cancelled: false; - #timeout; + #API; + #cancelled: false; + #timeout; - constructor(API: any, when: number) { - this.#API = API; - this.#timeout = setTimeout(this.#wrapper, when); - } + constructor(API: any, when: number) { + _self.#API = API; + _self.#timeout = setTimeout(_self.#wrapper, when); + } - cancel() { - this.#cancelled = true; - if (this.#timeout) { - clearTimeout(this.#timeout); - } + cancel() { + _self.#cancelled = true; + if (_self.#timeout) { + clearTimeout(_self.#timeout); } + } - #wrapper() { - if (!this.#cancelled) { - this.#API.LMSCommit(); - } + #wrapper = () => { + if (!_self.#cancelled) { + _self.#API.LMSCommit(); } + }; } diff --git a/src/Scorm12API.js b/src/Scorm12API.js index c0ec961..3a2878c 100644 --- a/src/Scorm12API.js +++ b/src/Scorm12API.js @@ -1,177 +1,178 @@ // @flow import BaseAPI from './BaseAPI'; import { - CMI, - CMIInteractionsCorrectResponsesObject, - CMIInteractionsObject, - CMIInteractionsObjectivesObject, - CMIObjectivesObject -} from "./cmi/scorm12_cmi"; + 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"; +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); + constructor() { + super(scorm12_error_codes); - this.cmi = new CMI(this); - } + this.cmi = new CMI(this); + } - /** - * @returns {string} bool + /** + * @return {string} bool */ - LMSInitialize() { - return this.APIInitialize("LMSInitialize", "LMS was already initialized!", "LMS is already finished!"); - } + LMSInitialize() { + return this.initialize('LMSInitialize', 'LMS was already initialized!', + 'LMS is already finished!'); + } - /** - * @returns {string} bool + /** + * @return {string} bool */ - LMSFinish() { - return this.APITerminate("LMSFinish", false); - } + LMSFinish() { + return this.terminate('LMSFinish', false); + } - /** + /** * @param CMIElement - * @returns {string} + * @return {string} */ - LMSGetValue(CMIElement) { - return this.APIGetValue("LMSGetValue", false, CMIElement); - } + LMSGetValue(CMIElement) { + return this.getValue('LMSGetValue', false, CMIElement); + } - /** + /** * @param CMIElement * @param value - * @returns {string} + * @return {string} */ - LMSSetValue(CMIElement, value) { - return this.APISetValue("LMSSetValue", false, CMIElement, value); - } + LMSSetValue(CMIElement, value) { + return this.setValue('LMSSetValue', false, CMIElement, value); + } - /** + /** * Orders LMS to store all content parameters * - * @returns {string} bool + * @return {string} bool */ - LMSCommit() { - return this.APICommit("LMSCommit", false); - } + LMSCommit() { + return this.commit('LMSCommit', false); + } - /** + /** * Returns last error code * - * @returns {string} + * @return {string} */ - LMSGetLastError() { - return this.APIGetLastError("LMSGetLastError"); - } + LMSGetLastError() { + return this.getLastError('LMSGetLastError'); + } - /** + /** * Returns the errorNumber error description * * @param CMIErrorCode - * @returns {string} + * @return {string} */ - LMSGetErrorString(CMIErrorCode) { - return this.APIGetErrorString("LMSGetErrorString", CMIErrorCode); - } + LMSGetErrorString(CMIErrorCode) { + return this.getErrorString('LMSGetErrorString', CMIErrorCode); + } - /** + /** * Returns a comprehensive description of the errorNumber error. * * @param CMIErrorCode - * @returns {string} + * @return {string} */ - LMSGetDiagnostic(CMIErrorCode) { - return this.APIGetDiagnostic("LMSGetDiagnostic", CMIErrorCode); - } + LMSGetDiagnostic(CMIErrorCode) { + return this.getDiagnostic('LMSGetDiagnostic', CMIErrorCode); + } - /** + /** * Sets a value on the CMI Object * * @param CMIElement * @param value - * @returns {string} + * @return {string} */ - setCMIValue(CMIElement, value) { - this._commonSetCMIValue("LMSSetValue", false, CMIElement, value); - } + setCMIValue(CMIElement, value) { + this._commonSetCMIValue('LMSSetValue', false, CMIElement, value); + } - /** + /** * Gets a value from the CMI Object * * @param CMIElement - * @returns {*} + * @return {*} */ - getCMIValue(CMIElement) { - return this._commonGetCMIValue("getCMIValue", false, CMIElement); - } + 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; + 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; + 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); } - validateCorrectResponse(CMIElement, value) { - return true; - } + 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"; + 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; + // 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); + getCurrentTotalTime() { + const timeRegex = new RegExp(scorm12_regex.CMITime); - const totalTime = this.cmi.core.total_time; - const sessionTime = this.cmi.core.session_time; + 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); + const totalSeconds = Utilities.getTimeAsSeconds(totalTime, timeRegex); + const sessionSeconds = Utilities.getTimeAsSeconds(sessionTime, timeRegex); - return Utilities.getSecondsAsHHMMSS(totalSeconds + sessionSeconds); - } + return Utilities.getSecondsAsHHMMSS(totalSeconds + sessionSeconds); + } - /** + /** * Replace the whole API with another */ - replaceWithAnotherScormAPI(newAPI) { - // Data Model - this.cmi = newAPI.cmi; - } + replaceWithAnotherScormAPI(newAPI) { + // Data Model + this.cmi = newAPI.cmi; + } } diff --git a/src/Scorm2004API.js b/src/Scorm2004API.js index a0068bb..982be97 100644 --- a/src/Scorm2004API.js +++ b/src/Scorm2004API.js @@ -1,379 +1,416 @@ // @flow import BaseAPI from './BaseAPI'; import { - ADL, - CMI, - CMICommentsFromLearnerObject, - CMICommentsFromLMSObject, - CMIInteractionsCorrectResponsesObject, - CMIInteractionsObject, - CMIInteractionsObjectivesObject, - CMIObjectivesObject + ADL, + CMI, + CMICommentsFromLearnerObject, + CMICommentsFromLMSObject, + CMIInteractionsCorrectResponsesObject, + CMIInteractionsObject, + CMIInteractionsObjectivesObject, + CMIObjectivesObject, } from './cmi/scorm2004_cmi'; -import * as Utilities from "./utilities"; -import {correct_responses, scorm2004_constants, scorm2004_error_codes} from "./constants"; -import {scorm2004_regex} from "./regex"; +import * as Util from './utilities'; +import { + correct_responses, + scorm2004_constants, + scorm2004_error_codes, +} from './constants'; +import {scorm2004_regex} from './regex'; const constants = scorm2004_constants; const valid_languages = constants.valid_languages; +let _self; +/** + * API class for SCORM 2004 + */ class Scorm2004API extends BaseAPI { - version: "1.0"; + version: '1.0'; - constructor() { - super(scorm2004_error_codes); + /** + * Constructor for SCORM 2004 API + */ + constructor() { + super(scorm2004_error_codes); - this.cmi = new CMI(this); - this.adl = new ADL(this); + _self = this; + _self.cmi = new CMI(_self); + _self.adl = new ADL(_self); - // 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; - } + // Rename functions to match 2004 Spec + _self.Initialize = _self.LMSInitialize; + _self.Terminate = _self.LMSTerminate; + _self.GetValue = _self.LMSGetValue; + _self.SetValue = _self.LMSSetValue; + _self.Commit = _self.LMSCommit; + _self.GetLastError = _self.LMSGetLastError; + _self.GetErrorString = _self.LMSGetErrorString; + _self.GetDiagnostic = _self.LMSGetDiagnostic; + } - /** - * @returns {string} bool - */ - LMSInitialize() { - return this.APIInitialize("Initialize"); - } + /** + * @return {string} bool + */ + LMSInitialize() { + return _self.initialize('Initialize'); + } - /** - * @returns {string} bool - */ - LMSTerminate() { - return this.APITerminate("Terminate", true); - } + /** + * @return {string} bool + */ + LMSTerminate() { + return _self.terminate('Terminate', true); + } - /** - * @param CMIElement - * @returns {string} - */ - LMSGetValue(CMIElement) { - return this.APIGetValue("GetValue", true, CMIElement); - } + /** + * @param {string} CMIElement + * @return {string} + */ + LMSGetValue(CMIElement) { + return _self.getValue('GetValue', true, CMIElement); + } - /** - * @param CMIElement - * @param value - * @returns {string} - */ - LMSSetValue(CMIElement, value) { - return this.APISetValue("SetValue", true, CMIElement, value); - } + /** + * @param {string} CMIElement + * @param {any} value + * @return {string} + */ + LMSSetValue(CMIElement, value) { + return _self.setValue('SetValue', true, CMIElement, value); + } - /** - * Orders LMS to store all content parameters - * - * @returns {string} bool - */ - LMSCommit() { - return this.APICommit("Commit"); - } + /** + * Orders LMS to store all content parameters + * + * @return {string} bool + */ + LMSCommit() { + return _self.commit('Commit'); + } - /** - * Returns last error code - * - * @returns {string} - */ - LMSGetLastError() { - return this.APIGetLastError("GetLastError"); - } + /** + * Returns last error code + * + * @return {string} + */ + LMSGetLastError() { + return _self.getLastError('GetLastError'); + } - /** - * Returns the errorNumber error description - * - * @param CMIErrorCode - * @returns {string} - */ - LMSGetErrorString(CMIErrorCode) { - return this.APIGetErrorString("GetErrorString", CMIErrorCode); - } + /** + * Returns the errorNumber error description + * + * @param CMIErrorCode + * @return {string} + */ + LMSGetErrorString(CMIErrorCode) { + return _self.APIGetErrorString('GetErrorString', CMIErrorCode); + } - /** - * Returns a comprehensive description of the errorNumber error. - * - * @param CMIErrorCode - * @returns {string} - */ - LMSGetDiagnostic(CMIErrorCode) { - return this.APIGetDiagnostic("GetDiagnostic", CMIErrorCode); - } + /** + * Returns a comprehensive description of the errorNumber error. + * + * @param CMIErrorCode + * @return {string} + */ + LMSGetDiagnostic(CMIErrorCode) { + return _self.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); - } + /** + * Sets a value on the CMI Object + * + * @param {string} CMIElement + * @param {any} value + */ + setCMIValue(CMIElement, value) { + _self._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; + /** + * Gets or builds a new child element to add to the array. + * + * @param {string} CMIElement + * @param {any} value + * @return {any} + */ + 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 (_self.stringContains(CMIElement, 'cmi.objectives')) { + newChild = new CMIObjectivesObject(this); + } else if (_self.stringContains(CMIElement, '.correct_responses')) { + const parts = CMIElement.split('.'); + const index = Number(parts[2]); + const interaction = _self.cmi.interactions.childArray[index]; + if (typeof interaction.type === 'undefined') { + _self.throwSCORMError(scorm2004_error_codes.DEPENDENCY_NOT_ESTABLISHED); + } else { + const interaction_type = interaction.type; + const interaction_count = interaction.correct_responses._count; + if (interaction_type === 'choice') { + for (let i = 0; i < interaction_count && _self.lastErrorCode === + 0; i++) { + const response = interaction.correct_responses.childArray[i]; + if (response.pattern === value) { + _self.throwSCORMError(scorm2004_error_codes.GENERAL_SET_FAILURE); } - 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"); - } - } + const response_type = correct_responses[interaction_type]; + let nodes = []; + if (response_type.delimiter !== '') { + nodes = value.split(response_type.delimiter); } 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; + nodes[0] = value; } - return detail ? detailMessage : basicMessage; + if (nodes.length > 0 && nodes.length <= response_type.max) { + _self.#checkCorrectResponseValue(interaction_type, nodes, value); + } else if (nodes.length > response_type.max) { + _self.throwSCORMError(scorm2004_error_codes.GENERAL_SET_FAILURE, + 'Data Model Element Pattern Too Long'); + } + } + if (_self.lastErrorCode === 0) { + newChild = new CMIInteractionsCorrectResponsesObject(this); + } + } else if (_self.stringContains(CMIElement, '.objectives')) { + newChild = new CMIInteractionsObjectivesObject(this); + } else if (_self.stringContains(CMIElement, 'cmi.interactions')) { + newChild = new CMIInteractionsObject(this); + } else if (_self.stringContains(CMIElement, 'cmi.comments_from_learner')) { + newChild = new CMICommentsFromLearnerObject(this); + } else if (_self.stringContains(CMIElement, 'cmi.comments_from_lms')) { + newChild = new CMICommentsFromLMSObject(this); } - #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 newChild; + } + + /** + * Validate correct response. + * @param {string} CMIElement + * @param {any} value + */ + validateCorrectResponse(CMIElement, value) { + const parts = CMIElement.split('.'); + const index = Number(parts[2]); + const pattern_index = Number(parts[4]); + const interaction = _self.cmi.interactions.childArray[index]; + + const interaction_type = interaction.type; + const interaction_count = interaction.correct_responses._count; + if (interaction_type === 'choice') { + for (let i = 0; i < interaction_count && _self.lastErrorCode === 0; i++) { + const response = interaction.correct_responses.childArray[i]; + if (response.pattern === value) { + _self.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) { + _self.#checkCorrectResponseValue(interaction_type, nodes, value); + } else if (nodes.length > response_type.max) { + _self.throwSCORMError(scorm2004_error_codes.GENERAL_SET_FAILURE, + 'Data Model Element Pattern Too Long'); + } + + if (_self.lastErrorCode === 0 && + (!response_type.duplicate || + !_self.#checkDuplicatedPattern(interaction.correct_responses, + pattern_index, value)) || + (_self.lastErrorCode === 0 && value === '')) { + // do nothing, we want the inverse + } else { + if (_self.lastErrorCode === 0) { + _self.throwSCORMError(scorm2004_error_codes.GENERAL_SET_FAILURE, + 'Data Model Element Pattern Already Exists'); + } + } + } else { + _self.throwSCORMError(scorm2004_error_codes.GENERAL_SET_FAILURE, + 'Data Model Element Collection Limit Reached'); + } + } + + /** + * Gets a value from the CMI Object + * + * @param {string} CMIElement + * @return {*} + */ + getCMIValue(CMIElement) { + return _self._commonGetCMIValue('GetValue', true, CMIElement); + } + + /** + * Returns the message that corresponds to errorNumber. + * + * @param {string,number} errorNumber + * @param {string} detail + * @return {string} + */ + 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; + const 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 && _self.lastErrorCode === 0; i++) { + if (interaction_type.match( + '^(fill-in|long-fill-in|matching|performance|sequencing)$')) { + nodes[i] = _self.#removeCorrectResponsePrefixes(nodes[i]); + } + + if (response.delimiter2 !== undefined) { + const values = nodes[i].split(response.delimiter2); + if (values.length === 2) { + const matches = values[0].match(formatRegex); + if (!matches) { + _self.throwSCORMError(scorm2004_error_codes.TYPE_MISMATCH); + } else { + if (!values[1].match(new RegExp(response.format2))) { + _self.throwSCORMError(scorm2004_error_codes.TYPE_MISMATCH); } + } + } else { + _self.throwSCORMError(scorm2004_error_codes.TYPE_MISMATCH); } - 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]); + } else { + const matches = nodes[i].match(formatRegex); + if ((!matches && value !== '') || + (!matches && interaction_type === 'true-false')) { + _self.throwSCORMError(scorm2004_error_codes.TYPE_MISMATCH); + } else { + if (interaction_type === 'numeric' && nodes.length > 1) { + if (Number(nodes[0]) > Number(nodes[1])) { + _self.throwSCORMError(scorm2004_error_codes.TYPE_MISMATCH); } - - 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); - } - } - } - } + } else { + if (nodes[i] !== '' && response.unique) { + for (let j = 0; j < i && _self.lastErrorCode === 0; j++) { + if (nodes[i] === nodes[j]) { + _self.throwSCORMError(scorm2004_error_codes.TYPE_MISMATCH); } + } } + } } + } } + }; - #removeCorrectResponsePrefixes(node) { - let seenOrder = false; - let seenCase = false; - let seenLang = false; + #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; + const prefixRegex = new RegExp( + '^({(lang|case_matters|order_matters)=([^}]+)})'); + let matches = node.match(prefixRegex); + let langMatches = null; + while (matches) { + switch (matches[2]) { + case 'lang': + langMatches = node.match(scorm2004_regex.CMILangcr); + if (langMatches) { + const lang = langMatches[3]; + if (lang !== undefined && lang.length > 0) { + if (valid_languages[lang.toLowerCase()] === undefined) { + _self.throwSCORMError(scorm2004_error_codes.TYPE_MISMATCH); + } } - node = node.substr(matches[1].length); - matches = node.match(prefixRegex); - } + } + seenLang = true; + break; + case 'case_matters': + if (!seenLang && !seenOrder && !seenCase) { + if (matches[3] !== 'true' && matches[3] !== 'false') { + _self.throwSCORMError(scorm2004_error_codes.TYPE_MISMATCH); + } + } - return node; + seenCase = true; + break; + case 'order_matters': + if (!seenCase && !seenLang && !seenOrder) { + if (matches[3] !== 'true' && matches[3] !== 'false') { + _self.throwSCORMError(scorm2004_error_codes.TYPE_MISMATCH); + } + } + + seenOrder = true; + break; + default: + break; + } + node = node.substr(matches[1].length); + matches = node.match(prefixRegex); } - /** - * Replace the whole API with another - */ - replaceWithAnotherScormAPI(newAPI) { - // Data Model - this.cmi = newAPI.cmi; - this.adl = newAPI.adl; - } + return node; + }; - /** - * Adds the current session time to the existing total time. - */ - getCurrentTotalTime() { - const totalTime = this.cmi.total_time; - const sessionTime = this.cmi.session_time; + /** + * Replace the whole API with another + * @param {Scorm2004API} newAPI + */ + replaceWithAnotherScormAPI(newAPI) { + // Data Model + _self.cmi = newAPI.cmi; + _self.adl = newAPI.adl; + } - const durationRegex = scorm2004_regex.CMITimespan; - const totalSeconds = Utilities.getDurationAsSeconds(totalTime, durationRegex); - const sessionSeconds = Utilities.getDurationAsSeconds(sessionTime, durationRegex); + /** + * Adds the current session time to the existing total time. + * + * @return {string} ISO8601 Duration + */ + getCurrentTotalTime() { + const totalTime = _self.cmi.total_time; + const sessionTime = _self.cmi.session_time; - return Utilities.getSecondsAsISODuration(totalSeconds + sessionSeconds); - } + const durationRegex = scorm2004_regex.CMITimespan; + const totalSeconds = Util.getDurationAsSeconds(totalTime, durationRegex); + const sessionSeconds = Util.getDurationAsSeconds(sessionTime, + durationRegex); + + return Util.getSecondsAsISODuration(totalSeconds + sessionSeconds); + } } diff --git a/src/cmi/aicc_cmi.js b/src/cmi/aicc_cmi.js index 1026c8e..42f72e0 100644 --- a/src/cmi/aicc_cmi.js +++ b/src/cmi/aicc_cmi.js @@ -1,115 +1,261 @@ import * as Scorm12CMI from './scorm12_cmi'; -import {BaseCMI, CMIArray, CMIScore} from "./common"; -import {aicc_constants} from "../constants"; -import {aicc_regex} from "../regex"; +import {BaseCMI, CMIArray, CMIScore} from './common'; +import {aicc_constants, scorm12_error_codes} from '../constants'; +import {aicc_regex} from '../regex'; const constants = aicc_constants; const regex = aicc_regex; +/** + * Sets a READ_ONLY error on the API + * @param {AICC} API + */ +function throwReadOnlyError(API) { + API.throwSCORMError(scorm12_error_codes.READ_ONLY_ELEMENT); +} + +/** + * CMI Class for AICC + */ export class CMI extends Scorm12CMI.CMI { - constructor(API) { - super(API, constants.cmi_children, new AICCCMIStudentData(API)); + /** + * Constructor for AICC CMI object + * @param {AICC} API + */ + constructor(API) { + super(API, constants.cmi_children, new AICCCMIStudentData(API)); - this.evaluation = new CMIEvaluation(API); - } + this.evaluation = new CMIEvaluation(API); + } } +/** + * AICC Evaluation object + */ class CMIEvaluation extends BaseCMI { - constructor(API) { - super(API); - } + /** + * Constructor for AICC Evaluation object + * @param {AICC} API + */ + constructor(API) { + super(API); + } - comments = new class extends CMIArray { - constructor(API) { - super(API, constants.comments_children, 402); - } - }; + comments = new class extends CMIArray { + /** + * Constructor for AICC Evaluation Comments object + * @param {AICC} API + */ + constructor(API) { + super(API, constants.comments_children, 402); + } + }; } +/** + * StudentData class for AICC + */ class AICCCMIStudentData extends Scorm12CMI.CMIStudentData { + /** + * Constructor for AICC StudentData object + * @param {AICC} API + */ + constructor(API) { + super(API, constants.student_data_children); + } + + #tries_during_lesson = ''; + + /** + * Getter for tries_during_lesson + * @return {string} + */ + get tries_during_lesson() { + return this.#tries_during_lesson; + } + + /** + * Setter for #tries_during_lesson. Sets an error if trying to set after + * API initialization. + * @param {string} tries_during_lesson + */ + set tries_during_lesson(tries_during_lesson) { + this.API.isNotInitialized() ? + this.#tries_during_lesson = tries_during_lesson : + throwReadOnlyError(); + } + + tries = new class extends CMIArray { + /** + * Constructor for inline Tries Array class + * @param {AICC} API + */ constructor(API) { - super(API, constants.student_data_children); + super(API, aicc_constants.tries_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); - } - }; + }; } +let _self; + +/** + * Class for AICC Tries + */ export class CMITriesObject extends BaseCMI { - constructor(API) { - super(API); + #API; + + /** + * Constructor for AICC Tries object + * @param {AICC} API + */ + constructor(API) { + super(API); + this.#API = API; + _self = this; + } + + #status = ''; + #time = ''; + + /** + * Getter for #status + * @return {string} + */ + get status() { + return this.#status; + } + + /** + * Setter for #status + * @param {string} status + */ + set status(status) { + if (this.API.checkValidFormat(status, regex.CMIStatus2)) { + this.#status = status; } + } - #status = ""; - #time = ""; + /** + * Getter for #time + * @return {string} + */ + get time() { + return this.#time; + } - get status() { return this.#status; } - set status(status) { - if(this.API.checkValidFormat(status, regex.CMIStatus2)) { - this.#status = status; - } + /** + * Setter for #time + * @param {string} time + */ + set time(time) { + if (this.API.checkValidFormat(time, regex.CMITime)) { + this.#time = time; } + } - get time() { return this.#time; } - set time(time) { - if(this.API.checkValidFormat(time, regex.CMITime)) { - this.#time = time; - } - } - - score = new CMIScore(API); + score = new CMIScore(_self.#API); } +/** + * Class for AICC Evaluation Comments + */ export class CMIEvaluationCommentsObject extends BaseCMI { - constructor(API) { - super(API); - } + /** + * Constructor for Evaluation Comments + * @param {AICC} API + */ + constructor(API) { + super(API); + } - #content = ""; - #location = ""; - #time = ""; + #content = ''; + #location = ''; + #time = ''; - get content() { return this.#content; } - set content(content) { - if(this.API.checkValidFormat(content, regex.CMIString256)) { - this.#content = content; - } - } + /** + * Getter for #content + * @return {string} + */ + get content() { + return this.#content; + } - get location() { return this.#location; } - set location(location) { - if(this.API.checkValidFormat(location, regex.CMIString256)) { - this.#location = location; - } + /** + * Setter for #content + * @param {string} content + */ + set content(content) { + if (this.API.checkValidFormat(content, regex.CMIString256)) { + this.#content = content; } + } - get time() { return this.#time; } - set time(time) { - if(this.API.checkValidFormat(time, regex.CMITime)) { - this.#time = time; - } + /** + * Getter for #location + * @return {string} + */ + get location() { + return this.#location; + } + + /** + * Setter for #location + * @param {string} location + */ + set location(location) { + if (this.API.checkValidFormat(location, regex.CMIString256)) { + this.#location = location; } + } + + /** + * Getter for #time + * @return {string} + */ + get time() { + return this.#time; + } + + /** + * Setting for #time + * @param {string} time + */ + set time(time) { + if (this.API.checkValidFormat(time, regex.CMITime)) { + this.#time = time; + } + } } +/** + * Class for AICC Navigation object + */ export class NAV extends BaseCMI { - constructor(API) { - super(API); - } + /** + * Constructor for NAV object + * @param {AICC} API + */ + constructor(API) { + super(API); + } - #event = ""; + #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; - } + /** + * Getter for #event + * @return {string} + */ + get event() { + return (!this.jsonString) ? this.API.throwSCORMError(404) : this.#event; + } + + /** + * Setter for #event + * @param {string} event + */ + set event(event) { + if (this.API.checkValidFormat(event, regex.NAVEvent)) { + this.#event = event; } + } } diff --git a/src/cmi/scorm12_cmi.js b/src/cmi/scorm12_cmi.js index 72934be..2b748fa 100644 --- a/src/cmi/scorm12_cmi.js +++ b/src/cmi/scorm12_cmi.js @@ -1,305 +1,434 @@ import {BaseCMI, CMIArray, CMIScore} from './common'; -import {scorm12_constants, scorm12_error_codes} from "../constants"; -import {scorm12_regex} from "../regex"; +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); + API.throwSCORMError(scorm12_error_codes.READ_ONLY_ELEMENT); } function throwWriteOnlyError(API) { - API.throwSCORMError(scorm12_error_codes.WRITE_ONLY_ELEMENT); + API.throwSCORMError(scorm12_error_codes.WRITE_ONLY_ELEMENT); } function throwInvalidValueError(API) { - API.throwSCORMError(scorm12_error_codes.INVALID_SET_VALUE); + 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 = ""; + #_children = ''; + #_version = '3.4'; + #suspend_data = ''; + #launch_data = ''; + #comments = ''; + #comments_from_lms = ''; student_data = null; constructor(API, cmi_children, student_data) { - super(API); + 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); + 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; + 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 _version() { + return this.#_version; + } - get _children() { return this.#_children; } - set _children(_children) { throwInvalidValueError(this.API); } + set _version(_version) { + throwInvalidValueError(this.API); + } - get suspend_data() { return this.#suspend_data; } + 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; - } + 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 launch_data() { + return this.#launch_data; + } - get comments() { return this.#comments; } + 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; - } + 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); } + 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); + constructor(API) { + super(API); - this.score = new CMIScore(API, constants.score_children, regex.score_range); + 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; } - #_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"; + set _children(_children) { + throwInvalidValueError(this.API); + } - get _children() { return this.#_children; } - set _children(_children) { throwInvalidValueError(this.API); } + get student_id() { + return this.#student_id; + } - get student_id() { return this.#student_id; } - set student_id(student_id) { this.API.isNotInitialized() ? this.#student_id = student_id : throwReadOnlyError(this.API); } + 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 student_name() { + return this.#student_name; + } - get lesson_location() { return this.#lesson_location; } + 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; - } + 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 credit() { + return this.#credit; + } - get lesson_status() { return this.#lesson_status; } + 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; - } + 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 entry() { + return this.#entry; + } - get total_time() { return this.#total_time; } - set total_time(total_time) { this.API.isNotInitialized() ? this.#total_time = total_time : throwReadOnlyError(this.API); } + set entry(entry) { + this.API.isNotInitialized() ? + this.#entry = entry : + 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 total_time() { + return this.#total_time; + } - get exit() { return (!this.jsonString) ? throwWriteOnlyError(this.API) : this.#exit; } + 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; - } + if (this.API.checkValidFormat(exit, regex.CMIExit)) { + this.#exit = exit; + } } - get session_time() { return (!this.jsonString) ? throwWriteOnlyError(this.API) : this.#session_time; } + 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; - } + 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; + 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 - }); - } + 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 = ""; + #mastery_score = ''; + #max_time_allowed = ''; + #time_limit_action = ''; constructor(API, student_data_children) { - super(API); + super(API); - this.#_children = student_data_children? student_data_children : constants.student_data_children; + this.#_children = student_data_children ? + student_data_children : + constants.student_data_children; } - get _children() { return this.#_children; } - set _children(_children) { throwInvalidValueError(this.API); } + get _children() { + return this.#_children; + } - get mastery_score() { return this.#mastery_score; } - set mastery_score(mastery_score) { this.API.isNotInitialized() ? this.#mastery_score = mastery_score : throwReadOnlyError(this.API); } + set _children(_children) { + throwInvalidValueError(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 mastery_score() { + return this.#mastery_score; + } - 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); } + 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; + 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); - } + constructor(API) { + super(API); + } #_children = constants.student_preference_children; - #audio = ""; - #language = ""; - #speed = ""; - #text = ""; + #audio = ''; + #language = ''; + #speed = ''; + #text = ''; - get _children() { return this.#_children; } - set _children(_children) { throwInvalidValueError(this.API); } + get _children() { + return this.#_children; + } - get audio() { return this.#audio; } + 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; - } + if (this.API.checkValidFormat(audio, regex.CMISInteger) && + this.API.checkValidRange(audio, regex.audio_range)) { + this.#audio = audio; + } } - get language() { return this.#language; } + get language() { + return this.#language; + } set language(language) { - if(this.API.checkValidFormat(language, regex.CMIString256)) { - this.#language = language; - } + if (this.API.checkValidFormat(language, regex.CMIString256)) { + this.#language = language; + } } - get speed() { return this.#speed; } + 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; - } + if (this.API.checkValidFormat(speed, regex.CMISInteger) && + this.API.checkValidRange(speed, regex.speed_range)) { + this.#speed = speed; + } } - get text() { return this.#text; } + 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; - } + 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; + 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 - }); - } + 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); + 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 - }); - } + 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: ""; @@ -309,154 +438,184 @@ export class CMIInteractionsObject extends BaseCMI { #result: ""; #latency: ""; - get id() { return (!this.jsonString) ? throwWriteOnlyError(this.API) : this.#id; } + get id() { + return (!this.jsonString) ? throwWriteOnlyError(this.API) : this.#id; + } set id(id) { - if(this.API.checkValidFormat(id, regex.CMIIdentifier)) { - this.#id = id; - } + if (this.API.checkValidFormat(id, regex.CMIIdentifier)) { + this.#id = id; + } } - get time() { return (!this.jsonString) ? throwWriteOnlyError(this.API) : this.#time; } + get time() { + return (!this.jsonString) ? throwWriteOnlyError(this.API) : this.#time; + } set time(time) { - if(this.API.checkValidFormat(time, regex.CMITime)) { - this.#time = time; - } + if (this.API.checkValidFormat(time, regex.CMITime)) { + this.#time = time; + } } - get type() { return (!this.jsonString) ? throwWriteOnlyError(this.API) : this.#type; } + get type() { + return (!this.jsonString) ? throwWriteOnlyError(this.API) : this.#type; + } set type(type) { - if(this.API.checkValidFormat(type, regex.CMIType)) { - this.#type = type; - } + if (this.API.checkValidFormat(type, regex.CMIType)) { + this.#type = type; + } } - get weighting() { return (!this.jsonString) ? throwWriteOnlyError(this.API) : this.#weighting; } + 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; - } + 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; } + 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; - } + if (this.API.checkValidFormat(student_response, regex.CMIFeedback)) { + this.#student_response = student_response; + } } - get result() { return (!this.jsonString) ? throwWriteOnlyError(this.API) : this.#result; } + get result() { + return (!this.jsonString) ? + throwWriteOnlyError(this.API) : + this.#result; + } set result(result) { - if(this.API.checkValidFormat(result, regex.CMIResult)) { - this.#result = result; - } + if (this.API.checkValidFormat(result, regex.CMIResult)) { + this.#result = result; + } } - get latency() { return (!this.jsonString) ? throwWriteOnlyError(this.API) : this.#latency; } + get latency() { + return (!this.jsonString) ? + throwWriteOnlyError(this.API) : + this.#latency; + } set latency(latency) { - if(this.API.checkValidFormat(latency, regex.CMITimespan)) { - this.#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; + 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); + constructor(API) { + super(API); - this.score = new CMIScore(API); - } + this.score = new CMIScore(API); + } #id: ""; #status: ""; - get id() { return this.#id; } + get id() { + return this.#id; + } set id(id) { - if(this.API.checkValidFormat(id, regex.CMIIdentifier)) { - this.#id = id; - } + if (this.API.checkValidFormat(id, regex.CMIIdentifier)) { + this.#id = id; + } } - get status() { return this.#status; } + get status() { + return this.#status; + } set status(status) { - if(this.API.checkValidFormat(status, regex.CMIStatus2)) { - this.#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; + 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); - } + constructor(API) { + super(API); + } #id: ""; - get id() { return this.#id; } + get id() { + return this.#id; + } set id(id) { - if(this.API.checkValidFormat(id, regex.CMIIdentifier)) { - this.#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; + this.jsonString = true; + const result = { + 'id': this.id, + }; + delete this.jsonString; + return result; } } export class CMIInteractionsCorrectResponsesObject extends BaseCMI { - constructor(API) { - super(API); - } + constructor(API) { + super(API); + } #pattern: ""; - get pattern() { return this.#pattern; } + get pattern() { + return this.#pattern; + } set pattern(pattern) { - if(this.API.checkValidFormat(pattern, regex.CMIFeedback)) { - this.#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; + this.jsonString = true; + const result = { + 'pattern': this.pattern, + }; + delete this.jsonString; + return result; } } diff --git a/src/utilities.js b/src/utilities.js index 27f6883..11fad7f 100644 --- a/src/utilities.js +++ b/src/utilities.js @@ -5,31 +5,31 @@ 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], + ['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} + * @param {Number} totalSeconds + * @return {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'; - } + // 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); + const hours = Math.floor(totalSeconds / SECONDS_PER_HOUR); - let dateObj = new Date(totalSeconds * 1000); - let minutes = dateObj.getUTCMinutes(); - let seconds = dateObj.getSeconds(); + const dateObj = new Date(totalSeconds * 1000); + const minutes = dateObj.getUTCMinutes(); + const seconds = dateObj.getSeconds(); - return hours.toString().padStart(2, '0') + ':' + + return hours.toString().padStart(2, '0') + ':' + minutes.toString().padStart(2, '0') + ':' + seconds.toString().padStart(2, '0'); } @@ -37,75 +37,77 @@ export function getSecondsAsHHMMSS(totalSeconds: Number) { /** * Calculate the number of seconds from ISO 8601 Duration * - * @param seconds - * @returns {String} + * @param {Number} seconds + * @return {String} */ export function getSecondsAsISODuration(seconds: Number) { - // SCORM spec does not deal with negative durations, give zero back - if(!seconds || seconds <= 0) { - return 'P0S'; + // 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}`; } + }); - 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; + return duration; } /** * Calculate the number of seconds from HH:MM:SS.DDDDDD * - * @param timeString - * @param timeRegex - * @returns {number} + * @param {string} timeString + * @param {RegExp} timeRegex + * @return {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; + 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} + * @param {string} duration + * @param {RegExp} durationRegex + * @return {number} */ export function getDurationAsSeconds(duration: String, durationRegex: RegExp) { - if(!duration || !duration.match(durationRegex)) { - return 0; - } + if (!duration || !duration.match(durationRegex)) { + return 0; + } - let [,years,months,,days,hours,minutes,seconds] = new RegExp(durationRegex).exec(duration) || []; + const [, 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); - } + const now = new Date(); + const 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) { + const milliseconds = Number(Number(seconds) % 1).toFixed(6) * 1000.0; + anchor.setMilliseconds(anchor.getMilliseconds() + milliseconds); + } - return (anchor - now) / 1000.0; + return (anchor - now) / 1000.0; }