scorm-again 1.7.1 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.babelrc +18 -7
- package/.github/dependabot.yml +5 -0
- package/.github/workflows/main.yml +79 -0
- package/.jsdoc.json +4 -5
- package/.mocharc.json +8 -0
- package/.run/Mocha Unit Tests.run.xml +5 -2
- package/CONTRIBUTING.md +1 -1
- package/README.md +14 -1
- package/dist/aicc.js +3661 -7170
- package/dist/aicc.js.map +1 -1
- package/dist/aicc.min.js +2 -40
- package/dist/aicc.min.js.map +1 -0
- package/dist/scorm-again.js +5671 -10695
- package/dist/scorm-again.js.map +1 -1
- package/dist/scorm-again.min.js +2 -52
- package/dist/scorm-again.min.js.map +1 -0
- package/dist/scorm12.js +2871 -5433
- package/dist/scorm12.js.map +1 -1
- package/dist/scorm12.min.js +2 -34
- package/dist/scorm12.min.js.map +1 -0
- package/dist/scorm2004.js +3868 -6797
- package/dist/scorm2004.js.map +1 -1
- package/dist/scorm2004.min.js +2 -40
- package/dist/scorm2004.min.js.map +1 -0
- package/eslint.config.js +21 -0
- package/package.json +72 -34
- package/results.json +34254 -0
- package/src/{AICC.js → AICC.ts} +27 -21
- package/src/BaseAPI.ts +1449 -0
- package/src/Scorm12API.ts +360 -0
- package/src/{Scorm2004API.js → Scorm2004API.ts} +245 -163
- package/src/cmi/aicc_cmi.ts +1248 -0
- package/src/cmi/common.ts +411 -0
- package/src/cmi/scorm12_cmi.ts +1426 -0
- package/src/cmi/scorm2004_cmi.ts +1874 -0
- package/src/constants/api_constants.ts +318 -0
- package/src/constants/error_codes.ts +88 -0
- package/src/constants/language_constants.ts +394 -0
- package/src/constants/regex.ts +97 -0
- package/src/constants/{response_constants.js → response_constants.ts} +67 -62
- package/src/exceptions.ts +133 -0
- package/src/exports/aicc.js +1 -1
- package/src/exports/scorm-again.js +3 -3
- package/src/exports/scorm12.js +1 -1
- package/src/exports/scorm2004.js +1 -1
- package/src/{utilities.js → utilities.ts} +114 -74
- package/tea.yaml +6 -0
- package/test/{AICC.spec.js → AICC.spec.ts} +70 -72
- package/test/Scorm12API.spec.ts +580 -0
- package/test/Scorm2004API.spec.ts +812 -0
- package/test/api_helpers.ts +176 -0
- package/test/cmi/{aicc_cmi.spec.js → aicc_cmi.spec.ts} +193 -209
- package/test/cmi/{scorm12_cmi.spec.js → scorm12_cmi.spec.ts} +251 -269
- package/test/cmi/scorm2004_cmi.spec.ts +1031 -0
- package/test/cmi_helpers.ts +207 -0
- package/test/exceptions.spec.ts +79 -0
- package/test/field_values.ts +202 -0
- package/test/utilities.spec.ts +322 -0
- package/tsconfig.json +18 -0
- package/webpack.config.js +65 -0
- package/.circleci/config.yml +0 -99
- package/.codeclimate.yml +0 -7
- package/.eslintrc.js +0 -36
- package/src/.flowconfig +0 -11
- package/src/BaseAPI.js +0 -1275
- package/src/Scorm12API.js +0 -308
- package/src/cmi/aicc_cmi.js +0 -1141
- package/src/cmi/common.js +0 -328
- package/src/cmi/scorm12_cmi.js +0 -1312
- package/src/cmi/scorm2004_cmi.js +0 -1692
- package/src/constants/api_constants.js +0 -218
- package/src/constants/error_codes.js +0 -87
- package/src/constants/language_constants.js +0 -76
- package/src/constants/regex.js +0 -84
- package/src/exceptions.js +0 -104
- package/test/Scorm12API.spec.js +0 -528
- package/test/Scorm2004API.spec.js +0 -775
- package/test/abstract_classes.spec.js +0 -17
- package/test/api_helpers.js +0 -128
- package/test/cmi/scorm2004_cmi.spec.js +0 -1066
- package/test/cmi_helpers.js +0 -161
- package/test/exceptions.spec.js +0 -71
- package/test/field_values.js +0 -353
- package/test/utilities.spec.js +0 -339
- package/webpack.js +0 -78
package/src/BaseAPI.js
DELETED
|
@@ -1,1275 +0,0 @@
|
|
|
1
|
-
// @flow
|
|
2
|
-
import {CMIArray} from './cmi/common';
|
|
3
|
-
import {ValidationError} from './exceptions';
|
|
4
|
-
import ErrorCodes from './constants/error_codes';
|
|
5
|
-
import APIConstants from './constants/api_constants';
|
|
6
|
-
import {unflatten} from './utilities';
|
|
7
|
-
import debounce from 'lodash.debounce';
|
|
8
|
-
|
|
9
|
-
const global_constants = APIConstants.global;
|
|
10
|
-
const scorm12_error_codes = ErrorCodes.scorm12;
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Base API class for AICC, SCORM 1.2, and SCORM 2004. Should be considered
|
|
14
|
-
* abstract, and never initialized on it's own.
|
|
15
|
-
*/
|
|
16
|
-
export default class BaseAPI {
|
|
17
|
-
#timeout;
|
|
18
|
-
#error_codes;
|
|
19
|
-
#settings = {
|
|
20
|
-
autocommit: false,
|
|
21
|
-
autocommitSeconds: 10,
|
|
22
|
-
asyncCommit: false,
|
|
23
|
-
sendBeaconCommit: false,
|
|
24
|
-
lmsCommitUrl: false,
|
|
25
|
-
dataCommitFormat: 'json', // valid formats are 'json' or 'flattened', 'params'
|
|
26
|
-
commitRequestDataType: 'application/json;charset=UTF-8',
|
|
27
|
-
autoProgress: false,
|
|
28
|
-
logLevel: global_constants.LOG_LEVEL_ERROR,
|
|
29
|
-
selfReportSessionTime: false,
|
|
30
|
-
alwaysSendTotalTime: false,
|
|
31
|
-
strict_errors: true,
|
|
32
|
-
xhrHeaders: {},
|
|
33
|
-
xhrWithCredentials: false,
|
|
34
|
-
responseHandler: function(xhr) {
|
|
35
|
-
let result;
|
|
36
|
-
if (typeof xhr !== 'undefined') {
|
|
37
|
-
result = JSON.parse(xhr.responseText);
|
|
38
|
-
if (result === null || !{}.hasOwnProperty.call(result, 'result')) {
|
|
39
|
-
result = {};
|
|
40
|
-
if (xhr.status === 200) {
|
|
41
|
-
result.result = global_constants.SCORM_TRUE;
|
|
42
|
-
result.errorCode = 0;
|
|
43
|
-
} else {
|
|
44
|
-
result.result = global_constants.SCORM_FALSE;
|
|
45
|
-
result.errorCode = 101;
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
return result;
|
|
50
|
-
},
|
|
51
|
-
requestHandler: function(commitObject) {
|
|
52
|
-
return commitObject;
|
|
53
|
-
},
|
|
54
|
-
onLogMessage: function(messageLevel, logMessage) {
|
|
55
|
-
switch (messageLevel) {
|
|
56
|
-
case global_constants.LOG_LEVEL_ERROR:
|
|
57
|
-
console.error(logMessage);
|
|
58
|
-
break;
|
|
59
|
-
case global_constants.LOG_LEVEL_WARNING:
|
|
60
|
-
console.warn(logMessage);
|
|
61
|
-
break;
|
|
62
|
-
case global_constants.LOG_LEVEL_INFO:
|
|
63
|
-
console.info(logMessage);
|
|
64
|
-
break;
|
|
65
|
-
case global_constants.LOG_LEVEL_DEBUG:
|
|
66
|
-
if (console.debug) {
|
|
67
|
-
console.debug(logMessage);
|
|
68
|
-
} else {
|
|
69
|
-
console.log(logMessage);
|
|
70
|
-
}
|
|
71
|
-
break;
|
|
72
|
-
}
|
|
73
|
-
},
|
|
74
|
-
};
|
|
75
|
-
cmi;
|
|
76
|
-
startingData: {};
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* Constructor for Base API class. Sets some shared API fields, as well as
|
|
80
|
-
* sets up options for the API.
|
|
81
|
-
* @param {object} error_codes
|
|
82
|
-
* @param {object} settings
|
|
83
|
-
*/
|
|
84
|
-
constructor(error_codes, settings) {
|
|
85
|
-
if (new.target === BaseAPI) {
|
|
86
|
-
throw new TypeError('Cannot construct BaseAPI instances directly');
|
|
87
|
-
}
|
|
88
|
-
this.currentState = global_constants.STATE_NOT_INITIALIZED;
|
|
89
|
-
this.lastErrorCode = 0;
|
|
90
|
-
this.listenerArray = [];
|
|
91
|
-
|
|
92
|
-
this.#timeout = null;
|
|
93
|
-
this.#error_codes = error_codes;
|
|
94
|
-
|
|
95
|
-
this.settings = settings;
|
|
96
|
-
this.apiLogLevel = this.settings.logLevel;
|
|
97
|
-
this.selfReportSessionTime = this.settings.selfReportSessionTime;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
/**
|
|
101
|
-
* Initialize the API
|
|
102
|
-
* @param {string} callbackName
|
|
103
|
-
* @param {string} initializeMessage
|
|
104
|
-
* @param {string} terminationMessage
|
|
105
|
-
* @return {string}
|
|
106
|
-
*/
|
|
107
|
-
initialize(
|
|
108
|
-
callbackName: String,
|
|
109
|
-
initializeMessage?: String,
|
|
110
|
-
terminationMessage?: String) {
|
|
111
|
-
let returnValue = global_constants.SCORM_FALSE;
|
|
112
|
-
|
|
113
|
-
if (this.isInitialized()) {
|
|
114
|
-
this.throwSCORMError(this.#error_codes.INITIALIZED, initializeMessage);
|
|
115
|
-
} else if (this.isTerminated()) {
|
|
116
|
-
this.throwSCORMError(this.#error_codes.TERMINATED, terminationMessage);
|
|
117
|
-
} else {
|
|
118
|
-
if (this.selfReportSessionTime) {
|
|
119
|
-
this.cmi.setStartTime();
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
this.currentState = global_constants.STATE_INITIALIZED;
|
|
123
|
-
this.lastErrorCode = 0;
|
|
124
|
-
returnValue = global_constants.SCORM_TRUE;
|
|
125
|
-
this.processListeners(callbackName);
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
this.apiLog(callbackName, null, 'returned: ' + returnValue,
|
|
129
|
-
global_constants.LOG_LEVEL_INFO);
|
|
130
|
-
this.clearSCORMError(returnValue);
|
|
131
|
-
|
|
132
|
-
return returnValue;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
/**
|
|
136
|
-
* Getter for #error_codes
|
|
137
|
-
* @return {object}
|
|
138
|
-
*/
|
|
139
|
-
get error_codes() {
|
|
140
|
-
return this.#error_codes;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
/**
|
|
144
|
-
* Getter for #settings
|
|
145
|
-
* @return {object}
|
|
146
|
-
*/
|
|
147
|
-
get settings() {
|
|
148
|
-
return this.#settings;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
/**
|
|
152
|
-
* Setter for #settings
|
|
153
|
-
* @param {object} settings
|
|
154
|
-
*/
|
|
155
|
-
set settings(settings: Object) {
|
|
156
|
-
this.#settings = {...this.#settings, ...settings};
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
/**
|
|
160
|
-
* Terminates the current run of the API
|
|
161
|
-
* @param {string} callbackName
|
|
162
|
-
* @param {boolean} checkTerminated
|
|
163
|
-
* @return {string}
|
|
164
|
-
*/
|
|
165
|
-
terminate(
|
|
166
|
-
callbackName: String,
|
|
167
|
-
checkTerminated: boolean) {
|
|
168
|
-
let returnValue = global_constants.SCORM_FALSE;
|
|
169
|
-
|
|
170
|
-
if (this.checkState(checkTerminated,
|
|
171
|
-
this.#error_codes.TERMINATION_BEFORE_INIT,
|
|
172
|
-
this.#error_codes.MULTIPLE_TERMINATION)) {
|
|
173
|
-
this.currentState = global_constants.STATE_TERMINATED;
|
|
174
|
-
|
|
175
|
-
const result = this.storeData(true);
|
|
176
|
-
if (!this.settings.sendBeaconCommit && !this.settings.asyncCommit &&
|
|
177
|
-
typeof result.errorCode !== 'undefined' && result.errorCode > 0) {
|
|
178
|
-
this.throwSCORMError(result.errorCode);
|
|
179
|
-
}
|
|
180
|
-
returnValue = (typeof result !== 'undefined' && result.result) ?
|
|
181
|
-
result.result : global_constants.SCORM_FALSE;
|
|
182
|
-
|
|
183
|
-
if (checkTerminated) this.lastErrorCode = 0;
|
|
184
|
-
|
|
185
|
-
returnValue = global_constants.SCORM_TRUE;
|
|
186
|
-
this.processListeners(callbackName);
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
this.apiLog(callbackName, null, 'returned: ' + returnValue,
|
|
190
|
-
global_constants.LOG_LEVEL_INFO);
|
|
191
|
-
this.clearSCORMError(returnValue);
|
|
192
|
-
|
|
193
|
-
return returnValue;
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
/**
|
|
197
|
-
* Get the value of the CMIElement.
|
|
198
|
-
*
|
|
199
|
-
* @param {string} callbackName
|
|
200
|
-
* @param {boolean} checkTerminated
|
|
201
|
-
* @param {string} CMIElement
|
|
202
|
-
* @return {string}
|
|
203
|
-
*/
|
|
204
|
-
getValue(
|
|
205
|
-
callbackName: String,
|
|
206
|
-
checkTerminated: boolean,
|
|
207
|
-
CMIElement: String) {
|
|
208
|
-
let returnValue;
|
|
209
|
-
|
|
210
|
-
if (this.checkState(checkTerminated,
|
|
211
|
-
this.#error_codes.RETRIEVE_BEFORE_INIT,
|
|
212
|
-
this.#error_codes.RETRIEVE_AFTER_TERM)) {
|
|
213
|
-
if (checkTerminated) this.lastErrorCode = 0;
|
|
214
|
-
try {
|
|
215
|
-
returnValue = this.getCMIValue(CMIElement);
|
|
216
|
-
} catch (e) {
|
|
217
|
-
if (e instanceof ValidationError) {
|
|
218
|
-
this.lastErrorCode = e.errorCode;
|
|
219
|
-
returnValue = global_constants.SCORM_FALSE;
|
|
220
|
-
} else {
|
|
221
|
-
if (e.message) {
|
|
222
|
-
console.error(e.message);
|
|
223
|
-
} else {
|
|
224
|
-
console.error(e);
|
|
225
|
-
}
|
|
226
|
-
this.throwSCORMError(this.#error_codes.GENERAL);
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
this.processListeners(callbackName, CMIElement);
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
this.apiLog(callbackName, CMIElement, ': returned: ' + returnValue,
|
|
233
|
-
global_constants.LOG_LEVEL_INFO);
|
|
234
|
-
this.clearSCORMError(returnValue);
|
|
235
|
-
|
|
236
|
-
return returnValue;
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
/**
|
|
240
|
-
* Sets the value of the CMIElement.
|
|
241
|
-
*
|
|
242
|
-
* @param {string} callbackName
|
|
243
|
-
* @param {string} commitCallback
|
|
244
|
-
* @param {boolean} checkTerminated
|
|
245
|
-
* @param {string} CMIElement
|
|
246
|
-
* @param {*} value
|
|
247
|
-
* @return {string}
|
|
248
|
-
*/
|
|
249
|
-
setValue(
|
|
250
|
-
callbackName: String,
|
|
251
|
-
commitCallback: String,
|
|
252
|
-
checkTerminated: boolean,
|
|
253
|
-
CMIElement,
|
|
254
|
-
value) {
|
|
255
|
-
if (value !== undefined) {
|
|
256
|
-
value = String(value);
|
|
257
|
-
}
|
|
258
|
-
let returnValue = global_constants.SCORM_FALSE;
|
|
259
|
-
|
|
260
|
-
if (this.checkState(checkTerminated, this.#error_codes.STORE_BEFORE_INIT,
|
|
261
|
-
this.#error_codes.STORE_AFTER_TERM)) {
|
|
262
|
-
if (checkTerminated) this.lastErrorCode = 0;
|
|
263
|
-
try {
|
|
264
|
-
returnValue = this.setCMIValue(CMIElement, value);
|
|
265
|
-
} catch (e) {
|
|
266
|
-
if (e instanceof ValidationError) {
|
|
267
|
-
this.lastErrorCode = e.errorCode;
|
|
268
|
-
returnValue = global_constants.SCORM_FALSE;
|
|
269
|
-
} else {
|
|
270
|
-
if (e.message) {
|
|
271
|
-
console.error(e.message);
|
|
272
|
-
} else {
|
|
273
|
-
console.error(e);
|
|
274
|
-
}
|
|
275
|
-
this.throwSCORMError(this.#error_codes.GENERAL);
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
this.processListeners(callbackName, CMIElement, value);
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
if (returnValue === undefined) {
|
|
282
|
-
returnValue = global_constants.SCORM_FALSE;
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
// If we didn't have any errors while setting the data, go ahead and
|
|
286
|
-
// schedule a commit, if autocommit is turned on
|
|
287
|
-
if (String(this.lastErrorCode) === '0') {
|
|
288
|
-
if (this.settings.autocommit && !this.#timeout) {
|
|
289
|
-
this.scheduleCommit(this.settings.autocommitSeconds * 1000, commitCallback);
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
this.apiLog(callbackName, CMIElement,
|
|
294
|
-
': ' + value + ': result: ' + returnValue,
|
|
295
|
-
global_constants.LOG_LEVEL_INFO);
|
|
296
|
-
this.clearSCORMError(returnValue);
|
|
297
|
-
|
|
298
|
-
return returnValue;
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
/**
|
|
302
|
-
* Orders LMS to store all content parameters
|
|
303
|
-
* @param {string} callbackName
|
|
304
|
-
* @param {boolean} checkTerminated
|
|
305
|
-
* @return {string}
|
|
306
|
-
*/
|
|
307
|
-
commit(
|
|
308
|
-
callbackName: String,
|
|
309
|
-
checkTerminated: boolean) {
|
|
310
|
-
this.clearScheduledCommit();
|
|
311
|
-
|
|
312
|
-
let returnValue = global_constants.SCORM_FALSE;
|
|
313
|
-
|
|
314
|
-
if (this.checkState(checkTerminated, this.#error_codes.COMMIT_BEFORE_INIT,
|
|
315
|
-
this.#error_codes.COMMIT_AFTER_TERM)) {
|
|
316
|
-
const result = this.storeData(false);
|
|
317
|
-
if (!this.settings.sendBeaconCommit && !this.settings.asyncCommit &&
|
|
318
|
-
result.errorCode && result.errorCode > 0) {
|
|
319
|
-
this.throwSCORMError(result.errorCode);
|
|
320
|
-
}
|
|
321
|
-
returnValue = (typeof result !== 'undefined' && result.result) ?
|
|
322
|
-
result.result : global_constants.SCORM_FALSE;
|
|
323
|
-
|
|
324
|
-
this.apiLog(callbackName, 'HttpRequest', ' Result: ' + returnValue,
|
|
325
|
-
global_constants.LOG_LEVEL_DEBUG);
|
|
326
|
-
|
|
327
|
-
if (checkTerminated) this.lastErrorCode = 0;
|
|
328
|
-
|
|
329
|
-
this.processListeners(callbackName);
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
this.apiLog(callbackName, null, 'returned: ' + returnValue,
|
|
333
|
-
global_constants.LOG_LEVEL_INFO);
|
|
334
|
-
this.clearSCORMError(returnValue);
|
|
335
|
-
|
|
336
|
-
return returnValue;
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
/**
|
|
340
|
-
* Returns last error code
|
|
341
|
-
* @param {string} callbackName
|
|
342
|
-
* @return {string}
|
|
343
|
-
*/
|
|
344
|
-
getLastError(callbackName: String) {
|
|
345
|
-
const returnValue = String(this.lastErrorCode);
|
|
346
|
-
|
|
347
|
-
this.processListeners(callbackName);
|
|
348
|
-
|
|
349
|
-
this.apiLog(callbackName, null, 'returned: ' + returnValue,
|
|
350
|
-
global_constants.LOG_LEVEL_INFO);
|
|
351
|
-
|
|
352
|
-
return returnValue;
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
/**
|
|
356
|
-
* Returns the errorNumber error description
|
|
357
|
-
*
|
|
358
|
-
* @param {string} callbackName
|
|
359
|
-
* @param {(string|number)} CMIErrorCode
|
|
360
|
-
* @return {string}
|
|
361
|
-
*/
|
|
362
|
-
getErrorString(callbackName: String, CMIErrorCode) {
|
|
363
|
-
let returnValue = '';
|
|
364
|
-
|
|
365
|
-
if (CMIErrorCode !== null && CMIErrorCode !== '') {
|
|
366
|
-
returnValue = this.getLmsErrorMessageDetails(CMIErrorCode);
|
|
367
|
-
this.processListeners(callbackName);
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
this.apiLog(callbackName, null, 'returned: ' + returnValue,
|
|
371
|
-
global_constants.LOG_LEVEL_INFO);
|
|
372
|
-
|
|
373
|
-
return returnValue;
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
/**
|
|
377
|
-
* Returns a comprehensive description of the errorNumber error.
|
|
378
|
-
*
|
|
379
|
-
* @param {string} callbackName
|
|
380
|
-
* @param {(string|number)} CMIErrorCode
|
|
381
|
-
* @return {string}
|
|
382
|
-
*/
|
|
383
|
-
getDiagnostic(callbackName: String, CMIErrorCode) {
|
|
384
|
-
let returnValue = '';
|
|
385
|
-
|
|
386
|
-
if (CMIErrorCode !== null && CMIErrorCode !== '') {
|
|
387
|
-
returnValue = this.getLmsErrorMessageDetails(CMIErrorCode, true);
|
|
388
|
-
this.processListeners(callbackName);
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
this.apiLog(callbackName, null, 'returned: ' + returnValue,
|
|
392
|
-
global_constants.LOG_LEVEL_INFO);
|
|
393
|
-
|
|
394
|
-
return returnValue;
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
/**
|
|
398
|
-
* Checks the LMS state and ensures it has been initialized.
|
|
399
|
-
*
|
|
400
|
-
* @param {boolean} checkTerminated
|
|
401
|
-
* @param {number} beforeInitError
|
|
402
|
-
* @param {number} afterTermError
|
|
403
|
-
* @return {boolean}
|
|
404
|
-
*/
|
|
405
|
-
checkState(
|
|
406
|
-
checkTerminated: boolean,
|
|
407
|
-
beforeInitError: number,
|
|
408
|
-
afterTermError?: number) {
|
|
409
|
-
if (this.isNotInitialized()) {
|
|
410
|
-
this.throwSCORMError(beforeInitError);
|
|
411
|
-
return false;
|
|
412
|
-
} else if (checkTerminated && this.isTerminated()) {
|
|
413
|
-
this.throwSCORMError(afterTermError);
|
|
414
|
-
return false;
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
return true;
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
/**
|
|
421
|
-
* Logging for all SCORM actions
|
|
422
|
-
*
|
|
423
|
-
* @param {string} functionName
|
|
424
|
-
* @param {string} CMIElement
|
|
425
|
-
* @param {string} logMessage
|
|
426
|
-
* @param {number}messageLevel
|
|
427
|
-
*/
|
|
428
|
-
apiLog(
|
|
429
|
-
functionName: String,
|
|
430
|
-
CMIElement: String,
|
|
431
|
-
logMessage: String,
|
|
432
|
-
messageLevel: number) {
|
|
433
|
-
logMessage = this.formatMessage(functionName, CMIElement, logMessage);
|
|
434
|
-
|
|
435
|
-
if (messageLevel >= this.apiLogLevel) {
|
|
436
|
-
this.settings.onLogMessage(messageLevel, logMessage);
|
|
437
|
-
}
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
/**
|
|
441
|
-
* Formats the SCORM messages for easy reading
|
|
442
|
-
*
|
|
443
|
-
* @param {string} functionName
|
|
444
|
-
* @param {string} CMIElement
|
|
445
|
-
* @param {string} message
|
|
446
|
-
* @return {string}
|
|
447
|
-
*/
|
|
448
|
-
formatMessage(functionName: String, CMIElement: String, message: String) {
|
|
449
|
-
const baseLength = 20;
|
|
450
|
-
let messageString = '';
|
|
451
|
-
|
|
452
|
-
messageString += functionName;
|
|
453
|
-
|
|
454
|
-
let fillChars = baseLength - messageString.length;
|
|
455
|
-
|
|
456
|
-
for (let i = 0; i < fillChars; i++) {
|
|
457
|
-
messageString += ' ';
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
messageString += ': ';
|
|
461
|
-
|
|
462
|
-
if (CMIElement) {
|
|
463
|
-
const CMIElementBaseLength = 70;
|
|
464
|
-
|
|
465
|
-
messageString += CMIElement;
|
|
466
|
-
|
|
467
|
-
fillChars = CMIElementBaseLength - messageString.length;
|
|
468
|
-
|
|
469
|
-
for (let j = 0; j < fillChars; j++) {
|
|
470
|
-
messageString += ' ';
|
|
471
|
-
}
|
|
472
|
-
}
|
|
473
|
-
|
|
474
|
-
if (message) {
|
|
475
|
-
messageString += message;
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
return messageString;
|
|
479
|
-
}
|
|
480
|
-
|
|
481
|
-
/**
|
|
482
|
-
* Checks to see if {str} contains {tester}
|
|
483
|
-
*
|
|
484
|
-
* @param {string} str String to check against
|
|
485
|
-
* @param {string} tester String to check for
|
|
486
|
-
* @return {boolean}
|
|
487
|
-
*/
|
|
488
|
-
stringMatches(str: String, tester: String) {
|
|
489
|
-
return str && tester && str.match(tester);
|
|
490
|
-
}
|
|
491
|
-
|
|
492
|
-
/**
|
|
493
|
-
* Check to see if the specific object has the given property
|
|
494
|
-
* @param {*} refObject
|
|
495
|
-
* @param {string} attribute
|
|
496
|
-
* @return {boolean}
|
|
497
|
-
* @private
|
|
498
|
-
*/
|
|
499
|
-
_checkObjectHasProperty(refObject, attribute: String) {
|
|
500
|
-
return Object.hasOwnProperty.call(refObject, attribute) ||
|
|
501
|
-
Object.getOwnPropertyDescriptor(
|
|
502
|
-
Object.getPrototypeOf(refObject), attribute) ||
|
|
503
|
-
(attribute in refObject);
|
|
504
|
-
}
|
|
505
|
-
|
|
506
|
-
/**
|
|
507
|
-
* Returns the message that corresponds to errorNumber
|
|
508
|
-
* APIs that inherit BaseAPI should override this function
|
|
509
|
-
*
|
|
510
|
-
* @param {(string|number)} _errorNumber
|
|
511
|
-
* @param {boolean} _detail
|
|
512
|
-
* @return {string}
|
|
513
|
-
* @abstract
|
|
514
|
-
*/
|
|
515
|
-
getLmsErrorMessageDetails(_errorNumber, _detail) {
|
|
516
|
-
throw new Error(
|
|
517
|
-
'The getLmsErrorMessageDetails method has not been implemented');
|
|
518
|
-
}
|
|
519
|
-
|
|
520
|
-
/**
|
|
521
|
-
* Gets the value for the specific element.
|
|
522
|
-
* APIs that inherit BaseAPI should override this function
|
|
523
|
-
*
|
|
524
|
-
* @param {string} _CMIElement
|
|
525
|
-
* @return {string}
|
|
526
|
-
* @abstract
|
|
527
|
-
*/
|
|
528
|
-
getCMIValue(_CMIElement) {
|
|
529
|
-
throw new Error('The getCMIValue method has not been implemented');
|
|
530
|
-
}
|
|
531
|
-
|
|
532
|
-
/**
|
|
533
|
-
* Sets the value for the specific element.
|
|
534
|
-
* APIs that inherit BaseAPI should override this function
|
|
535
|
-
*
|
|
536
|
-
* @param {string} _CMIElement
|
|
537
|
-
* @param {any} _value
|
|
538
|
-
* @return {string}
|
|
539
|
-
* @abstract
|
|
540
|
-
*/
|
|
541
|
-
setCMIValue(_CMIElement, _value) {
|
|
542
|
-
throw new Error('The setCMIValue method has not been implemented');
|
|
543
|
-
}
|
|
544
|
-
|
|
545
|
-
/**
|
|
546
|
-
* Shared API method to set a valid for a given element.
|
|
547
|
-
*
|
|
548
|
-
* @param {string} methodName
|
|
549
|
-
* @param {boolean} scorm2004
|
|
550
|
-
* @param {string} CMIElement
|
|
551
|
-
* @param {*} value
|
|
552
|
-
* @return {string}
|
|
553
|
-
*/
|
|
554
|
-
_commonSetCMIValue(
|
|
555
|
-
methodName: String, scorm2004: boolean, CMIElement, value) {
|
|
556
|
-
if (!CMIElement || CMIElement === '') {
|
|
557
|
-
return global_constants.SCORM_FALSE;
|
|
558
|
-
}
|
|
559
|
-
|
|
560
|
-
const structure = CMIElement.split('.');
|
|
561
|
-
let refObject = this;
|
|
562
|
-
let returnValue = global_constants.SCORM_FALSE;
|
|
563
|
-
let foundFirstIndex = false;
|
|
564
|
-
|
|
565
|
-
const invalidErrorMessage = `The data model element passed to ${methodName} (${CMIElement}) is not a valid SCORM data model element.`;
|
|
566
|
-
const invalidErrorCode = scorm2004 ?
|
|
567
|
-
this.#error_codes.UNDEFINED_DATA_MODEL :
|
|
568
|
-
this.#error_codes.GENERAL;
|
|
569
|
-
|
|
570
|
-
for (let i = 0; i < structure.length; i++) {
|
|
571
|
-
const attribute = structure[i];
|
|
572
|
-
|
|
573
|
-
if (i === structure.length - 1) {
|
|
574
|
-
if (scorm2004 && (attribute.substr(0, 8) === '{target=') &&
|
|
575
|
-
(typeof refObject._isTargetValid == 'function')) {
|
|
576
|
-
this.throwSCORMError(this.#error_codes.READ_ONLY_ELEMENT);
|
|
577
|
-
} else if (!this._checkObjectHasProperty(refObject, attribute)) {
|
|
578
|
-
this.throwSCORMError(invalidErrorCode, invalidErrorMessage);
|
|
579
|
-
} else {
|
|
580
|
-
if (this.isInitialized() &&
|
|
581
|
-
this.stringMatches(CMIElement, '\\.correct_responses\\.\\d+')) {
|
|
582
|
-
this.validateCorrectResponse(CMIElement, value);
|
|
583
|
-
}
|
|
584
|
-
|
|
585
|
-
if (!scorm2004 || this.lastErrorCode === 0) {
|
|
586
|
-
refObject[attribute] = value;
|
|
587
|
-
returnValue = global_constants.SCORM_TRUE;
|
|
588
|
-
}
|
|
589
|
-
}
|
|
590
|
-
} else {
|
|
591
|
-
refObject = refObject[attribute];
|
|
592
|
-
if (!refObject) {
|
|
593
|
-
this.throwSCORMError(invalidErrorCode, invalidErrorMessage);
|
|
594
|
-
break;
|
|
595
|
-
}
|
|
596
|
-
|
|
597
|
-
if (refObject instanceof CMIArray) {
|
|
598
|
-
const index = parseInt(structure[i + 1], 10);
|
|
599
|
-
|
|
600
|
-
// SCO is trying to set an item on an array
|
|
601
|
-
if (!isNaN(index)) {
|
|
602
|
-
const item = refObject.childArray[index];
|
|
603
|
-
|
|
604
|
-
if (item) {
|
|
605
|
-
refObject = item;
|
|
606
|
-
foundFirstIndex = true;
|
|
607
|
-
} else {
|
|
608
|
-
const newChild = this.getChildElement(CMIElement, value,
|
|
609
|
-
foundFirstIndex);
|
|
610
|
-
foundFirstIndex = true;
|
|
611
|
-
|
|
612
|
-
if (!newChild) {
|
|
613
|
-
this.throwSCORMError(invalidErrorCode, invalidErrorMessage);
|
|
614
|
-
} else {
|
|
615
|
-
if (refObject.initialized) newChild.initialize();
|
|
616
|
-
|
|
617
|
-
refObject.childArray.push(newChild);
|
|
618
|
-
refObject = newChild;
|
|
619
|
-
}
|
|
620
|
-
}
|
|
621
|
-
|
|
622
|
-
// Have to update i value to skip the array position
|
|
623
|
-
i++;
|
|
624
|
-
}
|
|
625
|
-
}
|
|
626
|
-
}
|
|
627
|
-
}
|
|
628
|
-
|
|
629
|
-
if (returnValue === global_constants.SCORM_FALSE) {
|
|
630
|
-
this.apiLog(methodName, null,
|
|
631
|
-
`There was an error setting the value for: ${CMIElement}, value of: ${value}`,
|
|
632
|
-
global_constants.LOG_LEVEL_WARNING);
|
|
633
|
-
}
|
|
634
|
-
|
|
635
|
-
return returnValue;
|
|
636
|
-
}
|
|
637
|
-
|
|
638
|
-
/**
|
|
639
|
-
* Abstract method for validating that a response is correct.
|
|
640
|
-
*
|
|
641
|
-
* @param {string} _CMIElement
|
|
642
|
-
* @param {*} _value
|
|
643
|
-
*/
|
|
644
|
-
validateCorrectResponse(_CMIElement, _value) {
|
|
645
|
-
// just a stub method
|
|
646
|
-
}
|
|
647
|
-
|
|
648
|
-
/**
|
|
649
|
-
* Gets or builds a new child element to add to the array.
|
|
650
|
-
* APIs that inherit BaseAPI should override this method.
|
|
651
|
-
*
|
|
652
|
-
* @param {string} _CMIElement - unused
|
|
653
|
-
* @param {*} _value - unused
|
|
654
|
-
* @param {boolean} _foundFirstIndex - unused
|
|
655
|
-
* @return {*}
|
|
656
|
-
* @abstract
|
|
657
|
-
*/
|
|
658
|
-
getChildElement(_CMIElement, _value, _foundFirstIndex) {
|
|
659
|
-
throw new Error('The getChildElement method has not been implemented');
|
|
660
|
-
}
|
|
661
|
-
|
|
662
|
-
/**
|
|
663
|
-
* Gets a value from the CMI Object
|
|
664
|
-
*
|
|
665
|
-
* @param {string} methodName
|
|
666
|
-
* @param {boolean} scorm2004
|
|
667
|
-
* @param {string} CMIElement
|
|
668
|
-
* @return {*}
|
|
669
|
-
*/
|
|
670
|
-
_commonGetCMIValue(methodName: String, scorm2004: boolean, CMIElement) {
|
|
671
|
-
if (!CMIElement || CMIElement === '') {
|
|
672
|
-
return '';
|
|
673
|
-
}
|
|
674
|
-
|
|
675
|
-
const structure = CMIElement.split('.');
|
|
676
|
-
let refObject = this;
|
|
677
|
-
let attribute = null;
|
|
678
|
-
|
|
679
|
-
const uninitializedErrorMessage = `The data model element passed to ${methodName} (${CMIElement}) has not been initialized.`;
|
|
680
|
-
const invalidErrorMessage = `The data model element passed to ${methodName} (${CMIElement}) is not a valid SCORM data model element.`;
|
|
681
|
-
const invalidErrorCode = scorm2004 ?
|
|
682
|
-
this.#error_codes.UNDEFINED_DATA_MODEL :
|
|
683
|
-
this.#error_codes.GENERAL;
|
|
684
|
-
|
|
685
|
-
for (let i = 0; i < structure.length; i++) {
|
|
686
|
-
attribute = structure[i];
|
|
687
|
-
|
|
688
|
-
if (!scorm2004) {
|
|
689
|
-
if (i === structure.length - 1) {
|
|
690
|
-
if (!this._checkObjectHasProperty(refObject, attribute)) {
|
|
691
|
-
this.throwSCORMError(invalidErrorCode, invalidErrorMessage);
|
|
692
|
-
return;
|
|
693
|
-
}
|
|
694
|
-
}
|
|
695
|
-
} else {
|
|
696
|
-
if ((String(attribute).substr(0, 8) === '{target=') &&
|
|
697
|
-
(typeof refObject._isTargetValid == 'function')) {
|
|
698
|
-
const target = String(attribute).substr(8, String(attribute).length - 9);
|
|
699
|
-
return refObject._isTargetValid(target);
|
|
700
|
-
} else if (!this._checkObjectHasProperty(refObject, attribute)) {
|
|
701
|
-
this.throwSCORMError(invalidErrorCode, invalidErrorMessage);
|
|
702
|
-
return;
|
|
703
|
-
}
|
|
704
|
-
}
|
|
705
|
-
|
|
706
|
-
refObject = refObject[attribute];
|
|
707
|
-
if (refObject === undefined) {
|
|
708
|
-
this.throwSCORMError(invalidErrorCode, invalidErrorMessage);
|
|
709
|
-
break;
|
|
710
|
-
}
|
|
711
|
-
|
|
712
|
-
if (refObject instanceof CMIArray) {
|
|
713
|
-
const index = parseInt(structure[i + 1], 10);
|
|
714
|
-
|
|
715
|
-
// SCO is trying to set an item on an array
|
|
716
|
-
if (!isNaN(index)) {
|
|
717
|
-
const item = refObject.childArray[index];
|
|
718
|
-
|
|
719
|
-
if (item) {
|
|
720
|
-
refObject = item;
|
|
721
|
-
} else {
|
|
722
|
-
this.throwSCORMError(this.#error_codes.VALUE_NOT_INITIALIZED,
|
|
723
|
-
uninitializedErrorMessage);
|
|
724
|
-
break;
|
|
725
|
-
}
|
|
726
|
-
|
|
727
|
-
// Have to update i value to skip the array position
|
|
728
|
-
i++;
|
|
729
|
-
}
|
|
730
|
-
}
|
|
731
|
-
}
|
|
732
|
-
|
|
733
|
-
if (refObject === null || refObject === undefined) {
|
|
734
|
-
if (!scorm2004) {
|
|
735
|
-
if (attribute === '_children') {
|
|
736
|
-
this.throwSCORMError(scorm12_error_codes.CHILDREN_ERROR);
|
|
737
|
-
} else if (attribute === '_count') {
|
|
738
|
-
this.throwSCORMError(scorm12_error_codes.COUNT_ERROR);
|
|
739
|
-
}
|
|
740
|
-
}
|
|
741
|
-
} else {
|
|
742
|
-
return refObject;
|
|
743
|
-
}
|
|
744
|
-
}
|
|
745
|
-
|
|
746
|
-
/**
|
|
747
|
-
* Returns true if the API's current state is STATE_INITIALIZED
|
|
748
|
-
*
|
|
749
|
-
* @return {boolean}
|
|
750
|
-
*/
|
|
751
|
-
isInitialized() {
|
|
752
|
-
return this.currentState === global_constants.STATE_INITIALIZED;
|
|
753
|
-
}
|
|
754
|
-
|
|
755
|
-
/**
|
|
756
|
-
* Returns true if the API's current state is STATE_NOT_INITIALIZED
|
|
757
|
-
*
|
|
758
|
-
* @return {boolean}
|
|
759
|
-
*/
|
|
760
|
-
isNotInitialized() {
|
|
761
|
-
return this.currentState === global_constants.STATE_NOT_INITIALIZED;
|
|
762
|
-
}
|
|
763
|
-
|
|
764
|
-
/**
|
|
765
|
-
* Returns true if the API's current state is STATE_TERMINATED
|
|
766
|
-
*
|
|
767
|
-
* @return {boolean}
|
|
768
|
-
*/
|
|
769
|
-
isTerminated() {
|
|
770
|
-
return this.currentState === global_constants.STATE_TERMINATED;
|
|
771
|
-
}
|
|
772
|
-
|
|
773
|
-
/**
|
|
774
|
-
* Provides a mechanism for attaching to a specific SCORM event
|
|
775
|
-
*
|
|
776
|
-
* @param {string} listenerName
|
|
777
|
-
* @param {function} callback
|
|
778
|
-
*/
|
|
779
|
-
on(listenerName: String, callback: function) {
|
|
780
|
-
if (!callback) return;
|
|
781
|
-
|
|
782
|
-
const listenerFunctions = listenerName.split(' ');
|
|
783
|
-
for (let i = 0; i < listenerFunctions.length; i++) {
|
|
784
|
-
const listenerSplit = listenerFunctions[i].split('.');
|
|
785
|
-
if (listenerSplit.length === 0) return;
|
|
786
|
-
|
|
787
|
-
const functionName = listenerSplit[0];
|
|
788
|
-
|
|
789
|
-
let CMIElement = null;
|
|
790
|
-
if (listenerSplit.length > 1) {
|
|
791
|
-
CMIElement = listenerName.replace(functionName + '.', '');
|
|
792
|
-
}
|
|
793
|
-
|
|
794
|
-
this.listenerArray.push({
|
|
795
|
-
functionName: functionName,
|
|
796
|
-
CMIElement: CMIElement,
|
|
797
|
-
callback: callback,
|
|
798
|
-
});
|
|
799
|
-
|
|
800
|
-
this.apiLog('on', functionName, `Added event listener: ${this.listenerArray.length}`, global_constants.LOG_LEVEL_INFO);
|
|
801
|
-
}
|
|
802
|
-
}
|
|
803
|
-
|
|
804
|
-
/**
|
|
805
|
-
* Provides a mechanism for detaching a specific SCORM event listener
|
|
806
|
-
*
|
|
807
|
-
* @param {string} listenerName
|
|
808
|
-
* @param {function} callback
|
|
809
|
-
*/
|
|
810
|
-
off(listenerName: String, callback: function) {
|
|
811
|
-
if (!callback) return;
|
|
812
|
-
|
|
813
|
-
const listenerFunctions = listenerName.split(' ');
|
|
814
|
-
for (let i = 0; i < listenerFunctions.length; i++) {
|
|
815
|
-
const listenerSplit = listenerFunctions[i].split('.');
|
|
816
|
-
if (listenerSplit.length === 0) return;
|
|
817
|
-
|
|
818
|
-
const functionName = listenerSplit[0];
|
|
819
|
-
|
|
820
|
-
let CMIElement = null;
|
|
821
|
-
if (listenerSplit.length > 1) {
|
|
822
|
-
CMIElement = listenerName.replace(functionName + '.', '');
|
|
823
|
-
}
|
|
824
|
-
|
|
825
|
-
const removeIndex = this.listenerArray.findIndex((obj) =>
|
|
826
|
-
obj.functionName === functionName &&
|
|
827
|
-
obj.CMIElement === CMIElement &&
|
|
828
|
-
obj.callback === callback,
|
|
829
|
-
);
|
|
830
|
-
if (removeIndex !== -1) {
|
|
831
|
-
this.listenerArray.splice(removeIndex, 1);
|
|
832
|
-
this.apiLog('off', functionName, `Removed event listener: ${this.listenerArray.length}`, global_constants.LOG_LEVEL_INFO);
|
|
833
|
-
}
|
|
834
|
-
}
|
|
835
|
-
}
|
|
836
|
-
|
|
837
|
-
/**
|
|
838
|
-
* Provides a mechanism for clearing all listeners from a specific SCORM event
|
|
839
|
-
*
|
|
840
|
-
* @param {string} listenerName
|
|
841
|
-
*/
|
|
842
|
-
clear(listenerName: String) {
|
|
843
|
-
const listenerFunctions = listenerName.split(' ');
|
|
844
|
-
for (let i = 0; i < listenerFunctions.length; i++) {
|
|
845
|
-
const listenerSplit = listenerFunctions[i].split('.');
|
|
846
|
-
if (listenerSplit.length === 0) return;
|
|
847
|
-
|
|
848
|
-
const functionName = listenerSplit[0];
|
|
849
|
-
|
|
850
|
-
let CMIElement = null;
|
|
851
|
-
if (listenerSplit.length > 1) {
|
|
852
|
-
CMIElement = listenerName.replace(functionName + '.', '');
|
|
853
|
-
}
|
|
854
|
-
|
|
855
|
-
this.listenerArray = this.listenerArray.filter((obj) =>
|
|
856
|
-
obj.functionName !== functionName &&
|
|
857
|
-
obj.CMIElement !== CMIElement,
|
|
858
|
-
);
|
|
859
|
-
}
|
|
860
|
-
}
|
|
861
|
-
|
|
862
|
-
/**
|
|
863
|
-
* Processes any 'on' listeners that have been created
|
|
864
|
-
*
|
|
865
|
-
* @param {string} functionName
|
|
866
|
-
* @param {string} CMIElement
|
|
867
|
-
* @param {*} value
|
|
868
|
-
*/
|
|
869
|
-
processListeners(functionName: String, CMIElement: String, value: any) {
|
|
870
|
-
this.apiLog(functionName, CMIElement, value);
|
|
871
|
-
for (let i = 0; i < this.listenerArray.length; i++) {
|
|
872
|
-
const listener = this.listenerArray[i];
|
|
873
|
-
const functionsMatch = listener.functionName === functionName;
|
|
874
|
-
const listenerHasCMIElement = !!listener.CMIElement;
|
|
875
|
-
let CMIElementsMatch = false;
|
|
876
|
-
if (CMIElement && listener.CMIElement &&
|
|
877
|
-
listener.CMIElement.substring(listener.CMIElement.length - 1) ===
|
|
878
|
-
'*') {
|
|
879
|
-
CMIElementsMatch = CMIElement.indexOf(listener.CMIElement.substring(0,
|
|
880
|
-
listener.CMIElement.length - 1)) === 0;
|
|
881
|
-
} else {
|
|
882
|
-
CMIElementsMatch = listener.CMIElement === CMIElement;
|
|
883
|
-
}
|
|
884
|
-
|
|
885
|
-
if (functionsMatch && (!listenerHasCMIElement || CMIElementsMatch)) {
|
|
886
|
-
listener.callback(CMIElement, value);
|
|
887
|
-
}
|
|
888
|
-
}
|
|
889
|
-
}
|
|
890
|
-
|
|
891
|
-
/**
|
|
892
|
-
* Throws a SCORM error
|
|
893
|
-
*
|
|
894
|
-
* @param {number} errorNumber
|
|
895
|
-
* @param {string} message
|
|
896
|
-
*/
|
|
897
|
-
throwSCORMError(errorNumber: number, message: String) {
|
|
898
|
-
if (!message) {
|
|
899
|
-
message = this.getLmsErrorMessageDetails(errorNumber);
|
|
900
|
-
}
|
|
901
|
-
|
|
902
|
-
this.apiLog('throwSCORMError', null, errorNumber + ': ' + message,
|
|
903
|
-
global_constants.LOG_LEVEL_ERROR);
|
|
904
|
-
|
|
905
|
-
this.lastErrorCode = String(errorNumber);
|
|
906
|
-
}
|
|
907
|
-
|
|
908
|
-
/**
|
|
909
|
-
* Clears the last SCORM error code on success.
|
|
910
|
-
*
|
|
911
|
-
* @param {string} success
|
|
912
|
-
*/
|
|
913
|
-
clearSCORMError(success: String) {
|
|
914
|
-
if (success !== undefined && success !== global_constants.SCORM_FALSE) {
|
|
915
|
-
this.lastErrorCode = 0;
|
|
916
|
-
}
|
|
917
|
-
}
|
|
918
|
-
|
|
919
|
-
/**
|
|
920
|
-
* Attempts to store the data to the LMS, logs data if no LMS configured
|
|
921
|
-
* APIs that inherit BaseAPI should override this function
|
|
922
|
-
*
|
|
923
|
-
* @param {boolean} _calculateTotalTime
|
|
924
|
-
* @return {string}
|
|
925
|
-
* @abstract
|
|
926
|
-
*/
|
|
927
|
-
storeData(_calculateTotalTime) {
|
|
928
|
-
throw new Error(
|
|
929
|
-
'The storeData method has not been implemented');
|
|
930
|
-
}
|
|
931
|
-
|
|
932
|
-
/**
|
|
933
|
-
* Load the CMI from a flattened JSON object
|
|
934
|
-
* @param {object} json
|
|
935
|
-
* @param {string} CMIElement
|
|
936
|
-
*/
|
|
937
|
-
loadFromFlattenedJSON(json, CMIElement) {
|
|
938
|
-
if (!this.isNotInitialized()) {
|
|
939
|
-
console.error(
|
|
940
|
-
'loadFromFlattenedJSON can only be called before the call to lmsInitialize.');
|
|
941
|
-
return;
|
|
942
|
-
}
|
|
943
|
-
|
|
944
|
-
/**
|
|
945
|
-
* Test match pattern.
|
|
946
|
-
*
|
|
947
|
-
* @param {string} a
|
|
948
|
-
* @param {string} c
|
|
949
|
-
* @param {RegExp} a_pattern
|
|
950
|
-
* @return {number}
|
|
951
|
-
*/
|
|
952
|
-
function testPattern(a, c, a_pattern) {
|
|
953
|
-
const a_match = a.match(a_pattern);
|
|
954
|
-
|
|
955
|
-
let c_match;
|
|
956
|
-
if (a_match !== null && (c_match = c.match(a_pattern)) !== null) {
|
|
957
|
-
const a_num = Number(a_match[2]);
|
|
958
|
-
const c_num = Number(c_match[2]);
|
|
959
|
-
if (a_num === c_num) {
|
|
960
|
-
if (a_match[3] === 'id') {
|
|
961
|
-
return -1;
|
|
962
|
-
} else if (a_match[3] === 'type') {
|
|
963
|
-
if (c_match[3] === 'id') {
|
|
964
|
-
return 1;
|
|
965
|
-
} else {
|
|
966
|
-
return -1;
|
|
967
|
-
}
|
|
968
|
-
} else {
|
|
969
|
-
return 1;
|
|
970
|
-
}
|
|
971
|
-
}
|
|
972
|
-
return a_num - c_num;
|
|
973
|
-
}
|
|
974
|
-
|
|
975
|
-
return null;
|
|
976
|
-
}
|
|
977
|
-
|
|
978
|
-
const int_pattern = /^(cmi\.interactions\.)(\d+)\.(.*)$/;
|
|
979
|
-
const obj_pattern = /^(cmi\.objectives\.)(\d+)\.(.*)$/;
|
|
980
|
-
|
|
981
|
-
const result = Object.keys(json).map(function(key) {
|
|
982
|
-
return [String(key), json[key]];
|
|
983
|
-
});
|
|
984
|
-
|
|
985
|
-
// CMI interactions need to have id and type loaded before any other fields
|
|
986
|
-
result.sort(function([a, b], [c, d]) {
|
|
987
|
-
let test;
|
|
988
|
-
if ((test = testPattern(a, c, int_pattern)) !== null) {
|
|
989
|
-
return test;
|
|
990
|
-
}
|
|
991
|
-
if ((test = testPattern(a, c, obj_pattern)) !== null) {
|
|
992
|
-
return test;
|
|
993
|
-
}
|
|
994
|
-
|
|
995
|
-
if (a < c) {
|
|
996
|
-
return -1;
|
|
997
|
-
}
|
|
998
|
-
if (a > c) {
|
|
999
|
-
return 1;
|
|
1000
|
-
}
|
|
1001
|
-
return 0;
|
|
1002
|
-
});
|
|
1003
|
-
|
|
1004
|
-
let obj;
|
|
1005
|
-
result.forEach((element) => {
|
|
1006
|
-
obj = {};
|
|
1007
|
-
obj[element[0]] = element[1];
|
|
1008
|
-
this.loadFromJSON(unflatten(obj), CMIElement);
|
|
1009
|
-
});
|
|
1010
|
-
}
|
|
1011
|
-
|
|
1012
|
-
/**
|
|
1013
|
-
* Loads CMI data from a JSON object.
|
|
1014
|
-
*
|
|
1015
|
-
* @param {object} json
|
|
1016
|
-
* @param {string} CMIElement
|
|
1017
|
-
*/
|
|
1018
|
-
loadFromJSON(json, CMIElement) {
|
|
1019
|
-
if (!this.isNotInitialized()) {
|
|
1020
|
-
console.error(
|
|
1021
|
-
'loadFromJSON can only be called before the call to lmsInitialize.');
|
|
1022
|
-
return;
|
|
1023
|
-
}
|
|
1024
|
-
|
|
1025
|
-
CMIElement = CMIElement !== undefined ? CMIElement : 'cmi';
|
|
1026
|
-
|
|
1027
|
-
this.startingData = json;
|
|
1028
|
-
|
|
1029
|
-
// could this be refactored down to flatten(json) then setCMIValue on each?
|
|
1030
|
-
for (const key in json) {
|
|
1031
|
-
if ({}.hasOwnProperty.call(json, key) && json[key]) {
|
|
1032
|
-
const currentCMIElement = (CMIElement ? CMIElement + '.' : '') + key;
|
|
1033
|
-
const value = json[key];
|
|
1034
|
-
|
|
1035
|
-
if (value['childArray']) {
|
|
1036
|
-
for (let i = 0; i < value['childArray'].length; i++) {
|
|
1037
|
-
this.loadFromJSON(value['childArray'][i],
|
|
1038
|
-
currentCMIElement + '.' + i);
|
|
1039
|
-
}
|
|
1040
|
-
} else if (value.constructor === Object) {
|
|
1041
|
-
this.loadFromJSON(value, currentCMIElement);
|
|
1042
|
-
} else {
|
|
1043
|
-
this.setCMIValue(currentCMIElement, value);
|
|
1044
|
-
}
|
|
1045
|
-
}
|
|
1046
|
-
}
|
|
1047
|
-
}
|
|
1048
|
-
|
|
1049
|
-
/**
|
|
1050
|
-
* Render the CMI object to JSON for sending to an LMS.
|
|
1051
|
-
*
|
|
1052
|
-
* @return {string}
|
|
1053
|
-
*/
|
|
1054
|
-
renderCMIToJSONString() {
|
|
1055
|
-
const cmi = this.cmi;
|
|
1056
|
-
// Do we want/need to return fields that have no set value?
|
|
1057
|
-
// return JSON.stringify({ cmi }, (k, v) => v === undefined ? null : v, 2);
|
|
1058
|
-
return JSON.stringify({cmi});
|
|
1059
|
-
}
|
|
1060
|
-
|
|
1061
|
-
/**
|
|
1062
|
-
* Returns a JS object representing the current cmi
|
|
1063
|
-
* @return {object}
|
|
1064
|
-
*/
|
|
1065
|
-
renderCMIToJSONObject() {
|
|
1066
|
-
// Do we want/need to return fields that have no set value?
|
|
1067
|
-
// return JSON.stringify({ cmi }, (k, v) => v === undefined ? null : v, 2);
|
|
1068
|
-
return JSON.parse(this.renderCMIToJSONString());
|
|
1069
|
-
}
|
|
1070
|
-
|
|
1071
|
-
/**
|
|
1072
|
-
* Render the cmi object to the proper format for LMS commit
|
|
1073
|
-
* APIs that inherit BaseAPI should override this function
|
|
1074
|
-
*
|
|
1075
|
-
* @param {boolean} _terminateCommit
|
|
1076
|
-
* @return {*}
|
|
1077
|
-
* @abstract
|
|
1078
|
-
*/
|
|
1079
|
-
renderCommitCMI(_terminateCommit) {
|
|
1080
|
-
throw new Error(
|
|
1081
|
-
'The storeData method has not been implemented');
|
|
1082
|
-
}
|
|
1083
|
-
|
|
1084
|
-
/**
|
|
1085
|
-
* Send the request to the LMS
|
|
1086
|
-
* @param {string} url
|
|
1087
|
-
* @param {object|Array} params
|
|
1088
|
-
* @param {boolean} immediate
|
|
1089
|
-
* @return {object}
|
|
1090
|
-
*/
|
|
1091
|
-
processHttpRequest(url: String, params, immediate = false) {
|
|
1092
|
-
const api = this;
|
|
1093
|
-
const process = function(url, params, settings, error_codes) {
|
|
1094
|
-
const genericError = {
|
|
1095
|
-
'result': global_constants.SCORM_FALSE,
|
|
1096
|
-
'errorCode': error_codes.GENERAL,
|
|
1097
|
-
};
|
|
1098
|
-
|
|
1099
|
-
let result;
|
|
1100
|
-
if (!settings.sendBeaconCommit) {
|
|
1101
|
-
const httpReq = new XMLHttpRequest();
|
|
1102
|
-
httpReq.open('POST', url, settings.asyncCommit);
|
|
1103
|
-
|
|
1104
|
-
if (Object.keys(settings.xhrHeaders).length) {
|
|
1105
|
-
Object.keys(settings.xhrHeaders).forEach((header) => {
|
|
1106
|
-
httpReq.setRequestHeader(header, settings.xhrHeaders[header]);
|
|
1107
|
-
});
|
|
1108
|
-
}
|
|
1109
|
-
|
|
1110
|
-
httpReq.withCredentials = settings.xhrWithCredentials;
|
|
1111
|
-
|
|
1112
|
-
if (settings.asyncCommit) {
|
|
1113
|
-
httpReq.onload = function(e) {
|
|
1114
|
-
if (typeof settings.responseHandler === 'function') {
|
|
1115
|
-
result = settings.responseHandler(httpReq);
|
|
1116
|
-
} else {
|
|
1117
|
-
result = JSON.parse(httpReq.responseText);
|
|
1118
|
-
}
|
|
1119
|
-
};
|
|
1120
|
-
}
|
|
1121
|
-
try {
|
|
1122
|
-
params = settings.requestHandler(params);
|
|
1123
|
-
if (params instanceof Array) {
|
|
1124
|
-
httpReq.setRequestHeader('Content-Type',
|
|
1125
|
-
'application/x-www-form-urlencoded');
|
|
1126
|
-
httpReq.send(params.join('&'));
|
|
1127
|
-
} else {
|
|
1128
|
-
httpReq.setRequestHeader('Content-Type',
|
|
1129
|
-
settings.commitRequestDataType);
|
|
1130
|
-
httpReq.send(JSON.stringify(params));
|
|
1131
|
-
}
|
|
1132
|
-
|
|
1133
|
-
if (!settings.asyncCommit) {
|
|
1134
|
-
if (typeof settings.responseHandler === 'function') {
|
|
1135
|
-
result = settings.responseHandler(httpReq);
|
|
1136
|
-
} else {
|
|
1137
|
-
result = JSON.parse(httpReq.responseText);
|
|
1138
|
-
}
|
|
1139
|
-
} else {
|
|
1140
|
-
result = {};
|
|
1141
|
-
result.result = global_constants.SCORM_TRUE;
|
|
1142
|
-
result.errorCode = 0;
|
|
1143
|
-
api.processListeners('CommitSuccess');
|
|
1144
|
-
return result;
|
|
1145
|
-
}
|
|
1146
|
-
} catch (e) {
|
|
1147
|
-
console.error(e);
|
|
1148
|
-
api.processListeners('CommitError');
|
|
1149
|
-
return genericError;
|
|
1150
|
-
}
|
|
1151
|
-
} else {
|
|
1152
|
-
try {
|
|
1153
|
-
const headers = {
|
|
1154
|
-
type: settings.commitRequestDataType,
|
|
1155
|
-
};
|
|
1156
|
-
let blob;
|
|
1157
|
-
if (params instanceof Array) {
|
|
1158
|
-
blob = new Blob([params.join('&')], headers);
|
|
1159
|
-
} else {
|
|
1160
|
-
blob = new Blob([JSON.stringify(params)], headers);
|
|
1161
|
-
}
|
|
1162
|
-
|
|
1163
|
-
result = {};
|
|
1164
|
-
if (navigator.sendBeacon(url, blob)) {
|
|
1165
|
-
result.result = global_constants.SCORM_TRUE;
|
|
1166
|
-
result.errorCode = 0;
|
|
1167
|
-
} else {
|
|
1168
|
-
result.result = global_constants.SCORM_FALSE;
|
|
1169
|
-
result.errorCode = 101;
|
|
1170
|
-
}
|
|
1171
|
-
} catch (e) {
|
|
1172
|
-
console.error(e);
|
|
1173
|
-
api.processListeners('CommitError');
|
|
1174
|
-
return genericError;
|
|
1175
|
-
}
|
|
1176
|
-
}
|
|
1177
|
-
|
|
1178
|
-
if (typeof result === 'undefined') {
|
|
1179
|
-
api.processListeners('CommitError');
|
|
1180
|
-
return genericError;
|
|
1181
|
-
}
|
|
1182
|
-
|
|
1183
|
-
if (result.result === true ||
|
|
1184
|
-
result.result === global_constants.SCORM_TRUE) {
|
|
1185
|
-
api.processListeners('CommitSuccess');
|
|
1186
|
-
} else {
|
|
1187
|
-
api.processListeners('CommitError');
|
|
1188
|
-
}
|
|
1189
|
-
|
|
1190
|
-
return result;
|
|
1191
|
-
};
|
|
1192
|
-
|
|
1193
|
-
if (typeof debounce !== 'undefined') {
|
|
1194
|
-
const debounced = debounce(process, 500);
|
|
1195
|
-
debounced(url, params, this.settings, this.error_codes);
|
|
1196
|
-
|
|
1197
|
-
// if we're terminating, go ahead and commit immediately
|
|
1198
|
-
if (immediate) {
|
|
1199
|
-
debounced.flush();
|
|
1200
|
-
}
|
|
1201
|
-
|
|
1202
|
-
return {
|
|
1203
|
-
result: global_constants.SCORM_TRUE,
|
|
1204
|
-
errorCode: 0,
|
|
1205
|
-
};
|
|
1206
|
-
} else {
|
|
1207
|
-
return process(url, params, this.settings, this.error_codes);
|
|
1208
|
-
}
|
|
1209
|
-
}
|
|
1210
|
-
|
|
1211
|
-
/**
|
|
1212
|
-
* Throws a SCORM error
|
|
1213
|
-
*
|
|
1214
|
-
* @param {number} when - the number of milliseconds to wait before committing
|
|
1215
|
-
* @param {string} callback - the name of the commit event callback
|
|
1216
|
-
*/
|
|
1217
|
-
scheduleCommit(when: number, callback: string) {
|
|
1218
|
-
this.#timeout = new ScheduledCommit(this, when, callback);
|
|
1219
|
-
this.apiLog('scheduleCommit', '', 'scheduled',
|
|
1220
|
-
global_constants.LOG_LEVEL_DEBUG);
|
|
1221
|
-
}
|
|
1222
|
-
|
|
1223
|
-
/**
|
|
1224
|
-
* Clears and cancels any currently scheduled commits
|
|
1225
|
-
*/
|
|
1226
|
-
clearScheduledCommit() {
|
|
1227
|
-
if (this.#timeout) {
|
|
1228
|
-
this.#timeout.cancel();
|
|
1229
|
-
this.#timeout = null;
|
|
1230
|
-
this.apiLog('clearScheduledCommit', '', 'cleared',
|
|
1231
|
-
global_constants.LOG_LEVEL_DEBUG);
|
|
1232
|
-
}
|
|
1233
|
-
}
|
|
1234
|
-
}
|
|
1235
|
-
|
|
1236
|
-
/**
|
|
1237
|
-
* Private class that wraps a timeout call to the commit() function
|
|
1238
|
-
*/
|
|
1239
|
-
class ScheduledCommit {
|
|
1240
|
-
#API;
|
|
1241
|
-
#cancelled = false;
|
|
1242
|
-
#timeout;
|
|
1243
|
-
#callback;
|
|
1244
|
-
|
|
1245
|
-
/**
|
|
1246
|
-
* Constructor for ScheduledCommit
|
|
1247
|
-
* @param {BaseAPI} API
|
|
1248
|
-
* @param {number} when
|
|
1249
|
-
* @param {string} callback
|
|
1250
|
-
*/
|
|
1251
|
-
constructor(API: any, when: number, callback: string) {
|
|
1252
|
-
this.#API = API;
|
|
1253
|
-
this.#timeout = setTimeout(this.wrapper.bind(this), when);
|
|
1254
|
-
this.#callback = callback;
|
|
1255
|
-
}
|
|
1256
|
-
|
|
1257
|
-
/**
|
|
1258
|
-
* Cancel any currently scheduled commit
|
|
1259
|
-
*/
|
|
1260
|
-
cancel() {
|
|
1261
|
-
this.#cancelled = true;
|
|
1262
|
-
if (this.#timeout) {
|
|
1263
|
-
clearTimeout(this.#timeout);
|
|
1264
|
-
}
|
|
1265
|
-
}
|
|
1266
|
-
|
|
1267
|
-
/**
|
|
1268
|
-
* Wrap the API commit call to check if the call has already been cancelled
|
|
1269
|
-
*/
|
|
1270
|
-
wrapper() {
|
|
1271
|
-
if (!this.#cancelled) {
|
|
1272
|
-
this.#API.commit(this.#callback);
|
|
1273
|
-
}
|
|
1274
|
-
}
|
|
1275
|
-
}
|