scorm-again 2.0.0 → 2.2.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/.github/workflows/stale.yml +14 -0
- package/.run/{Mocha Unit Tests.run.xml → Mocha Unit Tests (watch).run.xml } +1 -1
- package/.run/Template Mocha.run.xml +17 -0
- package/README.md +180 -72
- package/dist/aicc.js +1520 -1149
- package/dist/aicc.js.map +1 -1
- package/dist/aicc.min.js +1 -1
- package/dist/aicc.min.js.map +1 -1
- package/dist/scorm-again.js +2812 -2205
- package/dist/scorm-again.js.map +1 -1
- package/dist/scorm-again.min.js +1 -1
- package/dist/scorm-again.min.js.map +1 -1
- package/dist/scorm12.js +1129 -842
- package/dist/scorm12.js.map +1 -1
- package/dist/scorm12.min.js +1 -1
- package/dist/scorm12.min.js.map +1 -1
- package/dist/scorm2004.js +1921 -1564
- package/dist/scorm2004.js.map +1 -1
- package/dist/scorm2004.min.js +1 -1
- package/dist/scorm2004.min.js.map +1 -1
- package/package.json +20 -17
- package/src/AICC.ts +15 -17
- package/src/BaseAPI.ts +283 -420
- package/src/Scorm12API.ts +133 -41
- package/src/Scorm2004API.ts +224 -120
- package/src/cmi/aicc/attempts.ts +94 -0
- package/src/cmi/aicc/cmi.ts +100 -0
- package/src/cmi/aicc/core.ts +360 -0
- package/src/cmi/aicc/evaluation.ts +157 -0
- package/src/cmi/aicc/paths.ts +180 -0
- package/src/cmi/aicc/student_data.ts +86 -0
- package/src/cmi/aicc/student_demographics.ts +367 -0
- package/src/cmi/aicc/student_preferences.ts +176 -0
- package/src/cmi/aicc/tries.ts +116 -0
- package/src/cmi/aicc/validation.ts +25 -0
- package/src/cmi/common/array.ts +77 -0
- package/src/cmi/common/base_cmi.ts +46 -0
- package/src/cmi/common/score.ts +203 -0
- package/src/cmi/common/validation.ts +60 -0
- package/src/cmi/scorm12/cmi.ts +224 -0
- package/src/cmi/scorm12/interactions.ts +368 -0
- package/src/cmi/scorm12/nav.ts +54 -0
- package/src/cmi/scorm12/objectives.ts +112 -0
- package/src/cmi/scorm12/student_data.ts +130 -0
- package/src/cmi/scorm12/student_preference.ts +158 -0
- package/src/cmi/scorm12/validation.ts +48 -0
- package/src/cmi/scorm2004/adl.ts +272 -0
- package/src/cmi/scorm2004/cmi.ts +599 -0
- package/src/cmi/scorm2004/comments.ts +163 -0
- package/src/cmi/scorm2004/interactions.ts +466 -0
- package/src/cmi/scorm2004/learner_preference.ts +152 -0
- package/src/cmi/scorm2004/objectives.ts +212 -0
- package/src/cmi/scorm2004/score.ts +78 -0
- package/src/cmi/scorm2004/validation.ts +42 -0
- package/src/constants/default_settings.ts +82 -0
- package/src/constants/enums.ts +17 -0
- package/src/constants/regex.ts +2 -2
- package/src/constants/response_constants.ts +2 -0
- package/src/exceptions.ts +22 -1
- package/src/helpers/scheduled_commit.ts +42 -0
- package/src/interfaces/IBaseAPI.ts +35 -0
- package/src/types/api_types.ts +50 -0
- package/src/utilities/debounce.ts +31 -0
- package/src/utilities.ts +56 -0
- package/test/AICC.spec.ts +11 -1
- package/test/Scorm12API.spec.ts +372 -9
- package/test/Scorm2004API.spec.ts +558 -2
- package/test/cmi/aicc_cmi.spec.ts +188 -11
- package/test/cmi/scorm12_cmi.spec.ts +5 -5
- package/test/cmi/scorm2004_cmi.spec.ts +8 -8
- package/test/cmi_helpers.ts +1 -1
- package/test/types/api_types.spec.ts +126 -0
- package/test/utilities/debounce.spec.ts +56 -0
- package/src/cmi/aicc_cmi.ts +0 -1248
- package/src/cmi/common.ts +0 -411
- package/src/cmi/scorm12_cmi.ts +0 -1426
- package/src/cmi/scorm2004_cmi.ts +0 -1874
package/src/Scorm2004API.ts
CHANGED
|
@@ -1,26 +1,31 @@
|
|
|
1
|
-
import BaseAPI
|
|
2
|
-
import {
|
|
3
|
-
ADL,
|
|
4
|
-
CMI,
|
|
5
|
-
CMICommentsObject,
|
|
6
|
-
CMIInteractionsCorrectResponsesObject,
|
|
7
|
-
CMIInteractionsObject,
|
|
8
|
-
CMIInteractionsObjectivesObject,
|
|
9
|
-
CMIObjectivesObject,
|
|
10
|
-
} from "./cmi/scorm2004_cmi";
|
|
1
|
+
import BaseAPI from "./BaseAPI";
|
|
2
|
+
import { CMI } from "./cmi/scorm2004/cmi";
|
|
11
3
|
import * as Utilities from "./utilities";
|
|
4
|
+
import { stringMatches } from "./utilities";
|
|
12
5
|
import APIConstants from "./constants/api_constants";
|
|
13
6
|
import ErrorCodes from "./constants/error_codes";
|
|
14
7
|
import { CorrectResponses, ResponseType } from "./constants/response_constants";
|
|
15
8
|
import ValidLanguages from "./constants/language_constants";
|
|
16
9
|
import Regex from "./constants/regex";
|
|
17
|
-
import
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
10
|
+
import regex from "./constants/regex";
|
|
11
|
+
import { CMIArray } from "./cmi/common/array";
|
|
12
|
+
import { BaseCMI } from "./cmi/common/base_cmi";
|
|
13
|
+
import {
|
|
14
|
+
CMIInteractionsCorrectResponsesObject,
|
|
15
|
+
CMIInteractionsObject,
|
|
16
|
+
CMIInteractionsObjectivesObject,
|
|
17
|
+
} from "./cmi/scorm2004/interactions";
|
|
18
|
+
import { CMICommentsObject } from "./cmi/scorm2004/comments";
|
|
19
|
+
import { CMIObjectivesObject } from "./cmi/scorm2004/objectives";
|
|
20
|
+
import { ADL } from "./cmi/scorm2004/adl";
|
|
21
|
+
import {
|
|
22
|
+
CommitObject,
|
|
23
|
+
RefObject,
|
|
24
|
+
ResultObject,
|
|
25
|
+
ScoreObject,
|
|
26
|
+
Settings,
|
|
27
|
+
} from "./types/api_types";
|
|
28
|
+
import { CompletionStatus, SuccessStatus } from "./constants/enums";
|
|
24
29
|
|
|
25
30
|
/**
|
|
26
31
|
* API class for SCORM 2004
|
|
@@ -39,7 +44,7 @@ export default class Scorm2004API extends BaseAPI {
|
|
|
39
44
|
}
|
|
40
45
|
}
|
|
41
46
|
|
|
42
|
-
super(
|
|
47
|
+
super(ErrorCodes.scorm2004, settings);
|
|
43
48
|
|
|
44
49
|
this.cmi = new CMI();
|
|
45
50
|
this.adl = new ADL();
|
|
@@ -67,6 +72,16 @@ export default class Scorm2004API extends BaseAPI {
|
|
|
67
72
|
public GetErrorString: (CMIErrorCode: string | number) => string;
|
|
68
73
|
public GetDiagnostic: (CMIErrorCode: string | number) => string;
|
|
69
74
|
|
|
75
|
+
/**
|
|
76
|
+
* Called when the API needs to be reset
|
|
77
|
+
*/
|
|
78
|
+
reset(settings?: Settings) {
|
|
79
|
+
this.commonReset(settings);
|
|
80
|
+
|
|
81
|
+
this.cmi = new CMI();
|
|
82
|
+
this.adl = new ADL();
|
|
83
|
+
}
|
|
84
|
+
|
|
70
85
|
/**
|
|
71
86
|
* Getter for _version
|
|
72
87
|
* @return {string}
|
|
@@ -87,32 +102,39 @@ export default class Scorm2004API extends BaseAPI {
|
|
|
87
102
|
* @return {string} bool
|
|
88
103
|
*/
|
|
89
104
|
lmsFinish(): string {
|
|
90
|
-
|
|
105
|
+
(async () => {
|
|
106
|
+
await this.internalFinish();
|
|
107
|
+
})();
|
|
108
|
+
return APIConstants.global.SCORM_TRUE;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
async internalFinish(): Promise<string> {
|
|
112
|
+
const result = await this.terminate("Terminate", true);
|
|
91
113
|
|
|
92
|
-
if (result ===
|
|
114
|
+
if (result === APIConstants.global.SCORM_TRUE) {
|
|
93
115
|
if (this.adl.nav.request !== "_none_") {
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
+
const navActions: { [key: string]: string } = {
|
|
117
|
+
continue: "SequenceNext",
|
|
118
|
+
previous: "SequencePrevious",
|
|
119
|
+
choice: "SequenceChoice",
|
|
120
|
+
jump: "SequenceJump",
|
|
121
|
+
exit: "SequenceExit",
|
|
122
|
+
exitAll: "SequenceExitAll",
|
|
123
|
+
abandon: "SequenceAbandon",
|
|
124
|
+
abandonAll: "SequenceAbandonAll",
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
let request = this.adl.nav.request;
|
|
128
|
+
const choiceJumpRegex = new RegExp(regex.scorm2004.NAVEvent);
|
|
129
|
+
const matches = request.match(choiceJumpRegex);
|
|
130
|
+
let target = "";
|
|
131
|
+
if (matches && matches.length > 2) {
|
|
132
|
+
target = matches[2];
|
|
133
|
+
request = matches[1].replace(target, "");
|
|
134
|
+
}
|
|
135
|
+
const action = navActions[request];
|
|
136
|
+
if (action) {
|
|
137
|
+
this.processListeners(action, "adl.nav.request", target);
|
|
116
138
|
}
|
|
117
139
|
} else if (this.settings.autoProgress) {
|
|
118
140
|
this.processListeners("SequenceNext");
|
|
@@ -127,6 +149,19 @@ export default class Scorm2004API extends BaseAPI {
|
|
|
127
149
|
* @return {string}
|
|
128
150
|
*/
|
|
129
151
|
lmsGetValue(CMIElement: string): string {
|
|
152
|
+
const adlNavRequestRegex =
|
|
153
|
+
"^adl\\.nav\\.request_valid\\.(choice|jump)\\.{target=\\S{0,}([a-zA-Z0-9-_]+)}$";
|
|
154
|
+
if (stringMatches(CMIElement, adlNavRequestRegex)) {
|
|
155
|
+
const matches = CMIElement.match(adlNavRequestRegex);
|
|
156
|
+
const request = matches[1];
|
|
157
|
+
const target = matches[2].replace("{target=", "").replace("}", "");
|
|
158
|
+
if (request === "choice" || request === "jump") {
|
|
159
|
+
if (this.settings.scoItemIdValidator) {
|
|
160
|
+
return String(this.settings.scoItemIdValidator(target));
|
|
161
|
+
}
|
|
162
|
+
return String(this.settings.scoItemIds.includes(target));
|
|
163
|
+
}
|
|
164
|
+
}
|
|
130
165
|
return this.getValue("GetValue", true, CMIElement);
|
|
131
166
|
}
|
|
132
167
|
|
|
@@ -145,7 +180,10 @@ export default class Scorm2004API extends BaseAPI {
|
|
|
145
180
|
* @return {string} bool
|
|
146
181
|
*/
|
|
147
182
|
lmsCommit(): string {
|
|
148
|
-
|
|
183
|
+
(async () => {
|
|
184
|
+
await this.commit("Commit");
|
|
185
|
+
})();
|
|
186
|
+
return APIConstants.global.SCORM_TRUE;
|
|
149
187
|
}
|
|
150
188
|
|
|
151
189
|
/**
|
|
@@ -201,66 +239,71 @@ export default class Scorm2004API extends BaseAPI {
|
|
|
201
239
|
value: any,
|
|
202
240
|
foundFirstIndex: boolean,
|
|
203
241
|
): BaseCMI | null {
|
|
204
|
-
if (
|
|
242
|
+
if (stringMatches(CMIElement, "cmi\\.objectives\\.\\d+")) {
|
|
205
243
|
return new CMIObjectivesObject();
|
|
206
|
-
}
|
|
207
|
-
foundFirstIndex &&
|
|
208
|
-
this.stringMatches(
|
|
209
|
-
CMIElement,
|
|
210
|
-
"cmi\\.interactions\\.\\d+\\.correct_responses\\.\\d+",
|
|
211
|
-
)
|
|
212
|
-
) {
|
|
213
|
-
const parts = CMIElement.split(".");
|
|
214
|
-
const index = Number(parts[2]);
|
|
215
|
-
const interaction = this.cmi.interactions.childArray[index];
|
|
216
|
-
if (this.isInitialized()) {
|
|
217
|
-
if (!interaction.type) {
|
|
218
|
-
this.throwSCORMError(
|
|
219
|
-
scorm2004_error_codes.DEPENDENCY_NOT_ESTABLISHED,
|
|
220
|
-
);
|
|
221
|
-
} else {
|
|
222
|
-
this.checkDuplicateChoiceResponse(interaction, value);
|
|
244
|
+
}
|
|
223
245
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
246
|
+
if (foundFirstIndex) {
|
|
247
|
+
if (
|
|
248
|
+
stringMatches(
|
|
249
|
+
CMIElement,
|
|
250
|
+
"cmi\\.interactions\\.\\d+\\.correct_responses\\.\\d+",
|
|
251
|
+
)
|
|
252
|
+
) {
|
|
253
|
+
return this.createCorrectResponsesObject(CMIElement, value);
|
|
254
|
+
} else if (
|
|
255
|
+
stringMatches(
|
|
256
|
+
CMIElement,
|
|
257
|
+
"cmi\\.interactions\\.\\d+\\.objectives\\.\\d+",
|
|
258
|
+
)
|
|
259
|
+
) {
|
|
260
|
+
return new CMIInteractionsObjectivesObject();
|
|
237
261
|
}
|
|
238
|
-
} else if (
|
|
239
|
-
foundFirstIndex &&
|
|
240
|
-
this.stringMatches(
|
|
241
|
-
CMIElement,
|
|
242
|
-
"cmi\\.interactions\\.\\d+\\.objectives\\.\\d+",
|
|
243
|
-
)
|
|
244
|
-
) {
|
|
245
|
-
return new CMIInteractionsObjectivesObject();
|
|
246
|
-
} else if (
|
|
247
|
-
!foundFirstIndex &&
|
|
248
|
-
this.stringMatches(CMIElement, "cmi\\.interactions\\.\\d+")
|
|
249
|
-
) {
|
|
262
|
+
} else if (stringMatches(CMIElement, "cmi\\.interactions\\.\\d+")) {
|
|
250
263
|
return new CMIInteractionsObject();
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
) {
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
if (stringMatches(CMIElement, "cmi\\.comments_from_learner\\.\\d+")) {
|
|
254
267
|
return new CMICommentsObject();
|
|
255
|
-
} else if (
|
|
256
|
-
this.stringMatches(CMIElement, "cmi\\.comments_from_lms\\.\\d+")
|
|
257
|
-
) {
|
|
268
|
+
} else if (stringMatches(CMIElement, "cmi\\.comments_from_lms\\.\\d+")) {
|
|
258
269
|
return new CMICommentsObject(true);
|
|
259
270
|
}
|
|
260
271
|
|
|
261
272
|
return null;
|
|
262
273
|
}
|
|
263
274
|
|
|
275
|
+
private createCorrectResponsesObject(
|
|
276
|
+
CMIElement: string,
|
|
277
|
+
value: any,
|
|
278
|
+
): BaseCMI | null {
|
|
279
|
+
const parts = CMIElement.split(".");
|
|
280
|
+
const index = Number(parts[2]);
|
|
281
|
+
const interaction = this.cmi.interactions.childArray[index];
|
|
282
|
+
|
|
283
|
+
if (this.isInitialized()) {
|
|
284
|
+
if (!interaction.type) {
|
|
285
|
+
this.throwSCORMError(ErrorCodes.scorm2004.DEPENDENCY_NOT_ESTABLISHED);
|
|
286
|
+
} else {
|
|
287
|
+
this.checkDuplicateChoiceResponse(interaction, value);
|
|
288
|
+
const response_type = CorrectResponses[interaction.type];
|
|
289
|
+
if (response_type) {
|
|
290
|
+
this.checkValidResponseType(response_type, value, interaction.type);
|
|
291
|
+
} else {
|
|
292
|
+
this.throwSCORMError(
|
|
293
|
+
ErrorCodes.scorm2004.GENERAL_SET_FAILURE,
|
|
294
|
+
"Incorrect Response Type: " + interaction.type,
|
|
295
|
+
);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
if (this.lastErrorCode === "0") {
|
|
301
|
+
return new CMIInteractionsCorrectResponsesObject();
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
return null;
|
|
305
|
+
}
|
|
306
|
+
|
|
264
307
|
/**
|
|
265
308
|
* Checks for valid response types
|
|
266
309
|
* @param {object} response_type
|
|
@@ -283,7 +326,7 @@ export default class Scorm2004API extends BaseAPI {
|
|
|
283
326
|
this.checkCorrectResponseValue(interaction_type, nodes, value);
|
|
284
327
|
} else if (nodes.length > response_type.max) {
|
|
285
328
|
this.throwSCORMError(
|
|
286
|
-
|
|
329
|
+
ErrorCodes.scorm2004.GENERAL_SET_FAILURE,
|
|
287
330
|
"Data Model Element Pattern Too Long",
|
|
288
331
|
);
|
|
289
332
|
}
|
|
@@ -304,7 +347,7 @@ export default class Scorm2004API extends BaseAPI {
|
|
|
304
347
|
) {
|
|
305
348
|
const response = interaction.correct_responses.childArray[i];
|
|
306
349
|
if (response.pattern === value) {
|
|
307
|
-
this.throwSCORMError(
|
|
350
|
+
this.throwSCORMError(ErrorCodes.scorm2004.GENERAL_SET_FAILURE);
|
|
308
351
|
}
|
|
309
352
|
}
|
|
310
353
|
}
|
|
@@ -324,7 +367,7 @@ export default class Scorm2004API extends BaseAPI {
|
|
|
324
367
|
const interaction_count = interaction.correct_responses._count;
|
|
325
368
|
this.checkDuplicateChoiceResponse(interaction, value);
|
|
326
369
|
|
|
327
|
-
const response_type =
|
|
370
|
+
const response_type = CorrectResponses[interaction.type];
|
|
328
371
|
if (
|
|
329
372
|
typeof response_type.limit === "undefined" ||
|
|
330
373
|
interaction_count <= response_type.limit
|
|
@@ -345,14 +388,14 @@ export default class Scorm2004API extends BaseAPI {
|
|
|
345
388
|
} else {
|
|
346
389
|
if (this.lastErrorCode === "0") {
|
|
347
390
|
this.throwSCORMError(
|
|
348
|
-
|
|
391
|
+
ErrorCodes.scorm2004.GENERAL_SET_FAILURE,
|
|
349
392
|
"Data Model Element Pattern Already Exists",
|
|
350
393
|
);
|
|
351
394
|
}
|
|
352
395
|
}
|
|
353
396
|
} else {
|
|
354
397
|
this.throwSCORMError(
|
|
355
|
-
|
|
398
|
+
ErrorCodes.scorm2004.GENERAL_SET_FAILURE,
|
|
356
399
|
"Data Model Element Collection Limit Reached",
|
|
357
400
|
);
|
|
358
401
|
}
|
|
@@ -384,11 +427,11 @@ export default class Scorm2004API extends BaseAPI {
|
|
|
384
427
|
|
|
385
428
|
// Set error number to string since inconsistent from modules if string or number
|
|
386
429
|
errorNumber = String(errorNumber);
|
|
387
|
-
if (
|
|
430
|
+
if (APIConstants.scorm2004.error_descriptions[errorNumber]) {
|
|
388
431
|
basicMessage =
|
|
389
|
-
|
|
432
|
+
APIConstants.scorm2004.error_descriptions[errorNumber].basicMessage;
|
|
390
433
|
detailMessage =
|
|
391
|
-
|
|
434
|
+
APIConstants.scorm2004.error_descriptions[errorNumber].detailMessage;
|
|
392
435
|
}
|
|
393
436
|
|
|
394
437
|
return detail ? detailMessage : basicMessage;
|
|
@@ -427,7 +470,7 @@ export default class Scorm2004API extends BaseAPI {
|
|
|
427
470
|
nodes: Array<any>,
|
|
428
471
|
value: any,
|
|
429
472
|
) {
|
|
430
|
-
const response =
|
|
473
|
+
const response = CorrectResponses[interaction_type];
|
|
431
474
|
const formatRegex = new RegExp(response.format);
|
|
432
475
|
for (let i = 0; i < nodes.length && this.lastErrorCode === "0"; i++) {
|
|
433
476
|
if (
|
|
@@ -443,17 +486,17 @@ export default class Scorm2004API extends BaseAPI {
|
|
|
443
486
|
if (values.length === 2) {
|
|
444
487
|
const matches = values[0].match(formatRegex);
|
|
445
488
|
if (!matches) {
|
|
446
|
-
this.throwSCORMError(
|
|
489
|
+
this.throwSCORMError(ErrorCodes.scorm2004.TYPE_MISMATCH);
|
|
447
490
|
} else {
|
|
448
491
|
if (
|
|
449
492
|
!response.format2 ||
|
|
450
493
|
!values[1].match(new RegExp(response.format2))
|
|
451
494
|
) {
|
|
452
|
-
this.throwSCORMError(
|
|
495
|
+
this.throwSCORMError(ErrorCodes.scorm2004.TYPE_MISMATCH);
|
|
453
496
|
}
|
|
454
497
|
}
|
|
455
498
|
} else {
|
|
456
|
-
this.throwSCORMError(
|
|
499
|
+
this.throwSCORMError(ErrorCodes.scorm2004.TYPE_MISMATCH);
|
|
457
500
|
}
|
|
458
501
|
} else {
|
|
459
502
|
const matches = nodes[i].match(formatRegex);
|
|
@@ -461,17 +504,17 @@ export default class Scorm2004API extends BaseAPI {
|
|
|
461
504
|
(!matches && value !== "") ||
|
|
462
505
|
(!matches && interaction_type === "true-false")
|
|
463
506
|
) {
|
|
464
|
-
this.throwSCORMError(
|
|
507
|
+
this.throwSCORMError(ErrorCodes.scorm2004.TYPE_MISMATCH);
|
|
465
508
|
} else {
|
|
466
509
|
if (interaction_type === "numeric" && nodes.length > 1) {
|
|
467
510
|
if (Number(nodes[0]) > Number(nodes[1])) {
|
|
468
|
-
this.throwSCORMError(
|
|
511
|
+
this.throwSCORMError(ErrorCodes.scorm2004.TYPE_MISMATCH);
|
|
469
512
|
}
|
|
470
513
|
} else {
|
|
471
514
|
if (nodes[i] !== "" && response.unique) {
|
|
472
515
|
for (let j = 0; j < i && this.lastErrorCode === "0"; j++) {
|
|
473
516
|
if (nodes[i] === nodes[j]) {
|
|
474
|
-
this.throwSCORMError(
|
|
517
|
+
this.throwSCORMError(ErrorCodes.scorm2004.TYPE_MISMATCH);
|
|
475
518
|
}
|
|
476
519
|
}
|
|
477
520
|
}
|
|
@@ -499,12 +542,12 @@ export default class Scorm2004API extends BaseAPI {
|
|
|
499
542
|
while (matches) {
|
|
500
543
|
switch (matches[2]) {
|
|
501
544
|
case "lang":
|
|
502
|
-
langMatches = node.match(
|
|
545
|
+
langMatches = node.match(Regex.scorm2004.CMILangcr);
|
|
503
546
|
if (langMatches) {
|
|
504
547
|
const lang = langMatches[3];
|
|
505
548
|
if (lang !== undefined && lang.length > 0) {
|
|
506
549
|
if (!ValidLanguages.includes(lang.toLowerCase())) {
|
|
507
|
-
this.throwSCORMError(
|
|
550
|
+
this.throwSCORMError(ErrorCodes.scorm2004.TYPE_MISMATCH);
|
|
508
551
|
}
|
|
509
552
|
}
|
|
510
553
|
}
|
|
@@ -513,7 +556,7 @@ export default class Scorm2004API extends BaseAPI {
|
|
|
513
556
|
case "case_matters":
|
|
514
557
|
if (!seenLang && !seenOrder && !seenCase) {
|
|
515
558
|
if (matches[3] !== "true" && matches[3] !== "false") {
|
|
516
|
-
this.throwSCORMError(
|
|
559
|
+
this.throwSCORMError(ErrorCodes.scorm2004.TYPE_MISMATCH);
|
|
517
560
|
}
|
|
518
561
|
}
|
|
519
562
|
|
|
@@ -522,14 +565,12 @@ export default class Scorm2004API extends BaseAPI {
|
|
|
522
565
|
case "order_matters":
|
|
523
566
|
if (!seenCase && !seenLang && !seenOrder) {
|
|
524
567
|
if (matches[3] !== "true" && matches[3] !== "false") {
|
|
525
|
-
this.throwSCORMError(
|
|
568
|
+
this.throwSCORMError(ErrorCodes.scorm2004.TYPE_MISMATCH);
|
|
526
569
|
}
|
|
527
570
|
}
|
|
528
571
|
|
|
529
572
|
seenOrder = true;
|
|
530
573
|
break;
|
|
531
|
-
default:
|
|
532
|
-
break;
|
|
533
574
|
}
|
|
534
575
|
node = node.substring(matches[1].length);
|
|
535
576
|
matches = node.match(prefixRegex);
|
|
@@ -579,13 +620,74 @@ export default class Scorm2004API extends BaseAPI {
|
|
|
579
620
|
}
|
|
580
621
|
}
|
|
581
622
|
|
|
623
|
+
/**
|
|
624
|
+
* Render the cmi object to the proper format for LMS commit
|
|
625
|
+
* @param {boolean} terminateCommit
|
|
626
|
+
* @return {CommitObject}
|
|
627
|
+
*/
|
|
628
|
+
renderCommitObject(terminateCommit: boolean): CommitObject {
|
|
629
|
+
const cmiExport = this.renderCommitCMI(terminateCommit);
|
|
630
|
+
const totalTimeDuration = this.cmi.getCurrentTotalTime();
|
|
631
|
+
const totalTimeSeconds = Utilities.getDurationAsSeconds(
|
|
632
|
+
totalTimeDuration,
|
|
633
|
+
Regex.scorm2004.CMITimespan,
|
|
634
|
+
);
|
|
635
|
+
|
|
636
|
+
let completionStatus = CompletionStatus.unknown;
|
|
637
|
+
let successStatus = SuccessStatus.unknown;
|
|
638
|
+
if (this.cmi.completion_status) {
|
|
639
|
+
if (this.cmi.completion_status === "completed") {
|
|
640
|
+
completionStatus = CompletionStatus.completed;
|
|
641
|
+
} else if (this.cmi.completion_status === "incomplete") {
|
|
642
|
+
completionStatus = CompletionStatus.incomplete;
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
if (this.cmi.success_status) {
|
|
646
|
+
if (this.cmi.success_status === "passed") {
|
|
647
|
+
successStatus = SuccessStatus.passed;
|
|
648
|
+
} else if (this.cmi.success_status === "failed") {
|
|
649
|
+
successStatus = SuccessStatus.failed;
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
const score = this.cmi.score;
|
|
654
|
+
let scoreObject: ScoreObject = null;
|
|
655
|
+
if (score) {
|
|
656
|
+
scoreObject = {};
|
|
657
|
+
|
|
658
|
+
if (!Number.isNaN(Number.parseFloat(score.raw))) {
|
|
659
|
+
scoreObject.raw = Number.parseFloat(score.raw);
|
|
660
|
+
}
|
|
661
|
+
if (!Number.isNaN(Number.parseFloat(score.min))) {
|
|
662
|
+
scoreObject.min = Number.parseFloat(score.min);
|
|
663
|
+
}
|
|
664
|
+
if (!Number.isNaN(Number.parseFloat(score.max))) {
|
|
665
|
+
scoreObject.max = Number.parseFloat(score.max);
|
|
666
|
+
}
|
|
667
|
+
if (!Number.isNaN(Number.parseFloat(score.scaled))) {
|
|
668
|
+
scoreObject.scaled = Number.parseFloat(score.scaled);
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
const commitObject: CommitObject = {
|
|
673
|
+
completionStatus: completionStatus,
|
|
674
|
+
successStatus: successStatus,
|
|
675
|
+
totalTimeSeconds: totalTimeSeconds,
|
|
676
|
+
runtimeData: cmiExport,
|
|
677
|
+
};
|
|
678
|
+
if (scoreObject) {
|
|
679
|
+
commitObject.score = scoreObject;
|
|
680
|
+
}
|
|
681
|
+
return commitObject;
|
|
682
|
+
}
|
|
683
|
+
|
|
582
684
|
/**
|
|
583
685
|
* Attempts to store the data to the LMS
|
|
584
686
|
*
|
|
585
687
|
* @param {boolean} terminateCommit
|
|
586
688
|
* @return {ResultObject}
|
|
587
689
|
*/
|
|
588
|
-
storeData(terminateCommit: boolean): ResultObject {
|
|
690
|
+
async storeData(terminateCommit: boolean): Promise<ResultObject> {
|
|
589
691
|
if (terminateCommit) {
|
|
590
692
|
if (this.cmi.mode === "normal") {
|
|
591
693
|
if (this.cmi.credit === "credit") {
|
|
@@ -616,18 +718,20 @@ export default class Scorm2004API extends BaseAPI {
|
|
|
616
718
|
navRequest = true;
|
|
617
719
|
}
|
|
618
720
|
|
|
619
|
-
const
|
|
620
|
-
terminateCommit || this.settings.alwaysSendTotalTime
|
|
621
|
-
|
|
721
|
+
const shouldTerminateCommit =
|
|
722
|
+
terminateCommit || this.settings.alwaysSendTotalTime;
|
|
723
|
+
const commitObject = this.settings.renderCommonCommitFields
|
|
724
|
+
? this.renderCommitObject(shouldTerminateCommit)
|
|
725
|
+
: this.renderCommitCMI(shouldTerminateCommit);
|
|
622
726
|
|
|
623
|
-
if (this.apiLogLevel ===
|
|
727
|
+
if (this.apiLogLevel === APIConstants.global.LOG_LEVEL_DEBUG) {
|
|
624
728
|
console.debug(
|
|
625
729
|
"Commit (terminated: " + (terminateCommit ? "yes" : "no") + "): ",
|
|
626
730
|
);
|
|
627
731
|
console.debug(commitObject);
|
|
628
732
|
}
|
|
629
733
|
if (typeof this.settings.lmsCommitUrl === "string") {
|
|
630
|
-
const result = this.processHttpRequest(
|
|
734
|
+
const result = await this.processHttpRequest(
|
|
631
735
|
this.settings.lmsCommitUrl,
|
|
632
736
|
commitObject,
|
|
633
737
|
terminateCommit,
|
|
@@ -646,7 +750,7 @@ export default class Scorm2004API extends BaseAPI {
|
|
|
646
750
|
return result;
|
|
647
751
|
} else {
|
|
648
752
|
return {
|
|
649
|
-
result:
|
|
753
|
+
result: APIConstants.global.SCORM_TRUE,
|
|
650
754
|
errorCode: 0,
|
|
651
755
|
};
|
|
652
756
|
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import {BaseCMI} from "../common/base_cmi";
|
|
2
|
+
import {CMIScore} from "../common/score";
|
|
3
|
+
import APIConstants from "../../constants/api_constants";
|
|
4
|
+
import Regex from "../../constants/regex";
|
|
5
|
+
import ErrorCodes from "../../constants/error_codes";
|
|
6
|
+
import {AICCValidationError} from "../../exceptions";
|
|
7
|
+
import {checkAICCValidFormat} from "./validation";
|
|
8
|
+
import {CMIArray} from "../common/array";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Class for cmi.student_data.attempt_records array
|
|
12
|
+
*/
|
|
13
|
+
export class CMIAttemptRecords extends CMIArray {
|
|
14
|
+
/**
|
|
15
|
+
* Constructor for inline Tries Array class
|
|
16
|
+
*/
|
|
17
|
+
constructor() {
|
|
18
|
+
super({
|
|
19
|
+
children: APIConstants.aicc.attempt_records_children,
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Class for AICC Attempt Records
|
|
26
|
+
*/
|
|
27
|
+
export class CMIAttemptRecordsObject extends BaseCMI {
|
|
28
|
+
/**
|
|
29
|
+
* Constructor for AICC Attempt Records object
|
|
30
|
+
*/
|
|
31
|
+
constructor() {
|
|
32
|
+
super();
|
|
33
|
+
this.score = new CMIScore({
|
|
34
|
+
score_children: APIConstants.aicc.score_children,
|
|
35
|
+
score_range: Regex.aicc.score_range,
|
|
36
|
+
invalidErrorCode: ErrorCodes.scorm12.INVALID_SET_VALUE,
|
|
37
|
+
invalidTypeCode: ErrorCodes.scorm12.TYPE_MISMATCH,
|
|
38
|
+
invalidRangeCode: ErrorCodes.scorm12.VALUE_OUT_OF_RANGE,
|
|
39
|
+
errorClass: AICCValidationError,
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
public score: CMIScore;
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Called when the API has been initialized after the CMI has been created
|
|
47
|
+
*/
|
|
48
|
+
initialize() {
|
|
49
|
+
super.initialize();
|
|
50
|
+
this.score?.initialize();
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
private _lesson_status = "";
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Getter for _lesson_status
|
|
57
|
+
* @return {string}
|
|
58
|
+
*/
|
|
59
|
+
get lesson_status(): string {
|
|
60
|
+
return this._lesson_status;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Setter for _lesson_status
|
|
65
|
+
* @param {string} lesson_status
|
|
66
|
+
*/
|
|
67
|
+
set lesson_status(lesson_status: string) {
|
|
68
|
+
if (checkAICCValidFormat(lesson_status, Regex.aicc.CMIStatus2)) {
|
|
69
|
+
this._lesson_status = lesson_status;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* toJSON for cmi.student_data.attempt_records.n object
|
|
75
|
+
* @return {
|
|
76
|
+
* {
|
|
77
|
+
* lesson_status: string,
|
|
78
|
+
* score: CMIScore
|
|
79
|
+
* }
|
|
80
|
+
* }
|
|
81
|
+
*/
|
|
82
|
+
toJSON(): {
|
|
83
|
+
lesson_status: string;
|
|
84
|
+
score: CMIScore;
|
|
85
|
+
} {
|
|
86
|
+
this.jsonString = true;
|
|
87
|
+
const result = {
|
|
88
|
+
lesson_status: this.lesson_status,
|
|
89
|
+
score: this.score,
|
|
90
|
+
};
|
|
91
|
+
delete this.jsonString;
|
|
92
|
+
return result;
|
|
93
|
+
}
|
|
94
|
+
}
|