UNPKG

@szenadam/scorm-api-wrapper

Version:
657 lines (573 loc) 17.5 kB
class ScormApiWrapper { /** * The API (SCORM 1.2) or API_1484_11 (SCORM 2004) object which contains the SCORM Runtime API. */ private apiHandle: any; /** * The SCORM Runtime version. */ private scormVersion: string; /** * Turn on/off logging in trace(). */ private debugModeEnabled: boolean; /** * Should the wrapper handle completion status when running initialize(). */ private handleCompletionStatus: boolean; /** * Should the wrapper handle exit mode when running terminate(). */ private handleExitMode: boolean; /** * True is the wrapper did find the SCORM Runtime API. */ private apiIsFound: boolean; /** * Is the connection is active or not. */ private connectionIsActive: boolean; /** * Completion status value. */ private dataCompletionStatus: any; /** * Exit mode value. */ private dataExitStatus: any; constructor(debug: boolean) { this.debugModeEnabled = debug; this.scormVersion = ''; this.apiHandle = null; this.dataCompletionStatus = null; this.dataExitStatus = null; this.handleCompletionStatus = true; this.handleExitMode = true; this.apiIsFound = false; this.connectionIsActive = false; } /** * Tells the LMS to initiate the communication session. */ public initialize(): boolean { let success: boolean | null = false; let completionStatus: any; const traceMsgPrefix = 'ScormApiWrapper.initialize '; this.trace('initialize called.'); if (!this.connectionIsActive) { const API = this.getApiHandle(); let errorCode = 0; if (API) { switch (this.scormVersion) { case '1.2': success = this.stringToBoolean(API.LMSInitialize('')); break; case '2004': success = this.stringToBoolean(API.Initialize('')); break; } if (success) { errorCode = this.getCode(); if (errorCode !== null && errorCode === 0) { this.connectionIsActive = true; if (this.handleCompletionStatus) { completionStatus = this.getStatus(); if (completionStatus) { switch (completionStatus) { case 'not attempted': case 'unknown': this.setStatus('incomplete'); break; // Additional options, presented here in case you'd like to use them // case "completed" : break; // case "incomplete" : break; // case "passed" : break; //SCORM 1.2 only // case "failed" : break; //SCORM 1.2 only // case "browsed" : break; //SCORM 1.2 only } this.save(); } } } else { success = false; this.trace( traceMsgPrefix + 'failed. \nError code: ' + errorCode + ' \nError info: ' + this.getInfo(errorCode) ); } } else { errorCode = this.getCode(); if (errorCode !== null && errorCode !== 0) { this.trace( traceMsgPrefix + 'failed. \nError code: ' + errorCode + ' \nError info: ' + this.getInfo(errorCode) ); } else { this.trace(traceMsgPrefix + 'failed: No response from server.'); } } } else { this.trace(traceMsgPrefix + 'failed: API is null.'); } } else { this.trace(traceMsgPrefix + 'aborted: Connection already active.'); } return success; } /** * Tells the LMS to terminate the communication session. */ public terminate(): boolean { let success = false; const exitStatus = this.dataExitStatus; const completionStatus = this.dataCompletionStatus; const traceMsgPrefix = 'terminate '; if (this.connectionIsActive) { const API = this.getApiHandle(); let errorCode = 0; if (API) { if (this.handleExitMode && !exitStatus) { if (completionStatus !== 'completed' && completionStatus !== 'passed') { switch (this.scormVersion) { case '1.2': this.dataSet('cmi.core.exit', 'suspend'); break; case '2004': this.dataSet('cmi.exit', 'suspend'); break; } } else { switch (this.scormVersion) { case '1.2': this.dataSet('cmi.core.exit', 'logout'); break; case '2004': this.dataSet('cmi.exit', 'normal'); break; } } } success = this.scormVersion === '1.2' ? this.save() : true; if (success) { switch (this.scormVersion) { case '1.2': success = this.stringToBoolean(API.LMSFinish('')); break; case '2004': success = this.stringToBoolean(API.Terminate('')); break; } if (success) { this.connectionIsActive = false; } else { errorCode = this.getCode(); this.trace( traceMsgPrefix + 'failed. \nError code: ' + errorCode + ' \nError info: ' + this.getInfo(errorCode) ); } } } else { this.trace(traceMsgPrefix + 'failed: API is null.'); } } else { this.trace(traceMsgPrefix + 'aborted: Connection already terminated.'); } return success; } /** * A simple function to allow Flash ExternalInterface to confirm * presence of JS wrapper before attempting any LMS communication. */ public isAvailable(): boolean { return true; } /** * Looks for an object named API (SCORM 1.2) or API_1483_11 (SCORM 2004) in parent and opener windows. * * @param win the window object */ public findApi(win: any): any { let API = null; let findAttempts = 0; const findAttemptLimit = 500; const traceMsgPrefix = 'ScormApiWrapper.find'; while ( !win.API && !win.API_1484_11 && win.parent && win.parent != win && findAttempts <= findAttemptLimit ) { findAttempts++; win = win.parent; } if (this.scormVersion) { switch (this.scormVersion) { case '2004': if (win.API_1484_11) { API = win.API_1484_11; } else { this.trace( traceMsgPrefix + ': SCORM version 2004 was specified by user, but API_1484_11 cannot be found.' ); } break; case '1.2': if (win.API) { API = win.API; } else { this.trace( traceMsgPrefix + ': SCORM version 1.2 was specified by user, but API cannot be found.' ); } break; } } else { if (win.API_1484_11) { this.scormVersion = '2004'; API = win.API_1484_11; } else if (win.API) { this.scormVersion = '1.2'; API = win.API; } } if (API) { this.trace(traceMsgPrefix + ': API found. Version: ' + this.scormVersion); this.trace('API: ' + API); } else { this.trace( traceMsgPrefix + ': Error finding API. \nFind attempts: ' + findAttempts + '. \nFind attempt limit: ' + findAttemptLimit ); } return API; } /** * Looks for an object named API (SCORM 1.2) or API_1483_11 (SCORM 2004), first in the current window's frame * hierarchy and then, if necessary, in the current window's opener window * hierarchy (if there is an opener window). */ public getApi(): any { let API: null; const win = window; API = this.findApi(win); if (!API && win.parent && win.parent != win) { API = this.findApi(win.parent); } if (!API && win.top && win.top.opener) { API = this.findApi(win.top.opener); } if (!API && win.top && win.top.opener && win.top.opener.document) { API = this.findApi(win.top.opener.document); } if (API) { this.apiIsFound = true; } else { this.trace('API.get failed: Can\'t find the API!'); } return API; } /** * Returns the handle to API (SCORM 1.2) or API_1483_11 (SCORM 2004) object if it was previously set. */ public getApiHandle(): any { if (!this.apiHandle && !this.apiIsFound) { this.apiHandle = this.getApi(); } return this.apiHandle; } /** * Requests the error code for the current error state from the LMS. */ public getCode(): number { const API = this.getApiHandle(); let code = 0; if (API) { switch (this.scormVersion) { case '1.2': code = parseInt(API.LMSGetLastError(), 10); break; case '2004': code = parseInt(API.GetLastError(), 10); break; } } else { this.trace('ScormApiWrapper.getCode failed: API is null.'); } return code; } /** * Requests information from the LMS. * * Side effects: * - Sets the class property dataCompletionStatus when "cmi.core.lesson_status" (SCORM 1.2) or "cmi.completion_status" * (SCORM 2004) is requested. * - Also sets class property dataExitStatus when "cmi.core.exit" (SCORM 1.2) or "cmi.exit" * (SCORM 2004) is requested. * * @param {string} parameter parameter name of the SCORM data model element */ public dataGet(parameter: string): string { let value = ''; const traceMsgPrefix = 'ScormApiWrapper.dataGet(\'' + parameter + '\') '; if (this.connectionIsActive) { const API = this.getApiHandle(); let errorCode = 0; if (API) { switch (this.scormVersion) { case '1.2': value = API.LMSGetValue(parameter); break; case '2004': value = API.GetValue(parameter); break; } errorCode = this.getCode(); if (value !== '' || errorCode === 0) { switch (parameter) { case 'cmi.core.lesson_status': case 'cmi.completion_status': this.dataCompletionStatus = value; break; case 'cmi.core.exit': case 'cmi.exit': this.dataExitStatus = value; break; } } else { this.trace( traceMsgPrefix + 'failed. \nError code: ' + errorCode + '\nError info: ' + this.getInfo(errorCode) ); } } else { this.trace(traceMsgPrefix + 'failed: API is null.'); } } else { this.trace(traceMsgPrefix + 'failed: API connection is inactive.'); } this.trace(traceMsgPrefix + ' value: ' + value); return String(value); } /** * Tells the LMS to assign the value to the named data model element. * Also stores the SCO's completion status in a variable named * ScormApiWrapper.completionStatus. This variable is checked whenever * ScormApiWrapper.terminate() is invoked. * * @param parameter {string} The data model element * @param value {string} The value for the data model element */ public dataSet(parameter: string, value: string): boolean { let success: boolean = false; const traceMsgPrefix = 'ScormApiWrapper.dataSet(\'' + parameter + '\') '; if (this.connectionIsActive) { const API = this.getApiHandle(); let errorCode = 0; if (API) { switch (this.scormVersion) { case '1.2': success = this.stringToBoolean(API.LMSSetValue(parameter, value)); break; case '2004': success = this.stringToBoolean((API.SetValue(parameter, value))); break; } if (success) { if ( parameter === 'cmi.core.lesson_status' || parameter === 'cmi.completion_status' ) { this.dataCompletionStatus = value; } } else { errorCode = this.getCode(); this.trace( traceMsgPrefix + 'failed. \nError code: ' + errorCode + '. \nError info: ' + this.getInfo(errorCode) ); } } else { this.trace(traceMsgPrefix + 'failed: API is null.'); } } else { this.trace(traceMsgPrefix + 'failed: API connection is inactive.'); } this.trace(traceMsgPrefix + ' value: ' + value); return success; } /** * Get completion status value. * * @returns Current completion status value. */ public getStatus(): string { let status: string; let cmi = ''; switch (this.scormVersion) { case '1.2': cmi = 'cmi.core.lesson_status'; break; case '2004': cmi = 'cmi.completion_status'; break; } status = this.dataGet(cmi); return status; } /** * Set completion status. * * @param status completion status we want to set. * @returns */ public setStatus(status: string): boolean { let success: boolean let cmi = ''; const traceMsgPrefix = 'ScormApiWrapper.setStatus failed' switch (this.scormVersion) { case '1.2': cmi = 'cmi.core.lesson_status'; break; case '2004': cmi = 'cmi.completion_status'; break; } if (status !== '') { success = this.dataSet(cmi, status); } else { success = false; this.trace(traceMsgPrefix + ': status was not specified.'); } return success; } /** * Instructs the LMS to persist all data to this point in the session. */ public save(): boolean { let success: boolean = false; const traceMsgPrefix = 'ScormApiWrapper.save failed'; if (this.connectionIsActive) { const API = this.getApiHandle(); if (API) { switch (this.scormVersion) { case '1.2': success = this.stringToBoolean(API.LMSCommit('')); break; case '2004': success = this.stringToBoolean(API.Commit('')); break; } } else { this.trace(traceMsgPrefix + ': API is null.'); } } else { this.trace(traceMsgPrefix + ': API connection is inactive.'); } return success; } /** * Displays error messages when in debug mode. * * @param msg message to be displayed */ public trace(msg: string): void { if (this.debugModeEnabled) { if (console && console.log) { console.log(msg); } else { // ignored } } } /** * "Used by a SCO to request the textual description for the error code * specified by the value of [errorCode]." * * @param errorCode {number} */ public getInfo(errorCode: number): string { const API = this.getApiHandle(); let result = ''; if (API) { switch (this.scormVersion) { case '1.2': result = API.LMSGetErrorString(errorCode.toString()); break; case '2004': result = API.GetErrorString(errorCode.toString()); break; } } else { this.trace('ScormApiWrapper.getInfo failed: API is null.'); } return String(result); } /** * "Exists for LMS specific use. It allows the LMS to define additional * diagnostic information through the API Instance." * * @param {number} errorCode */ public getDiagnosticInfo(errorCode: number): string { const API = this.getApiHandle(); let result = ''; if (API) { switch (this.scormVersion) { case '1.2': result = API.LMSGetDiagnostic(errorCode); break; case '2004': result = API.GetDiagnostic(errorCode); break; } } else { this.trace('ScormApiWrapper.getDiagnosticInfo failed: API is null.'); } return String(result); } /** * Converts 'boolean strings' into actual valid booleans. * (Most values returned from the API are the strings "true" and "false".) * * @param value */ public stringToBoolean(value: any): boolean { const valueType = typeof value; switch (valueType) { case 'object': case 'string': return /(true|1)/i.test(value); case 'number': return !!value; case 'boolean': return value; case 'undefined': return false; default: return false; } } } export default ScormApiWrapper;