@lessonkit/core 1.5.0 → 1.7.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/README.md +1 -1
- package/dist/{chunk-KFXFQ6B2.js → chunk-DTJGIMUU.js} +216 -35
- package/dist/index.cjs +532 -60
- package/dist/index.d.cts +129 -13
- package/dist/index.d.ts +129 -13
- package/dist/index.js +308 -26
- package/dist/{testing-BFr8oEfw.d.cts → testing-CQ-ZsT7D.d.cts} +185 -5
- package/dist/{testing-BFr8oEfw.d.ts → testing-CQ-ZsT7D.d.ts} +185 -5
- package/dist/testing.cjs +2 -0
- package/dist/testing.d.cts +1 -1
- package/dist/testing.d.ts +1 -1
- package/dist/testing.js +1 -1
- package/package.json +3 -3
- package/telemetry-catalog.v3.json +140 -0
package/dist/index.cjs
CHANGED
|
@@ -21,18 +21,22 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
21
21
|
var index_exports = {};
|
|
22
22
|
__export(index_exports, {
|
|
23
23
|
ACCORDION_FORBIDDEN_CHILD_TYPES: () => ACCORDION_FORBIDDEN_CHILD_TYPES,
|
|
24
|
+
ASSESSMENT_17_CHILD_TYPES: () => ASSESSMENT_17_CHILD_TYPES,
|
|
24
25
|
ASSESSMENT_SEQUENCE_ALLOWED_CHILD_TYPES: () => ASSESSMENT_SEQUENCE_ALLOWED_CHILD_TYPES,
|
|
25
26
|
BLOCKS_14_PAGE_SLIDE: () => BLOCKS_14_PAGE_SLIDE,
|
|
26
27
|
BRANCHING_SCENARIO_ALLOWED_CHILD_TYPES: () => BRANCHING_SCENARIO_ALLOWED_CHILD_TYPES,
|
|
27
28
|
BRANCH_NODE_ALLOWED_CHILD_TYPES: () => BRANCH_NODE_ALLOWED_CHILD_TYPES,
|
|
28
29
|
COMPOUND_MAX_NESTING_DEPTH: () => COMPOUND_MAX_NESTING_DEPTH,
|
|
29
30
|
COMPOUND_RESUME_SCHEMA_VERSION: () => COMPOUND_RESUME_SCHEMA_VERSION,
|
|
31
|
+
GAME_MAP_ALLOWED_CHILD_TYPES: () => GAME_MAP_ALLOWED_CHILD_TYPES,
|
|
30
32
|
ID_MAX_LENGTH: () => ID_MAX_LENGTH,
|
|
31
33
|
ID_PATTERN: () => ID_PATTERN,
|
|
32
34
|
INTERACTIVE_BOOK_ALLOWED_CHILD_TYPES: () => INTERACTIVE_BOOK_ALLOWED_CHILD_TYPES,
|
|
33
35
|
INTERACTIVE_VIDEO_ALLOWED_CHILD_TYPES: () => INTERACTIVE_VIDEO_ALLOWED_CHILD_TYPES,
|
|
36
|
+
MAP_STAGE_ALLOWED_CHILD_TYPES: () => MAP_STAGE_ALLOWED_CHILD_TYPES,
|
|
34
37
|
PAGE_ALLOWED_CHILD_TYPES: () => PAGE_ALLOWED_CHILD_TYPES,
|
|
35
38
|
SESSION_STORAGE_KEY: () => SESSION_STORAGE_KEY,
|
|
39
|
+
SINGLE_CHOICE_SET_ALLOWED_CHILD_TYPES: () => SINGLE_CHOICE_SET_ALLOWED_CHILD_TYPES,
|
|
36
40
|
SLIDE_ALLOWED_CHILD_TYPES: () => SLIDE_ALLOWED_CHILD_TYPES,
|
|
37
41
|
SLIDE_DECK_ALLOWED_CHILD_TYPES: () => SLIDE_DECK_ALLOWED_CHILD_TYPES,
|
|
38
42
|
TELEMETRY_EVENT_CATALOG: () => TELEMETRY_EVENT_CATALOG,
|
|
@@ -77,6 +81,7 @@ __export(index_exports, {
|
|
|
77
81
|
hasCourseStartedXapiSent: () => hasCourseStartedXapiSent,
|
|
78
82
|
isChildTypeAllowed: () => isChildTypeAllowed,
|
|
79
83
|
isLifecycleTelemetryEvent: () => isLifecycleTelemetryEvent,
|
|
84
|
+
isMultiSelectMcq: () => isMultiSelectMcq,
|
|
80
85
|
loadCompoundState: () => loadCompoundState,
|
|
81
86
|
markCourseStarted: () => markCourseStarted,
|
|
82
87
|
markCourseStartedEmittedToTracking: () => markCourseStartedEmittedToTracking,
|
|
@@ -84,6 +89,7 @@ __export(index_exports, {
|
|
|
84
89
|
markCourseStartedXapiSent: () => markCourseStartedXapiSent,
|
|
85
90
|
migrateCourseStartedMark: () => migrateCourseStartedMark,
|
|
86
91
|
nowIso: () => nowIso,
|
|
92
|
+
orderChoicesByIndices: () => orderChoicesByIndices,
|
|
87
93
|
parseBlockId: () => parseBlockId,
|
|
88
94
|
parseCheckId: () => parseCheckId,
|
|
89
95
|
parseCompoundResumeState: () => parseCompoundResumeState,
|
|
@@ -92,8 +98,12 @@ __export(index_exports, {
|
|
|
92
98
|
resetSharedVolatileSessionIdForTests: () => resetSharedVolatileSessionIdForTests,
|
|
93
99
|
resetStoragePortForTests: () => resetStoragePortForTests,
|
|
94
100
|
resetTelemetryBuilderWarningsForTests: () => resetTelemetryBuilderWarningsForTests,
|
|
101
|
+
resolveMcqCorrectAnswers: () => resolveMcqCorrectAnswers,
|
|
102
|
+
resolveMcqShuffleSeed: () => resolveMcqShuffleSeed,
|
|
95
103
|
resolveSessionId: () => resolveSessionId,
|
|
96
104
|
saveCompoundState: () => saveCompoundState,
|
|
105
|
+
scoreMcqSelection: () => scoreMcqSelection,
|
|
106
|
+
shuffleChoiceIndices: () => shuffleChoiceIndices,
|
|
97
107
|
slugifyId: () => slugifyId,
|
|
98
108
|
telemetryCatalogV2Version: () => telemetryCatalogV2Version,
|
|
99
109
|
telemetryCatalogV3Version: () => telemetryCatalogV3Version,
|
|
@@ -240,7 +250,7 @@ function buildLessonkitUrn(parts) {
|
|
|
240
250
|
urn += `:block:${blockId}`;
|
|
241
251
|
}
|
|
242
252
|
if (parts.nodeId !== void 0) {
|
|
243
|
-
const nodeId = assertValidId(parts.nodeId, "
|
|
253
|
+
const nodeId = assertValidId(parts.nodeId, "nodeId");
|
|
244
254
|
if (parts.blockId === void 0) {
|
|
245
255
|
throw new Error("buildLessonkitUrn: nodeId requires blockId");
|
|
246
256
|
}
|
|
@@ -249,6 +259,69 @@ function buildLessonkitUrn(parts) {
|
|
|
249
259
|
return urn;
|
|
250
260
|
}
|
|
251
261
|
|
|
262
|
+
// src/mcqAssessment.ts
|
|
263
|
+
function resolveMcqCorrectAnswers(props) {
|
|
264
|
+
if (props.answers && props.answers.length > 1) {
|
|
265
|
+
return new Set(props.answers.map((a) => a.trim()).filter(Boolean));
|
|
266
|
+
}
|
|
267
|
+
const single = props.answers?.[0]?.trim() ?? props.answer.trim();
|
|
268
|
+
return new Set(single ? [single] : []);
|
|
269
|
+
}
|
|
270
|
+
function isMultiSelectMcq(props) {
|
|
271
|
+
return (props.answers?.length ?? 0) > 1;
|
|
272
|
+
}
|
|
273
|
+
function scoreMcqSelection(selected, correct, multi, passingScore) {
|
|
274
|
+
const maxScore = multi ? Math.max(correct.size, 1) : 1;
|
|
275
|
+
if (!selected || Array.isArray(selected) && selected.length === 0) {
|
|
276
|
+
return {
|
|
277
|
+
score: 0,
|
|
278
|
+
maxScore,
|
|
279
|
+
exactMatch: false,
|
|
280
|
+
hasWrongSelection: false,
|
|
281
|
+
passedThreshold: false
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
const selectedSet = new Set(
|
|
285
|
+
(Array.isArray(selected) ? selected : [selected]).map((s) => s.trim()).filter(Boolean)
|
|
286
|
+
);
|
|
287
|
+
let score = 0;
|
|
288
|
+
for (const label of selectedSet) {
|
|
289
|
+
if (correct.has(label)) score += 1;
|
|
290
|
+
}
|
|
291
|
+
const hasWrongSelection = [...selectedSet].some((label) => !correct.has(label));
|
|
292
|
+
const exactMatch = !hasWrongSelection && selectedSet.size === correct.size && [...correct].every((label) => selectedSet.has(label));
|
|
293
|
+
const threshold = passingScore ?? maxScore;
|
|
294
|
+
const passedThreshold = score >= threshold && !hasWrongSelection;
|
|
295
|
+
return { score, maxScore, exactMatch, hasWrongSelection, passedThreshold };
|
|
296
|
+
}
|
|
297
|
+
function hashSeedToNumber(seed) {
|
|
298
|
+
if (typeof seed === "number" && Number.isFinite(seed)) return Math.abs(Math.trunc(seed)) || 1;
|
|
299
|
+
let hash = 2166136261;
|
|
300
|
+
const text = String(seed);
|
|
301
|
+
for (let i = 0; i < text.length; i += 1) {
|
|
302
|
+
hash ^= text.charCodeAt(i);
|
|
303
|
+
hash = Math.imul(hash, 16777619);
|
|
304
|
+
}
|
|
305
|
+
return hash >>> 0 || 1;
|
|
306
|
+
}
|
|
307
|
+
function shuffleChoiceIndices(length, seed) {
|
|
308
|
+
const indices = Array.from({ length }, (_, i) => i);
|
|
309
|
+
if (length <= 1) return indices;
|
|
310
|
+
let state = hashSeedToNumber(seed);
|
|
311
|
+
for (let i = length - 1; i > 0; i -= 1) {
|
|
312
|
+
state = Math.imul(state, 1664525) + 1013904223 >>> 0;
|
|
313
|
+
const j = state % (i + 1);
|
|
314
|
+
[indices[i], indices[j]] = [indices[j], indices[i]];
|
|
315
|
+
}
|
|
316
|
+
return indices;
|
|
317
|
+
}
|
|
318
|
+
function resolveMcqShuffleSeed(props) {
|
|
319
|
+
return props.shuffleSeed ?? props.checkId;
|
|
320
|
+
}
|
|
321
|
+
function orderChoicesByIndices(choices, orderIndices) {
|
|
322
|
+
return orderIndices.map((index) => choices[index] ?? "").filter((c) => c.length > 0);
|
|
323
|
+
}
|
|
324
|
+
|
|
252
325
|
// src/compound.ts
|
|
253
326
|
var COMPOUND_RESUME_SCHEMA_VERSION = 1;
|
|
254
327
|
function createCompoundResumeState(input = {}) {
|
|
@@ -310,9 +383,11 @@ function parseCompoundResumeState(raw, opts) {
|
|
|
310
383
|
opts?.onDroppedChildKeys?.(droppedChildKeys);
|
|
311
384
|
}
|
|
312
385
|
const activeChapterIndex = typeof obj.activeChapterIndex === "number" && Number.isFinite(obj.activeChapterIndex) ? obj.activeChapterIndex : void 0;
|
|
386
|
+
const rawPageIndex = Math.max(0, Math.floor(obj.activePageIndex));
|
|
387
|
+
const activePageIndex = typeof opts?.pageCount === "number" && opts.pageCount > 0 ? clampCompoundPageIndex(rawPageIndex, opts.pageCount) : rawPageIndex;
|
|
313
388
|
return {
|
|
314
389
|
schemaVersion: COMPOUND_RESUME_SCHEMA_VERSION,
|
|
315
|
-
activePageIndex
|
|
390
|
+
activePageIndex,
|
|
316
391
|
...activeChapterIndex !== void 0 ? { activeChapterIndex: Math.max(0, Math.floor(activeChapterIndex)) } : {},
|
|
317
392
|
childStates
|
|
318
393
|
};
|
|
@@ -362,6 +437,11 @@ function clearCompoundState(storage, courseId, compoundId) {
|
|
|
362
437
|
}
|
|
363
438
|
|
|
364
439
|
// src/compoundAllowlists.ts
|
|
440
|
+
var ASSESSMENT_17_CHILD_TYPES = [
|
|
441
|
+
"SortParagraphs",
|
|
442
|
+
"GuessTheAnswer",
|
|
443
|
+
"MultimediaChoice"
|
|
444
|
+
];
|
|
365
445
|
var PAGE_AND_SLIDE_14_BLOCKS = [
|
|
366
446
|
"Video",
|
|
367
447
|
"Summary",
|
|
@@ -406,7 +486,18 @@ var PAGE_ALLOWED_CHILD_TYPES = [
|
|
|
406
486
|
"ImageSlider",
|
|
407
487
|
"Embed",
|
|
408
488
|
"Chart",
|
|
409
|
-
"
|
|
489
|
+
"Table",
|
|
490
|
+
"ImageJuxtaposition",
|
|
491
|
+
"Timeline",
|
|
492
|
+
"ImageSequence",
|
|
493
|
+
"Collage",
|
|
494
|
+
"AudioRecorder",
|
|
495
|
+
"CombinationLock",
|
|
496
|
+
"QrContent",
|
|
497
|
+
"Crossword",
|
|
498
|
+
"AdventCalendar",
|
|
499
|
+
"ProgressTracker",
|
|
500
|
+
...ASSESSMENT_17_CHILD_TYPES
|
|
410
501
|
];
|
|
411
502
|
var BRANCH_NODE_ALLOWED_CHILD_TYPES = [
|
|
412
503
|
"Text",
|
|
@@ -440,9 +531,66 @@ var BRANCH_NODE_ALLOWED_CHILD_TYPES = [
|
|
|
440
531
|
"ImageSlider",
|
|
441
532
|
"Embed",
|
|
442
533
|
"Chart",
|
|
443
|
-
"
|
|
534
|
+
"Table",
|
|
535
|
+
"ImageJuxtaposition",
|
|
536
|
+
"Timeline",
|
|
537
|
+
"ImageSequence",
|
|
538
|
+
"Collage",
|
|
539
|
+
"AudioRecorder",
|
|
540
|
+
"CombinationLock",
|
|
541
|
+
"QrContent",
|
|
542
|
+
"Crossword",
|
|
543
|
+
"AdventCalendar",
|
|
544
|
+
"BranchChoice",
|
|
545
|
+
...ASSESSMENT_17_CHILD_TYPES
|
|
444
546
|
];
|
|
445
547
|
var BRANCHING_SCENARIO_ALLOWED_CHILD_TYPES = ["BranchNode"];
|
|
548
|
+
var GAME_MAP_ALLOWED_CHILD_TYPES = ["MapStage"];
|
|
549
|
+
var MAP_STAGE_ALLOWED_CHILD_TYPES = [
|
|
550
|
+
"Text",
|
|
551
|
+
"Heading",
|
|
552
|
+
"Image",
|
|
553
|
+
"Video",
|
|
554
|
+
"Scenario",
|
|
555
|
+
"Reflection",
|
|
556
|
+
"Quiz",
|
|
557
|
+
"KnowledgeCheck",
|
|
558
|
+
"TrueFalse",
|
|
559
|
+
"FillInTheBlanks",
|
|
560
|
+
"DragAndDrop",
|
|
561
|
+
"DragTheWords",
|
|
562
|
+
"MarkTheWords",
|
|
563
|
+
"Summary",
|
|
564
|
+
"ImagePairing",
|
|
565
|
+
"ImageSequencing",
|
|
566
|
+
"MemoryGame",
|
|
567
|
+
"InformationWall",
|
|
568
|
+
"ParallaxSlideshow",
|
|
569
|
+
"Questionnaire",
|
|
570
|
+
"Essay",
|
|
571
|
+
"ArithmeticQuiz",
|
|
572
|
+
"Accordion",
|
|
573
|
+
"DialogCards",
|
|
574
|
+
"Flashcards",
|
|
575
|
+
"ImageHotspots",
|
|
576
|
+
"FindHotspot",
|
|
577
|
+
"FindMultipleHotspots",
|
|
578
|
+
"ImageSlider",
|
|
579
|
+
"Embed",
|
|
580
|
+
"Chart",
|
|
581
|
+
"Table",
|
|
582
|
+
"ImageJuxtaposition",
|
|
583
|
+
"Timeline",
|
|
584
|
+
"ImageSequence",
|
|
585
|
+
"Collage",
|
|
586
|
+
"AudioRecorder",
|
|
587
|
+
"CombinationLock",
|
|
588
|
+
"QrContent",
|
|
589
|
+
"Crossword",
|
|
590
|
+
"AdventCalendar",
|
|
591
|
+
"MapExit",
|
|
592
|
+
...ASSESSMENT_17_CHILD_TYPES
|
|
593
|
+
];
|
|
446
594
|
var INTERACTIVE_BOOK_ALLOWED_CHILD_TYPES = ["Page"];
|
|
447
595
|
var SLIDE_ALLOWED_CHILD_TYPES = [
|
|
448
596
|
"Text",
|
|
@@ -475,7 +623,18 @@ var SLIDE_ALLOWED_CHILD_TYPES = [
|
|
|
475
623
|
"FindMultipleHotspots",
|
|
476
624
|
"ImageSlider",
|
|
477
625
|
"Embed",
|
|
478
|
-
"Chart"
|
|
626
|
+
"Chart",
|
|
627
|
+
"Table",
|
|
628
|
+
"ImageJuxtaposition",
|
|
629
|
+
"Timeline",
|
|
630
|
+
"ImageSequence",
|
|
631
|
+
"Collage",
|
|
632
|
+
"AudioRecorder",
|
|
633
|
+
"CombinationLock",
|
|
634
|
+
"QrContent",
|
|
635
|
+
"Crossword",
|
|
636
|
+
"AdventCalendar",
|
|
637
|
+
...ASSESSMENT_17_CHILD_TYPES
|
|
479
638
|
];
|
|
480
639
|
var SLIDE_DECK_ALLOWED_CHILD_TYPES = ["Slide"];
|
|
481
640
|
var TIMED_CUE_ALLOWED_CHILD_TYPES = [
|
|
@@ -491,7 +650,9 @@ var TIMED_CUE_ALLOWED_CHILD_TYPES = [
|
|
|
491
650
|
"MemoryGame",
|
|
492
651
|
"Questionnaire",
|
|
493
652
|
"Essay",
|
|
494
|
-
"ArithmeticQuiz"
|
|
653
|
+
"ArithmeticQuiz",
|
|
654
|
+
"MultimediaChoice",
|
|
655
|
+
"GuessTheAnswer"
|
|
495
656
|
];
|
|
496
657
|
var INTERACTIVE_VIDEO_ALLOWED_CHILD_TYPES = ["TimedCue"];
|
|
497
658
|
var ASSESSMENT_SEQUENCE_ALLOWED_CHILD_TYPES = [
|
|
@@ -508,8 +669,10 @@ var ASSESSMENT_SEQUENCE_ALLOWED_CHILD_TYPES = [
|
|
|
508
669
|
"ImagePairing",
|
|
509
670
|
"ImageSequencing",
|
|
510
671
|
"ArithmeticQuiz",
|
|
511
|
-
"Essay"
|
|
672
|
+
"Essay",
|
|
673
|
+
...ASSESSMENT_17_CHILD_TYPES
|
|
512
674
|
];
|
|
675
|
+
var SINGLE_CHOICE_SET_ALLOWED_CHILD_TYPES = ["Quiz", "KnowledgeCheck"];
|
|
513
676
|
var ALLOWLISTS = {
|
|
514
677
|
Page: PAGE_ALLOWED_CHILD_TYPES,
|
|
515
678
|
InteractiveBook: INTERACTIVE_BOOK_ALLOWED_CHILD_TYPES,
|
|
@@ -519,7 +682,10 @@ var ALLOWLISTS = {
|
|
|
519
682
|
InteractiveVideo: INTERACTIVE_VIDEO_ALLOWED_CHILD_TYPES,
|
|
520
683
|
AssessmentSequence: ASSESSMENT_SEQUENCE_ALLOWED_CHILD_TYPES,
|
|
521
684
|
BranchingScenario: BRANCHING_SCENARIO_ALLOWED_CHILD_TYPES,
|
|
522
|
-
BranchNode: BRANCH_NODE_ALLOWED_CHILD_TYPES
|
|
685
|
+
BranchNode: BRANCH_NODE_ALLOWED_CHILD_TYPES,
|
|
686
|
+
GameMap: GAME_MAP_ALLOWED_CHILD_TYPES,
|
|
687
|
+
MapStage: MAP_STAGE_ALLOWED_CHILD_TYPES,
|
|
688
|
+
SingleChoiceSet: SINGLE_CHOICE_SET_ALLOWED_CHILD_TYPES
|
|
523
689
|
};
|
|
524
690
|
var COMPOUND_MAX_NESTING_DEPTH = {
|
|
525
691
|
Page: 1,
|
|
@@ -530,7 +696,10 @@ var COMPOUND_MAX_NESTING_DEPTH = {
|
|
|
530
696
|
InteractiveVideo: 2,
|
|
531
697
|
AssessmentSequence: 1,
|
|
532
698
|
BranchingScenario: 2,
|
|
533
|
-
BranchNode: 1
|
|
699
|
+
BranchNode: 1,
|
|
700
|
+
GameMap: 2,
|
|
701
|
+
MapStage: 1,
|
|
702
|
+
SingleChoiceSet: 1
|
|
534
703
|
};
|
|
535
704
|
function getAllowedChildTypes(parent) {
|
|
536
705
|
return ALLOWLISTS[parent];
|
|
@@ -835,6 +1004,78 @@ var TELEMETRY_EVENT_CATALOG_V3 = [
|
|
|
835
1004
|
dataFields: ["blockId", "fromNodeId", "toNodeId", "label", "scoreWeight"],
|
|
836
1005
|
xapiVerb: "http://adlnet.gov/expapi/verbs/answered",
|
|
837
1006
|
urnPattern: "urn:lessonkit:course:{courseId}:lesson:{lessonId}:block:{blockId}:node:{toNodeId}"
|
|
1007
|
+
},
|
|
1008
|
+
{
|
|
1009
|
+
name: "image_juxtaposition_changed",
|
|
1010
|
+
description: "Learner adjusted the before/after divider",
|
|
1011
|
+
requiredFields: ["courseId", "sessionId", "timestamp"],
|
|
1012
|
+
dataFields: ["blockId", "position"],
|
|
1013
|
+
xapiVerb: "http://adlnet.gov/expapi/verbs/interacted",
|
|
1014
|
+
urnPattern: "urn:lessonkit:course:{courseId}:lesson:{lessonId}:block:{blockId}"
|
|
1015
|
+
},
|
|
1016
|
+
{
|
|
1017
|
+
name: "timeline_event_viewed",
|
|
1018
|
+
description: "Learner focused a timeline event",
|
|
1019
|
+
requiredFields: ["courseId", "sessionId", "timestamp"],
|
|
1020
|
+
dataFields: ["blockId", "eventId"],
|
|
1021
|
+
xapiVerb: "http://adlnet.gov/expapi/verbs/experienced",
|
|
1022
|
+
urnPattern: "urn:lessonkit:course:{courseId}:lesson:{lessonId}:block:{blockId}"
|
|
1023
|
+
},
|
|
1024
|
+
{
|
|
1025
|
+
name: "image_sequence_changed",
|
|
1026
|
+
description: "Learner changed the image sequence frame",
|
|
1027
|
+
requiredFields: ["courseId", "sessionId", "timestamp"],
|
|
1028
|
+
dataFields: ["blockId", "frameIndex"],
|
|
1029
|
+
xapiVerb: "http://adlnet.gov/expapi/verbs/interacted",
|
|
1030
|
+
urnPattern: "urn:lessonkit:course:{courseId}:lesson:{lessonId}:block:{blockId}"
|
|
1031
|
+
},
|
|
1032
|
+
{
|
|
1033
|
+
name: "audio_recording_started",
|
|
1034
|
+
description: "Learner started an audio recording",
|
|
1035
|
+
requiredFields: ["courseId", "sessionId", "timestamp"],
|
|
1036
|
+
dataFields: ["blockId"],
|
|
1037
|
+
xapiVerb: "http://adlnet.gov/expapi/verbs/interacted",
|
|
1038
|
+
urnPattern: "urn:lessonkit:course:{courseId}:lesson:{lessonId}:block:{blockId}"
|
|
1039
|
+
},
|
|
1040
|
+
{
|
|
1041
|
+
name: "audio_recording_completed",
|
|
1042
|
+
description: "Learner completed an audio recording",
|
|
1043
|
+
requiredFields: ["courseId", "sessionId", "timestamp"],
|
|
1044
|
+
dataFields: ["blockId"],
|
|
1045
|
+
xapiVerb: "http://adlnet.gov/expapi/verbs/completed",
|
|
1046
|
+
urnPattern: "urn:lessonkit:course:{courseId}:lesson:{lessonId}:block:{blockId}"
|
|
1047
|
+
},
|
|
1048
|
+
{
|
|
1049
|
+
name: "qr_content_revealed",
|
|
1050
|
+
description: "Learner revealed QR hidden content",
|
|
1051
|
+
requiredFields: ["courseId", "sessionId", "timestamp"],
|
|
1052
|
+
dataFields: ["blockId"],
|
|
1053
|
+
xapiVerb: "http://adlnet.gov/expapi/verbs/experienced",
|
|
1054
|
+
urnPattern: "urn:lessonkit:course:{courseId}:lesson:{lessonId}:block:{blockId}"
|
|
1055
|
+
},
|
|
1056
|
+
{
|
|
1057
|
+
name: "advent_door_opened",
|
|
1058
|
+
description: "Learner opened an advent calendar door",
|
|
1059
|
+
requiredFields: ["courseId", "sessionId", "timestamp"],
|
|
1060
|
+
dataFields: ["blockId", "doorId", "day"],
|
|
1061
|
+
xapiVerb: "http://adlnet.gov/expapi/verbs/interacted",
|
|
1062
|
+
urnPattern: "urn:lessonkit:course:{courseId}:lesson:{lessonId}:block:{blockId}"
|
|
1063
|
+
},
|
|
1064
|
+
{
|
|
1065
|
+
name: "map_stage_viewed",
|
|
1066
|
+
description: "Learner viewed a stage in a GameMap",
|
|
1067
|
+
requiredFields: ["courseId", "lessonId", "sessionId", "timestamp"],
|
|
1068
|
+
dataFields: ["blockId", "stageId", "stageIndex", "stageLabel"],
|
|
1069
|
+
xapiVerb: "http://adlnet.gov/expapi/verbs/experienced",
|
|
1070
|
+
urnPattern: "urn:lessonkit:course:{courseId}:lesson:{lessonId}:block:{blockId}:stage:{stageId}"
|
|
1071
|
+
},
|
|
1072
|
+
{
|
|
1073
|
+
name: "map_exit_selected",
|
|
1074
|
+
description: "Learner selected a map exit in a GameMap",
|
|
1075
|
+
requiredFields: ["courseId", "lessonId", "sessionId", "timestamp"],
|
|
1076
|
+
dataFields: ["blockId", "fromStageId", "toStageId", "label", "scoreWeight"],
|
|
1077
|
+
xapiVerb: "http://adlnet.gov/expapi/verbs/answered",
|
|
1078
|
+
urnPattern: "urn:lessonkit:course:{courseId}:lesson:{lessonId}:block:{blockId}:stage:{toStageId}"
|
|
838
1079
|
}
|
|
839
1080
|
];
|
|
840
1081
|
function buildTelemetryCatalogV3() {
|
|
@@ -931,6 +1172,10 @@ function createTrackingClient(opts) {
|
|
|
931
1172
|
const runFlush = () => {
|
|
932
1173
|
if (!buffer.length) return Promise.resolve(true);
|
|
933
1174
|
const events = buffer.splice(0, buffer.length);
|
|
1175
|
+
for (const event of events) {
|
|
1176
|
+
const key = eventDedupKey(event);
|
|
1177
|
+
if (key) pendingDeliverIds.add(key);
|
|
1178
|
+
}
|
|
934
1179
|
let succeeded = false;
|
|
935
1180
|
return Promise.resolve().then(async () => {
|
|
936
1181
|
if (batchSink) {
|
|
@@ -940,7 +1185,7 @@ function createTrackingClient(opts) {
|
|
|
940
1185
|
try {
|
|
941
1186
|
await sink?.(events[i]);
|
|
942
1187
|
} catch {
|
|
943
|
-
buffer.unshift(...events
|
|
1188
|
+
buffer.unshift(...events);
|
|
944
1189
|
return;
|
|
945
1190
|
}
|
|
946
1191
|
}
|
|
@@ -1064,7 +1309,8 @@ function randomSessionIdFallback() {
|
|
|
1064
1309
|
if (g.crypto?.getRandomValues) {
|
|
1065
1310
|
const bytes = new Uint8Array(16);
|
|
1066
1311
|
g.crypto.getRandomValues(bytes);
|
|
1067
|
-
|
|
1312
|
+
const hex = Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
1313
|
+
return `s-${hex}`;
|
|
1068
1314
|
}
|
|
1069
1315
|
throw new Error(
|
|
1070
1316
|
"[lessonkit] createSessionId requires crypto.randomUUID or crypto.getRandomValues"
|
|
@@ -1072,7 +1318,9 @@ function randomSessionIdFallback() {
|
|
|
1072
1318
|
}
|
|
1073
1319
|
function createSessionId() {
|
|
1074
1320
|
const g = globalThis;
|
|
1075
|
-
if (g.crypto?.randomUUID)
|
|
1321
|
+
if (g.crypto?.randomUUID) {
|
|
1322
|
+
return `s-${g.crypto.randomUUID().replace(/-/g, "")}`;
|
|
1323
|
+
}
|
|
1076
1324
|
return randomSessionIdFallback();
|
|
1077
1325
|
}
|
|
1078
1326
|
|
|
@@ -1380,6 +1628,90 @@ var TELEMETRY_EVENT_REGISTRY = {
|
|
|
1380
1628
|
data: opts.data
|
|
1381
1629
|
};
|
|
1382
1630
|
}
|
|
1631
|
+
},
|
|
1632
|
+
image_juxtaposition_changed: {
|
|
1633
|
+
build: (opts, base) => ({
|
|
1634
|
+
name: "image_juxtaposition_changed",
|
|
1635
|
+
...base,
|
|
1636
|
+
lessonId: opts.lessonId,
|
|
1637
|
+
data: opts.data
|
|
1638
|
+
})
|
|
1639
|
+
},
|
|
1640
|
+
timeline_event_viewed: {
|
|
1641
|
+
build: (opts, base) => ({
|
|
1642
|
+
name: "timeline_event_viewed",
|
|
1643
|
+
...base,
|
|
1644
|
+
lessonId: opts.lessonId,
|
|
1645
|
+
data: opts.data
|
|
1646
|
+
})
|
|
1647
|
+
},
|
|
1648
|
+
image_sequence_changed: {
|
|
1649
|
+
build: (opts, base) => ({
|
|
1650
|
+
name: "image_sequence_changed",
|
|
1651
|
+
...base,
|
|
1652
|
+
lessonId: opts.lessonId,
|
|
1653
|
+
data: opts.data
|
|
1654
|
+
})
|
|
1655
|
+
},
|
|
1656
|
+
audio_recording_started: {
|
|
1657
|
+
build: (opts, base) => ({
|
|
1658
|
+
name: "audio_recording_started",
|
|
1659
|
+
...base,
|
|
1660
|
+
lessonId: opts.lessonId,
|
|
1661
|
+
data: opts.data
|
|
1662
|
+
})
|
|
1663
|
+
},
|
|
1664
|
+
audio_recording_completed: {
|
|
1665
|
+
build: (opts, base) => ({
|
|
1666
|
+
name: "audio_recording_completed",
|
|
1667
|
+
...base,
|
|
1668
|
+
lessonId: opts.lessonId,
|
|
1669
|
+
data: opts.data
|
|
1670
|
+
})
|
|
1671
|
+
},
|
|
1672
|
+
qr_content_revealed: {
|
|
1673
|
+
build: (opts, base) => ({
|
|
1674
|
+
name: "qr_content_revealed",
|
|
1675
|
+
...base,
|
|
1676
|
+
lessonId: opts.lessonId,
|
|
1677
|
+
data: opts.data
|
|
1678
|
+
})
|
|
1679
|
+
},
|
|
1680
|
+
advent_door_opened: {
|
|
1681
|
+
build: (opts, base) => ({
|
|
1682
|
+
name: "advent_door_opened",
|
|
1683
|
+
...base,
|
|
1684
|
+
lessonId: opts.lessonId,
|
|
1685
|
+
data: opts.data
|
|
1686
|
+
})
|
|
1687
|
+
},
|
|
1688
|
+
map_stage_viewed: {
|
|
1689
|
+
requiresLessonId: true,
|
|
1690
|
+
build: (opts, base) => {
|
|
1691
|
+
if (opts.name !== "map_stage_viewed") throw new Error("unexpected event");
|
|
1692
|
+
const lessonId = opts.lessonId;
|
|
1693
|
+
if (!lessonId) throw new Error("map_stage_viewed requires active lessonId");
|
|
1694
|
+
return {
|
|
1695
|
+
name: "map_stage_viewed",
|
|
1696
|
+
...base,
|
|
1697
|
+
lessonId,
|
|
1698
|
+
data: opts.data
|
|
1699
|
+
};
|
|
1700
|
+
}
|
|
1701
|
+
},
|
|
1702
|
+
map_exit_selected: {
|
|
1703
|
+
requiresLessonId: true,
|
|
1704
|
+
build: (opts, base) => {
|
|
1705
|
+
if (opts.name !== "map_exit_selected") throw new Error("unexpected event");
|
|
1706
|
+
const lessonId = opts.lessonId;
|
|
1707
|
+
if (!lessonId) throw new Error("map_exit_selected requires active lessonId");
|
|
1708
|
+
return {
|
|
1709
|
+
name: "map_exit_selected",
|
|
1710
|
+
...base,
|
|
1711
|
+
lessonId,
|
|
1712
|
+
data: opts.data
|
|
1713
|
+
};
|
|
1714
|
+
}
|
|
1383
1715
|
}
|
|
1384
1716
|
};
|
|
1385
1717
|
function buildTelemetryEventFromRegistry(opts) {
|
|
@@ -1540,26 +1872,35 @@ function createMemoryBackedSessionStorage(session) {
|
|
|
1540
1872
|
);
|
|
1541
1873
|
}
|
|
1542
1874
|
};
|
|
1875
|
+
const bypassCacheForKey = (key) => key === "lessonkit:sessionId" || key.startsWith("lessonkit:course_started");
|
|
1543
1876
|
return {
|
|
1544
1877
|
getItem: (key) => {
|
|
1545
1878
|
if (tombstones.has(key)) return null;
|
|
1546
|
-
if (memory.has(key)) return memory.get(key);
|
|
1879
|
+
if (!bypassCacheForKey(key) && memory.has(key)) return memory.get(key);
|
|
1547
1880
|
try {
|
|
1548
1881
|
const value = session.getItem(key);
|
|
1549
|
-
if (value !== null)
|
|
1550
|
-
|
|
1882
|
+
if (value !== null) {
|
|
1883
|
+
memory.set(key, value);
|
|
1884
|
+
return value;
|
|
1885
|
+
}
|
|
1886
|
+
if (bypassCacheForKey(key) && memory.has(key)) {
|
|
1887
|
+
return memory.get(key);
|
|
1888
|
+
}
|
|
1889
|
+
if (bypassCacheForKey(key)) memory.delete(key);
|
|
1890
|
+
return null;
|
|
1551
1891
|
} catch {
|
|
1552
1892
|
return memory.get(key) ?? null;
|
|
1553
1893
|
}
|
|
1554
1894
|
},
|
|
1555
1895
|
setItem: (key, value) => {
|
|
1556
1896
|
tombstones.delete(key);
|
|
1557
|
-
memory.set(key, value);
|
|
1558
1897
|
try {
|
|
1559
1898
|
session.setItem(key, value);
|
|
1899
|
+
memory.set(key, value);
|
|
1560
1900
|
return true;
|
|
1561
1901
|
} catch {
|
|
1562
1902
|
warnPersistFailure();
|
|
1903
|
+
memory.set(key, value);
|
|
1563
1904
|
return false;
|
|
1564
1905
|
}
|
|
1565
1906
|
},
|
|
@@ -1672,6 +2013,29 @@ function createProgressController() {
|
|
|
1672
2013
|
// src/session.ts
|
|
1673
2014
|
var SESSION_STORAGE_KEY = "lessonkit:sessionId";
|
|
1674
2015
|
var volatileSessionIds = /* @__PURE__ */ new WeakMap();
|
|
2016
|
+
var volatileStorageMarks = /* @__PURE__ */ new WeakMap();
|
|
2017
|
+
function rememberVolatileMark(storage, key) {
|
|
2018
|
+
let keys = volatileStorageMarks.get(storage);
|
|
2019
|
+
if (!keys) {
|
|
2020
|
+
keys = /* @__PURE__ */ new Set();
|
|
2021
|
+
volatileStorageMarks.set(storage, keys);
|
|
2022
|
+
}
|
|
2023
|
+
keys.add(key);
|
|
2024
|
+
}
|
|
2025
|
+
function hasVolatileMark(storage, key) {
|
|
2026
|
+
return volatileStorageMarks.get(storage)?.has(key) ?? false;
|
|
2027
|
+
}
|
|
2028
|
+
function clearVolatileMark(storage, key) {
|
|
2029
|
+
volatileStorageMarks.get(storage)?.delete(key);
|
|
2030
|
+
}
|
|
2031
|
+
function storageHasMark(storage, key) {
|
|
2032
|
+
return storage.getItem(key) === "1" || hasVolatileMark(storage, key);
|
|
2033
|
+
}
|
|
2034
|
+
function storageSetMark(storage, key) {
|
|
2035
|
+
const persisted = storage.setItem(key, "1");
|
|
2036
|
+
if (!persisted) rememberVolatileMark(storage, key);
|
|
2037
|
+
return persisted;
|
|
2038
|
+
}
|
|
1675
2039
|
function isDevEnvironment2() {
|
|
1676
2040
|
const g = globalThis;
|
|
1677
2041
|
return typeof g.process !== "undefined" && g.process.env?.NODE_ENV !== "production";
|
|
@@ -1687,21 +2051,7 @@ function sessionKeySegment(sessionId) {
|
|
|
1687
2051
|
const validated = validateId(sessionId);
|
|
1688
2052
|
return validated.ok ? validated.id : encodeURIComponent(sessionId);
|
|
1689
2053
|
}
|
|
1690
|
-
function
|
|
1691
|
-
if (provided !== void 0) {
|
|
1692
|
-
const trimmed = provided.trim();
|
|
1693
|
-
if (trimmed.length > 0) {
|
|
1694
|
-
const validated = validateId(trimmed);
|
|
1695
|
-
if (validated.ok) return validated.id;
|
|
1696
|
-
if (isDevEnvironment2()) {
|
|
1697
|
-
console.warn(
|
|
1698
|
-
`[lessonkit] Invalid sessionId "${trimmed}"; falling back to tab or generated id.`
|
|
1699
|
-
);
|
|
1700
|
-
}
|
|
1701
|
-
}
|
|
1702
|
-
}
|
|
1703
|
-
const existing = storage.getItem(SESSION_STORAGE_KEY);
|
|
1704
|
-
if (existing) return existing;
|
|
2054
|
+
function resolveGeneratedSessionId(storage) {
|
|
1705
2055
|
const volatile = volatileSessionIds.get(storage);
|
|
1706
2056
|
if (volatile) return volatile;
|
|
1707
2057
|
const id = createSessionId();
|
|
@@ -1717,6 +2067,50 @@ function resolveSessionId(storage, provided) {
|
|
|
1717
2067
|
}
|
|
1718
2068
|
return id;
|
|
1719
2069
|
}
|
|
2070
|
+
function resolveFallbackSessionId(storage, options) {
|
|
2071
|
+
const existing = storage.getItem(SESSION_STORAGE_KEY);
|
|
2072
|
+
if (existing) {
|
|
2073
|
+
const trimmedExisting = existing.trim();
|
|
2074
|
+
const validatedExisting = validateId(trimmedExisting);
|
|
2075
|
+
if (validatedExisting.ok) return validatedExisting.id;
|
|
2076
|
+
storage.removeItem?.(SESSION_STORAGE_KEY);
|
|
2077
|
+
if (isDevEnvironment2()) {
|
|
2078
|
+
console.warn(
|
|
2079
|
+
`[lessonkit] Invalid stored sessionId "${existing}"; generating a new id.`
|
|
2080
|
+
);
|
|
2081
|
+
}
|
|
2082
|
+
const fallback = resolveGeneratedSessionId(storage);
|
|
2083
|
+
options?.onInvalidSessionId?.({
|
|
2084
|
+
invalidId: existing,
|
|
2085
|
+
fallbackId: fallback,
|
|
2086
|
+
source: "stored"
|
|
2087
|
+
});
|
|
2088
|
+
return fallback;
|
|
2089
|
+
}
|
|
2090
|
+
return resolveGeneratedSessionId(storage);
|
|
2091
|
+
}
|
|
2092
|
+
function resolveSessionId(storage, provided, options) {
|
|
2093
|
+
if (provided !== void 0) {
|
|
2094
|
+
const trimmed = provided.trim();
|
|
2095
|
+
if (trimmed.length > 0) {
|
|
2096
|
+
const validated = validateId(trimmed);
|
|
2097
|
+
if (validated.ok) return validated.id;
|
|
2098
|
+
if (isDevEnvironment2()) {
|
|
2099
|
+
console.warn(
|
|
2100
|
+
`[lessonkit] Invalid sessionId "${trimmed}"; falling back to tab or generated id.`
|
|
2101
|
+
);
|
|
2102
|
+
}
|
|
2103
|
+
const fallback = resolveFallbackSessionId(storage, options);
|
|
2104
|
+
options?.onInvalidSessionId?.({
|
|
2105
|
+
invalidId: trimmed,
|
|
2106
|
+
fallbackId: fallback,
|
|
2107
|
+
source: "provided"
|
|
2108
|
+
});
|
|
2109
|
+
return fallback;
|
|
2110
|
+
}
|
|
2111
|
+
}
|
|
2112
|
+
return resolveFallbackSessionId(storage, options);
|
|
2113
|
+
}
|
|
1720
2114
|
function courseStartedStorageKey(sessionId, courseId) {
|
|
1721
2115
|
return `${COURSE_STARTED_PREFIX}${sessionKeySegment(sessionId)}:${courseId ?? ""}`;
|
|
1722
2116
|
}
|
|
@@ -1731,35 +2125,35 @@ function courseStartedXapiStorageKey(sessionId, courseId) {
|
|
|
1731
2125
|
}
|
|
1732
2126
|
function hasCourseStarted(storage, sessionId, courseId) {
|
|
1733
2127
|
if (!courseId) return false;
|
|
1734
|
-
return storage
|
|
2128
|
+
return storageHasMark(storage, courseStartedStorageKey(sessionId, courseId));
|
|
1735
2129
|
}
|
|
1736
2130
|
function markCourseStarted(storage, sessionId, courseId) {
|
|
1737
2131
|
if (!courseId) return false;
|
|
1738
|
-
return storage
|
|
2132
|
+
return storageSetMark(storage, courseStartedStorageKey(sessionId, courseId));
|
|
1739
2133
|
}
|
|
1740
2134
|
function hasCourseStartedEmittedToTracking(storage, sessionId, courseId) {
|
|
1741
2135
|
if (!courseId) return false;
|
|
1742
|
-
return storage
|
|
2136
|
+
return storageHasMark(storage, courseStartedTrackingStorageKey(sessionId, courseId));
|
|
1743
2137
|
}
|
|
1744
2138
|
function markCourseStartedEmittedToTracking(storage, sessionId, courseId) {
|
|
1745
2139
|
if (!courseId) return false;
|
|
1746
|
-
return storage
|
|
2140
|
+
return storageSetMark(storage, courseStartedTrackingStorageKey(sessionId, courseId));
|
|
1747
2141
|
}
|
|
1748
2142
|
function hasCourseStartedPipelineDelivered(storage, sessionId, courseId) {
|
|
1749
2143
|
if (!courseId) return false;
|
|
1750
|
-
return storage
|
|
2144
|
+
return storageHasMark(storage, courseStartedPipelineStorageKey(sessionId, courseId));
|
|
1751
2145
|
}
|
|
1752
2146
|
function markCourseStartedPipelineDelivered(storage, sessionId, courseId) {
|
|
1753
2147
|
if (!courseId) return false;
|
|
1754
|
-
return storage
|
|
2148
|
+
return storageSetMark(storage, courseStartedPipelineStorageKey(sessionId, courseId));
|
|
1755
2149
|
}
|
|
1756
2150
|
function hasCourseStartedXapiSent(storage, sessionId, courseId) {
|
|
1757
2151
|
if (!courseId) return false;
|
|
1758
|
-
return storage
|
|
2152
|
+
return storageHasMark(storage, courseStartedXapiStorageKey(sessionId, courseId));
|
|
1759
2153
|
}
|
|
1760
2154
|
function markCourseStartedXapiSent(storage, sessionId, courseId) {
|
|
1761
2155
|
if (!courseId) return false;
|
|
1762
|
-
return storage
|
|
2156
|
+
return storageSetMark(storage, courseStartedXapiStorageKey(sessionId, courseId));
|
|
1763
2157
|
}
|
|
1764
2158
|
function resetSharedVolatileSessionIdForTests() {
|
|
1765
2159
|
}
|
|
@@ -1767,6 +2161,7 @@ function migrateStorageMark(storage, fromKey, toKey, hasMark) {
|
|
|
1767
2161
|
if (!hasMark) return;
|
|
1768
2162
|
if (storage.setItem(toKey, "1")) {
|
|
1769
2163
|
storage.removeItem?.(fromKey);
|
|
2164
|
+
clearVolatileMark(storage, fromKey);
|
|
1770
2165
|
}
|
|
1771
2166
|
}
|
|
1772
2167
|
function migrateCourseStartedMark(storage, fromSessionId, toSessionId, courseId) {
|
|
@@ -1799,11 +2194,28 @@ function migrateCourseStartedMark(storage, fromSessionId, toSessionId, courseId)
|
|
|
1799
2194
|
|
|
1800
2195
|
// src/runtime/courseLifecycle.ts
|
|
1801
2196
|
var courseStartedEmitFlights = /* @__PURE__ */ new Map();
|
|
2197
|
+
var courseStartedEmittedInTab = /* @__PURE__ */ new Set();
|
|
1802
2198
|
function tryEmitCourseStarted(ctx, deps, alreadyEmittedToSink) {
|
|
1803
2199
|
const flightKey = `${ctx.sessionId}:${ctx.courseId}`;
|
|
1804
2200
|
const marked = hasCourseStarted(ctx.storage, ctx.sessionId, ctx.courseId);
|
|
2201
|
+
if (courseStartedEmittedInTab.has(flightKey)) {
|
|
2202
|
+
return Promise.resolve({
|
|
2203
|
+
emitted: true,
|
|
2204
|
+
marked: marked || markCourseStarted(ctx.storage, ctx.sessionId, ctx.courseId)
|
|
2205
|
+
});
|
|
2206
|
+
}
|
|
1805
2207
|
if (alreadyEmittedToSink) {
|
|
1806
|
-
|
|
2208
|
+
const markPersisted = marked ? true : markCourseStarted(ctx.storage, ctx.sessionId, ctx.courseId);
|
|
2209
|
+
return Promise.resolve({
|
|
2210
|
+
emitted: true,
|
|
2211
|
+
marked: markPersisted
|
|
2212
|
+
});
|
|
2213
|
+
}
|
|
2214
|
+
if (marked && hasCourseStartedEmittedToTracking(ctx.storage, ctx.sessionId, ctx.courseId)) {
|
|
2215
|
+
return Promise.resolve({
|
|
2216
|
+
emitted: true,
|
|
2217
|
+
marked: true
|
|
2218
|
+
});
|
|
1807
2219
|
}
|
|
1808
2220
|
const existing = courseStartedEmitFlights.get(flightKey);
|
|
1809
2221
|
if (existing) {
|
|
@@ -1812,15 +2224,19 @@ function tryEmitCourseStarted(ctx, deps, alreadyEmittedToSink) {
|
|
|
1812
2224
|
const flight = Promise.resolve().then(() => {
|
|
1813
2225
|
try {
|
|
1814
2226
|
const emitted = deps.emitCourseStartedEvent(ctx);
|
|
1815
|
-
if (emitted
|
|
1816
|
-
|
|
2227
|
+
if (emitted) {
|
|
2228
|
+
courseStartedEmittedInTab.add(flightKey);
|
|
1817
2229
|
}
|
|
2230
|
+
const markPersisted = emitted && !marked ? markCourseStarted(ctx.storage, ctx.sessionId, ctx.courseId) : marked;
|
|
1818
2231
|
return {
|
|
1819
2232
|
emitted,
|
|
1820
|
-
marked:
|
|
2233
|
+
marked: markPersisted
|
|
1821
2234
|
};
|
|
1822
2235
|
} catch {
|
|
1823
|
-
return {
|
|
2236
|
+
return {
|
|
2237
|
+
emitted: false,
|
|
2238
|
+
marked: hasCourseStarted(ctx.storage, ctx.sessionId, ctx.courseId)
|
|
2239
|
+
};
|
|
1824
2240
|
} finally {
|
|
1825
2241
|
if (courseStartedEmitFlights.get(flightKey) === flight) {
|
|
1826
2242
|
courseStartedEmitFlights.delete(flightKey);
|
|
@@ -1856,7 +2272,16 @@ function completeCourseWithTelemetry(opts) {
|
|
|
1856
2272
|
});
|
|
1857
2273
|
}
|
|
1858
2274
|
const result = opts.progress.completeCourse();
|
|
1859
|
-
if (!result.didComplete)
|
|
2275
|
+
if (!result.didComplete) {
|
|
2276
|
+
const after = opts.progress.getState();
|
|
2277
|
+
if (after.activeLessonId) {
|
|
2278
|
+
const lessonResult = opts.progress.completeLesson(after.activeLessonId, opts.nowMs);
|
|
2279
|
+
if (lessonResult.didComplete) {
|
|
2280
|
+
opts.emitLessonCompleted(after.activeLessonId, lessonResult.durationMs);
|
|
2281
|
+
}
|
|
2282
|
+
}
|
|
2283
|
+
return false;
|
|
2284
|
+
}
|
|
1860
2285
|
opts.emitCourseCompleted();
|
|
1861
2286
|
return true;
|
|
1862
2287
|
}
|
|
@@ -1979,6 +2404,10 @@ function resolvePluginHost(plugins) {
|
|
|
1979
2404
|
if (Array.isArray(plugins) && plugins.length > 0) return createPluginRegistry(plugins);
|
|
1980
2405
|
return null;
|
|
1981
2406
|
}
|
|
2407
|
+
function pluginListFingerprint(plugins) {
|
|
2408
|
+
if (!plugins || !Array.isArray(plugins)) return null;
|
|
2409
|
+
return plugins.map((p) => `${p.id}\0${p.version ?? ""}`).join("\n");
|
|
2410
|
+
}
|
|
1982
2411
|
function warnRuntimeV1Deprecated() {
|
|
1983
2412
|
const g = globalThis;
|
|
1984
2413
|
if (typeof g.process !== "undefined" && g.process.env?.NODE_ENV === "production") return;
|
|
@@ -1991,12 +2420,19 @@ function createLessonkitRuntime(config, ports = {}) {
|
|
|
1991
2420
|
const storage = ports.storage ?? createSessionStoragePort();
|
|
1992
2421
|
const clock = ports.clock ?? createDefaultClock();
|
|
1993
2422
|
const configSnapshot = { ...config };
|
|
2423
|
+
const hasExplicitSessionId = Boolean(configSnapshot.session?.sessionId?.trim());
|
|
2424
|
+
let autoSessionId;
|
|
1994
2425
|
let sessionId = resolveSessionId(storage, configSnapshot.session?.sessionId);
|
|
2426
|
+
if (!hasExplicitSessionId) {
|
|
2427
|
+
autoSessionId = sessionId;
|
|
2428
|
+
}
|
|
1995
2429
|
let attemptId = configSnapshot.session?.attemptId;
|
|
1996
2430
|
let user = configSnapshot.session?.user;
|
|
1997
2431
|
let courseId = configSnapshot.courseId;
|
|
2432
|
+
let configuredSessionId = configSnapshot.session?.sessionId;
|
|
1998
2433
|
let progress = createProgressController();
|
|
1999
2434
|
let pluginHost = resolvePluginHost(configSnapshot.plugins);
|
|
2435
|
+
let pluginFingerprint = pluginListFingerprint(configSnapshot.plugins);
|
|
2000
2436
|
let disposed = false;
|
|
2001
2437
|
const getPluginCtx = () => buildPluginContext({
|
|
2002
2438
|
courseId,
|
|
@@ -2008,12 +2444,6 @@ function createLessonkitRuntime(config, ports = {}) {
|
|
|
2008
2444
|
pluginHost?.setupAll(getPluginCtx());
|
|
2009
2445
|
}
|
|
2010
2446
|
const getSession = () => ({ sessionId, attemptId, user });
|
|
2011
|
-
const syncSessionFromConfig = (next) => {
|
|
2012
|
-
sessionId = resolveSessionId(storage, next.session?.sessionId);
|
|
2013
|
-
attemptId = next.session?.attemptId;
|
|
2014
|
-
user = next.session?.user;
|
|
2015
|
-
courseId = next.courseId;
|
|
2016
|
-
};
|
|
2017
2447
|
const applyPluginsToEvent = (event) => {
|
|
2018
2448
|
if (!pluginHost) return event;
|
|
2019
2449
|
return pluginHost.runTelemetry(event, getPluginCtx());
|
|
@@ -2035,7 +2465,6 @@ function createLessonkitRuntime(config, ports = {}) {
|
|
|
2035
2465
|
const event = buildAndApply(name, data, lessonId);
|
|
2036
2466
|
if (event) emitFn(event);
|
|
2037
2467
|
};
|
|
2038
|
-
syncSessionFromConfig(configSnapshot);
|
|
2039
2468
|
const track = (name, data, emit, lessonId) => {
|
|
2040
2469
|
if (disposed) return;
|
|
2041
2470
|
const event = buildAndApply(name, data, lessonId);
|
|
@@ -2077,21 +2506,52 @@ function createLessonkitRuntime(config, ports = {}) {
|
|
|
2077
2506
|
if (next.autoCompleteOnLessonSwitch !== void 0) {
|
|
2078
2507
|
configSnapshot.autoCompleteOnLessonSwitch = next.autoCompleteOnLessonSwitch;
|
|
2079
2508
|
}
|
|
2509
|
+
if (next.courseId !== void 0) {
|
|
2510
|
+
courseId = next.courseId;
|
|
2511
|
+
}
|
|
2080
2512
|
if (next.session !== void 0) {
|
|
2513
|
+
const previousSessionId = sessionId;
|
|
2081
2514
|
configSnapshot.session = { ...configSnapshot.session, ...next.session };
|
|
2515
|
+
const explicitSessionId = configSnapshot.session?.sessionId?.trim();
|
|
2516
|
+
if (explicitSessionId) {
|
|
2517
|
+
sessionId = resolveSessionId(storage, explicitSessionId);
|
|
2518
|
+
autoSessionId = void 0;
|
|
2519
|
+
} else {
|
|
2520
|
+
sessionId = autoSessionId ?? resolveSessionId(storage, void 0);
|
|
2521
|
+
if (!autoSessionId) autoSessionId = sessionId;
|
|
2522
|
+
}
|
|
2523
|
+
attemptId = configSnapshot.session?.attemptId;
|
|
2524
|
+
user = configSnapshot.session?.user;
|
|
2525
|
+
if (previousSessionId !== sessionId) {
|
|
2526
|
+
const prevExplicit = configuredSessionId?.trim();
|
|
2527
|
+
const nextExplicit = configSnapshot.session?.sessionId?.trim();
|
|
2528
|
+
const isExplicitLearnerSwap = Boolean(prevExplicit) && Boolean(nextExplicit) && prevExplicit !== nextExplicit;
|
|
2529
|
+
if (!isExplicitLearnerSwap) {
|
|
2530
|
+
migrateCourseStartedMark(storage, previousSessionId, sessionId, courseId);
|
|
2531
|
+
}
|
|
2532
|
+
}
|
|
2533
|
+
configuredSessionId = configSnapshot.session?.sessionId;
|
|
2082
2534
|
}
|
|
2083
|
-
syncSessionFromConfig(configSnapshot);
|
|
2084
2535
|
const sessionKeyAfter = JSON.stringify({ sessionId, attemptId, user });
|
|
2085
2536
|
if (next.courseId !== void 0 && next.courseId !== previousCourseId) {
|
|
2086
2537
|
progress = createProgressController();
|
|
2087
|
-
}
|
|
2088
|
-
if (next.plugins !== void 0 && next.plugins !== configSnapshot.plugins) {
|
|
2089
|
-
pluginHost?.disposeAll();
|
|
2090
|
-
configSnapshot.plugins = next.plugins;
|
|
2091
|
-
pluginHost = resolvePluginHost(configSnapshot.plugins);
|
|
2092
2538
|
if (!configSnapshot.deferPluginSetup) {
|
|
2539
|
+
pluginHost?.disposeAll();
|
|
2093
2540
|
pluginHost?.setupAll(getPluginCtx());
|
|
2094
2541
|
}
|
|
2542
|
+
}
|
|
2543
|
+
if (next.plugins !== void 0) {
|
|
2544
|
+
const nextFingerprint = pluginListFingerprint(next.plugins);
|
|
2545
|
+
const pluginsChanged = next.plugins !== configSnapshot.plugins || nextFingerprint !== null && nextFingerprint !== pluginFingerprint;
|
|
2546
|
+
if (pluginsChanged) {
|
|
2547
|
+
pluginHost?.disposeAll();
|
|
2548
|
+
configSnapshot.plugins = next.plugins;
|
|
2549
|
+
pluginFingerprint = nextFingerprint;
|
|
2550
|
+
pluginHost = resolvePluginHost(configSnapshot.plugins);
|
|
2551
|
+
if (!configSnapshot.deferPluginSetup) {
|
|
2552
|
+
pluginHost?.setupAll(getPluginCtx());
|
|
2553
|
+
}
|
|
2554
|
+
}
|
|
2095
2555
|
} else if (next.session !== void 0 && sessionKeyBefore !== sessionKeyAfter && pluginHost && !configSnapshot.deferPluginSetup) {
|
|
2096
2556
|
pluginHost.disposeAll();
|
|
2097
2557
|
pluginHost.setupAll(getPluginCtx());
|
|
@@ -2146,15 +2606,17 @@ function createLessonkitRuntime(config, ports = {}) {
|
|
|
2146
2606
|
configSnapshot.courseId = nextCourseId;
|
|
2147
2607
|
courseId = nextCourseId;
|
|
2148
2608
|
progress = createProgressController();
|
|
2149
|
-
pluginHost?.disposeAll();
|
|
2150
2609
|
if (!configSnapshot.deferPluginSetup) {
|
|
2610
|
+
pluginHost?.disposeAll();
|
|
2151
2611
|
pluginHost?.setupAll(getPluginCtx());
|
|
2152
2612
|
}
|
|
2153
2613
|
},
|
|
2154
2614
|
dispose() {
|
|
2155
2615
|
if (disposed) return;
|
|
2156
2616
|
disposed = true;
|
|
2157
|
-
|
|
2617
|
+
if (!configSnapshot.deferPluginSetup) {
|
|
2618
|
+
pluginHost?.disposeAll();
|
|
2619
|
+
}
|
|
2158
2620
|
}
|
|
2159
2621
|
};
|
|
2160
2622
|
}
|
|
@@ -2172,18 +2634,22 @@ function defineLifecyclePlugin(plugin) {
|
|
|
2172
2634
|
// Annotate the CommonJS export names for ESM import in node:
|
|
2173
2635
|
0 && (module.exports = {
|
|
2174
2636
|
ACCORDION_FORBIDDEN_CHILD_TYPES,
|
|
2637
|
+
ASSESSMENT_17_CHILD_TYPES,
|
|
2175
2638
|
ASSESSMENT_SEQUENCE_ALLOWED_CHILD_TYPES,
|
|
2176
2639
|
BLOCKS_14_PAGE_SLIDE,
|
|
2177
2640
|
BRANCHING_SCENARIO_ALLOWED_CHILD_TYPES,
|
|
2178
2641
|
BRANCH_NODE_ALLOWED_CHILD_TYPES,
|
|
2179
2642
|
COMPOUND_MAX_NESTING_DEPTH,
|
|
2180
2643
|
COMPOUND_RESUME_SCHEMA_VERSION,
|
|
2644
|
+
GAME_MAP_ALLOWED_CHILD_TYPES,
|
|
2181
2645
|
ID_MAX_LENGTH,
|
|
2182
2646
|
ID_PATTERN,
|
|
2183
2647
|
INTERACTIVE_BOOK_ALLOWED_CHILD_TYPES,
|
|
2184
2648
|
INTERACTIVE_VIDEO_ALLOWED_CHILD_TYPES,
|
|
2649
|
+
MAP_STAGE_ALLOWED_CHILD_TYPES,
|
|
2185
2650
|
PAGE_ALLOWED_CHILD_TYPES,
|
|
2186
2651
|
SESSION_STORAGE_KEY,
|
|
2652
|
+
SINGLE_CHOICE_SET_ALLOWED_CHILD_TYPES,
|
|
2187
2653
|
SLIDE_ALLOWED_CHILD_TYPES,
|
|
2188
2654
|
SLIDE_DECK_ALLOWED_CHILD_TYPES,
|
|
2189
2655
|
TELEMETRY_EVENT_CATALOG,
|
|
@@ -2228,6 +2694,7 @@ function defineLifecyclePlugin(plugin) {
|
|
|
2228
2694
|
hasCourseStartedXapiSent,
|
|
2229
2695
|
isChildTypeAllowed,
|
|
2230
2696
|
isLifecycleTelemetryEvent,
|
|
2697
|
+
isMultiSelectMcq,
|
|
2231
2698
|
loadCompoundState,
|
|
2232
2699
|
markCourseStarted,
|
|
2233
2700
|
markCourseStartedEmittedToTracking,
|
|
@@ -2235,6 +2702,7 @@ function defineLifecyclePlugin(plugin) {
|
|
|
2235
2702
|
markCourseStartedXapiSent,
|
|
2236
2703
|
migrateCourseStartedMark,
|
|
2237
2704
|
nowIso,
|
|
2705
|
+
orderChoicesByIndices,
|
|
2238
2706
|
parseBlockId,
|
|
2239
2707
|
parseCheckId,
|
|
2240
2708
|
parseCompoundResumeState,
|
|
@@ -2243,8 +2711,12 @@ function defineLifecyclePlugin(plugin) {
|
|
|
2243
2711
|
resetSharedVolatileSessionIdForTests,
|
|
2244
2712
|
resetStoragePortForTests,
|
|
2245
2713
|
resetTelemetryBuilderWarningsForTests,
|
|
2714
|
+
resolveMcqCorrectAnswers,
|
|
2715
|
+
resolveMcqShuffleSeed,
|
|
2246
2716
|
resolveSessionId,
|
|
2247
2717
|
saveCompoundState,
|
|
2718
|
+
scoreMcqSelection,
|
|
2719
|
+
shuffleChoiceIndices,
|
|
2248
2720
|
slugifyId,
|
|
2249
2721
|
telemetryCatalogV2Version,
|
|
2250
2722
|
telemetryCatalogV3Version,
|