scorm-again
Version:
A modern SCORM JavaScript run-time library for AICC, SCORM 1.2, and SCORM 2004
441 lines (400 loc) • 12 kB
text/typescript
import { CMI } from "./cmi/scorm12/cmi";
import * as Utilities from "./utilities";
import { stringMatches } from "./utilities";
import { global_constants, scorm12_constants } from "./constants/api_constants";
import { scorm12_errors } from "./constants/error_codes";
import { BaseCMI } from "./cmi/common/base_cmi";
import { CMIObjectivesObject } from "./cmi/scorm12/objectives";
import {
CMIInteractionsCorrectResponsesObject,
CMIInteractionsObject,
CMIInteractionsObjectivesObject
} from "./cmi/scorm12/interactions";
import { NAV } from "./cmi/scorm12/nav";
import { CommitObject, RefObject, ResultObject, ScoreObject, Settings } from "./types/api_types";
import { CompletionStatus, SuccessStatus } from "./constants/enums";
import BaseAPI from "./BaseAPI";
import { scorm12_regex } from "./constants/regex";
/**
* API class for SCORM 1.2
*/
class Scorm12Impl extends BaseAPI {
/**
* Constructor for SCORM 1.2 API
* @param {object} settings
*/
constructor(settings?: Settings) {
if (settings) {
if (settings.mastery_override === undefined) {
settings.mastery_override = false;
}
}
super(scorm12_errors, settings);
this.cmi = new CMI();
this.nav = new NAV();
// Rename functions to match 1.2 Spec and expose to modules
this.LMSInitialize = this.lmsInitialize;
this.LMSFinish = this.lmsFinish;
this.LMSGetValue = this.lmsGetValue;
this.LMSSetValue = this.lmsSetValue;
this.LMSCommit = this.lmsCommit;
this.LMSGetLastError = this.lmsGetLastError;
this.LMSGetErrorString = this.lmsGetErrorString;
this.LMSGetDiagnostic = this.lmsGetDiagnostic;
}
public statusSetByModule = false;
cmi: CMI;
nav: NAV;
LMSInitialize: () => string;
LMSFinish: () => string;
LMSGetValue: (CMIElement: string) => string;
LMSSetValue: (CMIElement: string, value: any) => string;
LMSCommit: () => string;
LMSGetLastError: () => string;
LMSGetErrorString: (CMIErrorCode: string) => string;
LMSGetDiagnostic: (CMIErrorCode: string) => string;
/**
* Called when the API needs to be reset
*/
reset(settings?: Settings) {
this.commonReset(settings);
this.cmi?.reset();
this.nav?.reset();
}
/**
* lmsInitialize function from SCORM 1.2 Spec
*
* @return {string} bool
*/
lmsInitialize(): string {
this.cmi.initialize();
if (this.cmi.core.lesson_status) {
this.statusSetByModule = true;
} else {
this.cmi.core.lesson_status = "not attempted";
}
return this.initialize(
"LMSInitialize",
"LMS was already initialized!",
"LMS is already finished!",
);
}
/**
* LMSFinish function from SCORM 1.2 Spec
*
* @return {string} bool
*/
lmsFinish(): string {
(async () => {
await this.internalFinish();
})();
return global_constants.SCORM_TRUE;
}
async internalFinish(): Promise<string> {
const result = await this.terminate("LMSFinish", true);
if (result === global_constants.SCORM_TRUE) {
if (this.nav.event !== "") {
if (this.nav.event === "continue") {
this.processListeners("SequenceNext");
} else {
this.processListeners("SequencePrevious");
}
} else if (this.settings.autoProgress) {
this.processListeners("SequenceNext");
}
}
return result;
}
/**
* LMSGetValue function from SCORM 1.2 Spec
*
* @param {string} CMIElement
* @return {string}
*/
lmsGetValue(CMIElement: string): string {
return this.getValue("LMSGetValue", false, CMIElement);
}
/**
* LMSSetValue function from SCORM 1.2 Spec
*
* @param {string} CMIElement
* @param {*} value
* @return {string}
*/
lmsSetValue(CMIElement: string, value: any): string {
if (CMIElement === "cmi.core.lesson_status") {
this.statusSetByModule = true;
}
return this.setValue("LMSSetValue", "LMSCommit", false, CMIElement, value);
}
/**
* LMSCommit function from SCORM 1.2 Spec
*
* @return {string} bool
*/
lmsCommit(): string {
if (this.settings.asyncCommit) {
this.scheduleCommit(500, "LMSCommit");
} else {
(async () => {
await this.commit("LMSCommit", false);
})();
}
return global_constants.SCORM_TRUE;
}
/**
* LMSGetLastError function from SCORM 1.2 Spec
*
* @return {string}
*/
lmsGetLastError(): string {
return this.getLastError("LMSGetLastError");
}
/**
* LMSGetErrorString function from SCORM 1.2 Spec
*
* @param {string} CMIErrorCode
* @return {string}
*/
lmsGetErrorString(CMIErrorCode: string): string {
return this.getErrorString("LMSGetErrorString", CMIErrorCode);
}
/**
* LMSGetDiagnostic function from SCORM 1.2 Spec
*
* @param {string} CMIErrorCode
* @return {string}
*/
lmsGetDiagnostic(CMIErrorCode: string): string {
return this.getDiagnostic("LMSGetDiagnostic", CMIErrorCode);
}
/**
* Sets a value on the CMI Object
*
* @param {string} CMIElement
* @param {*} value
* @return {string}
*/
override setCMIValue(CMIElement: string, value: any): string {
return this._commonSetCMIValue("LMSSetValue", false, CMIElement, value);
}
/**
* Gets a value from the CMI Object
*
* @param {string} CMIElement
* @return {*}
*/
override getCMIValue(CMIElement: string): any {
return this._commonGetCMIValue("getCMIValue", false, CMIElement);
}
/**
* Gets or builds a new child element to add to the array.
*
* @param {string} CMIElement
* @param {*} _value
* @param {boolean} foundFirstIndex
* @return {BaseCMI|null}
*/
getChildElement(
CMIElement: string,
_value: any,
foundFirstIndex: boolean,
): BaseCMI | null {
if (stringMatches(CMIElement, "cmi\\.objectives\\.\\d+")) {
return new CMIObjectivesObject();
} else if (
foundFirstIndex &&
stringMatches(
CMIElement,
"cmi\\.interactions\\.\\d+\\.correct_responses\\.\\d+",
)
) {
return new CMIInteractionsCorrectResponsesObject();
} else if (
foundFirstIndex &&
stringMatches(CMIElement, "cmi\\.interactions\\.\\d+\\.objectives\\.\\d+")
) {
return new CMIInteractionsObjectivesObject();
} else if (
!foundFirstIndex &&
stringMatches(CMIElement, "cmi\\.interactions\\.\\d+")
) {
return new CMIInteractionsObject();
}
return null;
}
/**
* Validates Correct Response values
*
* @param {string} _CMIElement
* @param {*} _value
*/
validateCorrectResponse(_CMIElement: string, _value: any) {
// do nothing
}
/**
* Returns the message that corresponds to errorNumber.
*
* @param {number|string} errorNumber
* @param {boolean} detail
* @return {string}
*/
override getLmsErrorMessageDetails(
errorNumber: number | string,
detail: boolean,
): string {
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 (scorm12_constants.error_descriptions[errorNumber]) {
basicMessage =
scorm12_constants.error_descriptions[errorNumber]?.basicMessage ||
"General Error";
detailMessage =
scorm12_constants.error_descriptions[errorNumber]?.detailMessage || "";
}
return detail ? detailMessage : basicMessage;
}
/**
* Replace the whole API with another
*
* @param {Scorm12Impl} newAPI
*/
replaceWithAnotherScormAPI(newAPI: Scorm12Impl) {
// Data Model
this.cmi = newAPI.cmi;
}
/**
* Render the cmi object to the proper format for LMS commit
*
* @param {boolean} terminateCommit
* @return {object|Array}
*/
renderCommitCMI(terminateCommit: boolean): object | Array<any> {
const cmiExport: RefObject = this.renderCMIToJSONObject();
if (terminateCommit) {
cmiExport.cmi.core.total_time = this.cmi.getCurrentTotalTime();
}
const result = [];
const flattened: RefObject = Utilities.flatten(cmiExport);
switch (this.settings.dataCommitFormat) {
case "flattened":
return Utilities.flatten(cmiExport);
case "params":
for (const item in flattened) {
if ({}.hasOwnProperty.call(flattened, item)) {
result.push(`${item}=${flattened[item]}`);
}
}
return result;
case "json":
default:
return cmiExport;
}
}
/**
* Render the cmi object to the proper format for LMS commit
* @param {boolean} terminateCommit
* @return {CommitObject}
*/
renderCommitObject(terminateCommit: boolean): CommitObject {
const cmiExport = this.renderCommitCMI(terminateCommit);
const totalTimeHHMMSS = this.cmi.getCurrentTotalTime();
const totalTimeSeconds = Utilities.getTimeAsSeconds(
totalTimeHHMMSS,
scorm12_regex.CMITimespan,
);
const lessonStatus = this.cmi.core.lesson_status;
let completionStatus = CompletionStatus.unknown;
let successStatus = SuccessStatus.unknown;
if (lessonStatus) {
completionStatus =
lessonStatus === "completed" || lessonStatus === "passed"
? CompletionStatus.completed
: CompletionStatus.incomplete;
if (lessonStatus === "passed") {
successStatus = SuccessStatus.passed;
} else if (lessonStatus === "failed") {
successStatus = SuccessStatus.failed;
}
}
const score = this.cmi.core.score;
const scoreObject: ScoreObject = {};
if (score) {
if (!Number.isNaN(Number.parseFloat(score.raw))) {
scoreObject.raw = Number.parseFloat(score.raw);
}
if (!Number.isNaN(Number.parseFloat(score.min))) {
scoreObject.min = Number.parseFloat(score.min);
}
if (!Number.isNaN(Number.parseFloat(score.max))) {
scoreObject.max = Number.parseFloat(score.max);
}
}
const commitObject: CommitObject = {
successStatus: successStatus,
completionStatus: completionStatus,
runtimeData: cmiExport,
totalTimeSeconds: totalTimeSeconds,
};
if (scoreObject) {
commitObject.score = scoreObject;
}
return commitObject;
}
/**
* Attempts to store the data to the LMS
*
* @param {boolean} terminateCommit
* @return {ResultObject}
*/
async storeData(terminateCommit: boolean): Promise<ResultObject> {
if (terminateCommit) {
const originalStatus = this.cmi.core.lesson_status;
if (
!this.cmi.core.lesson_status ||
(!this.statusSetByModule &&
this.cmi.core.lesson_status === "not attempted")
) {
this.cmi.core.lesson_status = "completed";
}
if (this.cmi.core.lesson_mode === "normal") {
if (this.cmi.core.credit === "credit") {
if (
this.settings.mastery_override &&
this.cmi.student_data.mastery_score !== "" &&
this.cmi.core.score.raw !== ""
) {
this.cmi.core.lesson_status =
parseFloat(this.cmi.core.score.raw) >=
parseFloat(this.cmi.student_data.mastery_score)
? "passed"
: "failed";
}
}
} else if (this.cmi.core.lesson_mode === "browse") {
if (
(this.startingData?.cmi?.core?.lesson_status || "") === "" &&
originalStatus === "not attempted"
) {
this.cmi.core.lesson_status = "browsed";
}
}
}
const commitObject = this.getCommitObject(terminateCommit);
if (typeof this.settings.lmsCommitUrl === "string") {
return await this.processHttpRequest(
this.settings.lmsCommitUrl,
commitObject,
terminateCommit,
);
} else {
return {
result: global_constants.SCORM_TRUE,
errorCode: 0,
};
}
}
}
export { Scorm12Impl as Scorm12API };