@lessonkit/core 1.5.0 → 1.6.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/dist/{chunk-KFXFQ6B2.js → chunk-NGCHHJSM.js} +134 -12
- package/dist/index.cjs +353 -33
- package/dist/index.d.cts +94 -11
- package/dist/index.d.ts +94 -11
- package/dist/index.js +218 -22
- package/dist/{testing-BFr8oEfw.d.cts → testing-CzgxF1Ru.d.cts} +148 -2
- package/dist/{testing-BFr8oEfw.d.ts → testing-CzgxF1Ru.d.ts} +148 -2
- package/dist/testing.d.cts +1 -1
- package/dist/testing.d.ts +1 -1
- package/dist/testing.js +1 -1
- package/package.json +1 -1
- package/telemetry-catalog.v3.json +140 -0
package/dist/index.cjs
CHANGED
|
@@ -27,10 +27,12 @@ __export(index_exports, {
|
|
|
27
27
|
BRANCH_NODE_ALLOWED_CHILD_TYPES: () => BRANCH_NODE_ALLOWED_CHILD_TYPES,
|
|
28
28
|
COMPOUND_MAX_NESTING_DEPTH: () => COMPOUND_MAX_NESTING_DEPTH,
|
|
29
29
|
COMPOUND_RESUME_SCHEMA_VERSION: () => COMPOUND_RESUME_SCHEMA_VERSION,
|
|
30
|
+
GAME_MAP_ALLOWED_CHILD_TYPES: () => GAME_MAP_ALLOWED_CHILD_TYPES,
|
|
30
31
|
ID_MAX_LENGTH: () => ID_MAX_LENGTH,
|
|
31
32
|
ID_PATTERN: () => ID_PATTERN,
|
|
32
33
|
INTERACTIVE_BOOK_ALLOWED_CHILD_TYPES: () => INTERACTIVE_BOOK_ALLOWED_CHILD_TYPES,
|
|
33
34
|
INTERACTIVE_VIDEO_ALLOWED_CHILD_TYPES: () => INTERACTIVE_VIDEO_ALLOWED_CHILD_TYPES,
|
|
35
|
+
MAP_STAGE_ALLOWED_CHILD_TYPES: () => MAP_STAGE_ALLOWED_CHILD_TYPES,
|
|
34
36
|
PAGE_ALLOWED_CHILD_TYPES: () => PAGE_ALLOWED_CHILD_TYPES,
|
|
35
37
|
SESSION_STORAGE_KEY: () => SESSION_STORAGE_KEY,
|
|
36
38
|
SLIDE_ALLOWED_CHILD_TYPES: () => SLIDE_ALLOWED_CHILD_TYPES,
|
|
@@ -240,7 +242,7 @@ function buildLessonkitUrn(parts) {
|
|
|
240
242
|
urn += `:block:${blockId}`;
|
|
241
243
|
}
|
|
242
244
|
if (parts.nodeId !== void 0) {
|
|
243
|
-
const nodeId = assertValidId(parts.nodeId, "
|
|
245
|
+
const nodeId = assertValidId(parts.nodeId, "nodeId");
|
|
244
246
|
if (parts.blockId === void 0) {
|
|
245
247
|
throw new Error("buildLessonkitUrn: nodeId requires blockId");
|
|
246
248
|
}
|
|
@@ -310,9 +312,11 @@ function parseCompoundResumeState(raw, opts) {
|
|
|
310
312
|
opts?.onDroppedChildKeys?.(droppedChildKeys);
|
|
311
313
|
}
|
|
312
314
|
const activeChapterIndex = typeof obj.activeChapterIndex === "number" && Number.isFinite(obj.activeChapterIndex) ? obj.activeChapterIndex : void 0;
|
|
315
|
+
const rawPageIndex = Math.max(0, Math.floor(obj.activePageIndex));
|
|
316
|
+
const activePageIndex = typeof opts?.pageCount === "number" && opts.pageCount > 0 ? clampCompoundPageIndex(rawPageIndex, opts.pageCount) : rawPageIndex;
|
|
313
317
|
return {
|
|
314
318
|
schemaVersion: COMPOUND_RESUME_SCHEMA_VERSION,
|
|
315
|
-
activePageIndex
|
|
319
|
+
activePageIndex,
|
|
316
320
|
...activeChapterIndex !== void 0 ? { activeChapterIndex: Math.max(0, Math.floor(activeChapterIndex)) } : {},
|
|
317
321
|
childStates
|
|
318
322
|
};
|
|
@@ -406,6 +410,16 @@ var PAGE_ALLOWED_CHILD_TYPES = [
|
|
|
406
410
|
"ImageSlider",
|
|
407
411
|
"Embed",
|
|
408
412
|
"Chart",
|
|
413
|
+
"Table",
|
|
414
|
+
"ImageJuxtaposition",
|
|
415
|
+
"Timeline",
|
|
416
|
+
"ImageSequence",
|
|
417
|
+
"Collage",
|
|
418
|
+
"AudioRecorder",
|
|
419
|
+
"CombinationLock",
|
|
420
|
+
"QrContent",
|
|
421
|
+
"Crossword",
|
|
422
|
+
"AdventCalendar",
|
|
409
423
|
"ProgressTracker"
|
|
410
424
|
];
|
|
411
425
|
var BRANCH_NODE_ALLOWED_CHILD_TYPES = [
|
|
@@ -440,9 +454,64 @@ var BRANCH_NODE_ALLOWED_CHILD_TYPES = [
|
|
|
440
454
|
"ImageSlider",
|
|
441
455
|
"Embed",
|
|
442
456
|
"Chart",
|
|
457
|
+
"Table",
|
|
458
|
+
"ImageJuxtaposition",
|
|
459
|
+
"Timeline",
|
|
460
|
+
"ImageSequence",
|
|
461
|
+
"Collage",
|
|
462
|
+
"AudioRecorder",
|
|
463
|
+
"CombinationLock",
|
|
464
|
+
"QrContent",
|
|
465
|
+
"Crossword",
|
|
466
|
+
"AdventCalendar",
|
|
443
467
|
"BranchChoice"
|
|
444
468
|
];
|
|
445
469
|
var BRANCHING_SCENARIO_ALLOWED_CHILD_TYPES = ["BranchNode"];
|
|
470
|
+
var GAME_MAP_ALLOWED_CHILD_TYPES = ["MapStage"];
|
|
471
|
+
var MAP_STAGE_ALLOWED_CHILD_TYPES = [
|
|
472
|
+
"Text",
|
|
473
|
+
"Heading",
|
|
474
|
+
"Image",
|
|
475
|
+
"Video",
|
|
476
|
+
"Scenario",
|
|
477
|
+
"Reflection",
|
|
478
|
+
"Quiz",
|
|
479
|
+
"KnowledgeCheck",
|
|
480
|
+
"TrueFalse",
|
|
481
|
+
"FillInTheBlanks",
|
|
482
|
+
"DragAndDrop",
|
|
483
|
+
"DragTheWords",
|
|
484
|
+
"MarkTheWords",
|
|
485
|
+
"Summary",
|
|
486
|
+
"ImagePairing",
|
|
487
|
+
"ImageSequencing",
|
|
488
|
+
"MemoryGame",
|
|
489
|
+
"InformationWall",
|
|
490
|
+
"ParallaxSlideshow",
|
|
491
|
+
"Questionnaire",
|
|
492
|
+
"Essay",
|
|
493
|
+
"ArithmeticQuiz",
|
|
494
|
+
"Accordion",
|
|
495
|
+
"DialogCards",
|
|
496
|
+
"Flashcards",
|
|
497
|
+
"ImageHotspots",
|
|
498
|
+
"FindHotspot",
|
|
499
|
+
"FindMultipleHotspots",
|
|
500
|
+
"ImageSlider",
|
|
501
|
+
"Embed",
|
|
502
|
+
"Chart",
|
|
503
|
+
"Table",
|
|
504
|
+
"ImageJuxtaposition",
|
|
505
|
+
"Timeline",
|
|
506
|
+
"ImageSequence",
|
|
507
|
+
"Collage",
|
|
508
|
+
"AudioRecorder",
|
|
509
|
+
"CombinationLock",
|
|
510
|
+
"QrContent",
|
|
511
|
+
"Crossword",
|
|
512
|
+
"AdventCalendar",
|
|
513
|
+
"MapExit"
|
|
514
|
+
];
|
|
446
515
|
var INTERACTIVE_BOOK_ALLOWED_CHILD_TYPES = ["Page"];
|
|
447
516
|
var SLIDE_ALLOWED_CHILD_TYPES = [
|
|
448
517
|
"Text",
|
|
@@ -475,7 +544,17 @@ var SLIDE_ALLOWED_CHILD_TYPES = [
|
|
|
475
544
|
"FindMultipleHotspots",
|
|
476
545
|
"ImageSlider",
|
|
477
546
|
"Embed",
|
|
478
|
-
"Chart"
|
|
547
|
+
"Chart",
|
|
548
|
+
"Table",
|
|
549
|
+
"ImageJuxtaposition",
|
|
550
|
+
"Timeline",
|
|
551
|
+
"ImageSequence",
|
|
552
|
+
"Collage",
|
|
553
|
+
"AudioRecorder",
|
|
554
|
+
"CombinationLock",
|
|
555
|
+
"QrContent",
|
|
556
|
+
"Crossword",
|
|
557
|
+
"AdventCalendar"
|
|
479
558
|
];
|
|
480
559
|
var SLIDE_DECK_ALLOWED_CHILD_TYPES = ["Slide"];
|
|
481
560
|
var TIMED_CUE_ALLOWED_CHILD_TYPES = [
|
|
@@ -519,7 +598,9 @@ var ALLOWLISTS = {
|
|
|
519
598
|
InteractiveVideo: INTERACTIVE_VIDEO_ALLOWED_CHILD_TYPES,
|
|
520
599
|
AssessmentSequence: ASSESSMENT_SEQUENCE_ALLOWED_CHILD_TYPES,
|
|
521
600
|
BranchingScenario: BRANCHING_SCENARIO_ALLOWED_CHILD_TYPES,
|
|
522
|
-
BranchNode: BRANCH_NODE_ALLOWED_CHILD_TYPES
|
|
601
|
+
BranchNode: BRANCH_NODE_ALLOWED_CHILD_TYPES,
|
|
602
|
+
GameMap: GAME_MAP_ALLOWED_CHILD_TYPES,
|
|
603
|
+
MapStage: MAP_STAGE_ALLOWED_CHILD_TYPES
|
|
523
604
|
};
|
|
524
605
|
var COMPOUND_MAX_NESTING_DEPTH = {
|
|
525
606
|
Page: 1,
|
|
@@ -530,7 +611,9 @@ var COMPOUND_MAX_NESTING_DEPTH = {
|
|
|
530
611
|
InteractiveVideo: 2,
|
|
531
612
|
AssessmentSequence: 1,
|
|
532
613
|
BranchingScenario: 2,
|
|
533
|
-
BranchNode: 1
|
|
614
|
+
BranchNode: 1,
|
|
615
|
+
GameMap: 2,
|
|
616
|
+
MapStage: 1
|
|
534
617
|
};
|
|
535
618
|
function getAllowedChildTypes(parent) {
|
|
536
619
|
return ALLOWLISTS[parent];
|
|
@@ -835,6 +918,78 @@ var TELEMETRY_EVENT_CATALOG_V3 = [
|
|
|
835
918
|
dataFields: ["blockId", "fromNodeId", "toNodeId", "label", "scoreWeight"],
|
|
836
919
|
xapiVerb: "http://adlnet.gov/expapi/verbs/answered",
|
|
837
920
|
urnPattern: "urn:lessonkit:course:{courseId}:lesson:{lessonId}:block:{blockId}:node:{toNodeId}"
|
|
921
|
+
},
|
|
922
|
+
{
|
|
923
|
+
name: "image_juxtaposition_changed",
|
|
924
|
+
description: "Learner adjusted the before/after divider",
|
|
925
|
+
requiredFields: ["courseId", "sessionId", "timestamp"],
|
|
926
|
+
dataFields: ["blockId", "position"],
|
|
927
|
+
xapiVerb: "http://adlnet.gov/expapi/verbs/interacted",
|
|
928
|
+
urnPattern: "urn:lessonkit:course:{courseId}:lesson:{lessonId}:block:{blockId}"
|
|
929
|
+
},
|
|
930
|
+
{
|
|
931
|
+
name: "timeline_event_viewed",
|
|
932
|
+
description: "Learner focused a timeline event",
|
|
933
|
+
requiredFields: ["courseId", "sessionId", "timestamp"],
|
|
934
|
+
dataFields: ["blockId", "eventId"],
|
|
935
|
+
xapiVerb: "http://adlnet.gov/expapi/verbs/experienced",
|
|
936
|
+
urnPattern: "urn:lessonkit:course:{courseId}:lesson:{lessonId}:block:{blockId}"
|
|
937
|
+
},
|
|
938
|
+
{
|
|
939
|
+
name: "image_sequence_changed",
|
|
940
|
+
description: "Learner changed the image sequence frame",
|
|
941
|
+
requiredFields: ["courseId", "sessionId", "timestamp"],
|
|
942
|
+
dataFields: ["blockId", "frameIndex"],
|
|
943
|
+
xapiVerb: "http://adlnet.gov/expapi/verbs/interacted",
|
|
944
|
+
urnPattern: "urn:lessonkit:course:{courseId}:lesson:{lessonId}:block:{blockId}"
|
|
945
|
+
},
|
|
946
|
+
{
|
|
947
|
+
name: "audio_recording_started",
|
|
948
|
+
description: "Learner started an audio recording",
|
|
949
|
+
requiredFields: ["courseId", "sessionId", "timestamp"],
|
|
950
|
+
dataFields: ["blockId"],
|
|
951
|
+
xapiVerb: "http://adlnet.gov/expapi/verbs/interacted",
|
|
952
|
+
urnPattern: "urn:lessonkit:course:{courseId}:lesson:{lessonId}:block:{blockId}"
|
|
953
|
+
},
|
|
954
|
+
{
|
|
955
|
+
name: "audio_recording_completed",
|
|
956
|
+
description: "Learner completed an audio recording",
|
|
957
|
+
requiredFields: ["courseId", "sessionId", "timestamp"],
|
|
958
|
+
dataFields: ["blockId"],
|
|
959
|
+
xapiVerb: "http://adlnet.gov/expapi/verbs/completed",
|
|
960
|
+
urnPattern: "urn:lessonkit:course:{courseId}:lesson:{lessonId}:block:{blockId}"
|
|
961
|
+
},
|
|
962
|
+
{
|
|
963
|
+
name: "qr_content_revealed",
|
|
964
|
+
description: "Learner revealed QR hidden content",
|
|
965
|
+
requiredFields: ["courseId", "sessionId", "timestamp"],
|
|
966
|
+
dataFields: ["blockId"],
|
|
967
|
+
xapiVerb: "http://adlnet.gov/expapi/verbs/experienced",
|
|
968
|
+
urnPattern: "urn:lessonkit:course:{courseId}:lesson:{lessonId}:block:{blockId}"
|
|
969
|
+
},
|
|
970
|
+
{
|
|
971
|
+
name: "advent_door_opened",
|
|
972
|
+
description: "Learner opened an advent calendar door",
|
|
973
|
+
requiredFields: ["courseId", "sessionId", "timestamp"],
|
|
974
|
+
dataFields: ["blockId", "doorId", "day"],
|
|
975
|
+
xapiVerb: "http://adlnet.gov/expapi/verbs/interacted",
|
|
976
|
+
urnPattern: "urn:lessonkit:course:{courseId}:lesson:{lessonId}:block:{blockId}"
|
|
977
|
+
},
|
|
978
|
+
{
|
|
979
|
+
name: "map_stage_viewed",
|
|
980
|
+
description: "Learner viewed a stage in a GameMap",
|
|
981
|
+
requiredFields: ["courseId", "lessonId", "sessionId", "timestamp"],
|
|
982
|
+
dataFields: ["blockId", "stageId", "stageIndex", "stageLabel"],
|
|
983
|
+
xapiVerb: "http://adlnet.gov/expapi/verbs/experienced",
|
|
984
|
+
urnPattern: "urn:lessonkit:course:{courseId}:lesson:{lessonId}:block:{blockId}:stage:{stageId}"
|
|
985
|
+
},
|
|
986
|
+
{
|
|
987
|
+
name: "map_exit_selected",
|
|
988
|
+
description: "Learner selected a map exit in a GameMap",
|
|
989
|
+
requiredFields: ["courseId", "lessonId", "sessionId", "timestamp"],
|
|
990
|
+
dataFields: ["blockId", "fromStageId", "toStageId", "label", "scoreWeight"],
|
|
991
|
+
xapiVerb: "http://adlnet.gov/expapi/verbs/answered",
|
|
992
|
+
urnPattern: "urn:lessonkit:course:{courseId}:lesson:{lessonId}:block:{blockId}:stage:{toStageId}"
|
|
838
993
|
}
|
|
839
994
|
];
|
|
840
995
|
function buildTelemetryCatalogV3() {
|
|
@@ -931,6 +1086,10 @@ function createTrackingClient(opts) {
|
|
|
931
1086
|
const runFlush = () => {
|
|
932
1087
|
if (!buffer.length) return Promise.resolve(true);
|
|
933
1088
|
const events = buffer.splice(0, buffer.length);
|
|
1089
|
+
for (const event of events) {
|
|
1090
|
+
const key = eventDedupKey(event);
|
|
1091
|
+
if (key) pendingDeliverIds.add(key);
|
|
1092
|
+
}
|
|
934
1093
|
let succeeded = false;
|
|
935
1094
|
return Promise.resolve().then(async () => {
|
|
936
1095
|
if (batchSink) {
|
|
@@ -940,7 +1099,7 @@ function createTrackingClient(opts) {
|
|
|
940
1099
|
try {
|
|
941
1100
|
await sink?.(events[i]);
|
|
942
1101
|
} catch {
|
|
943
|
-
buffer.unshift(...events
|
|
1102
|
+
buffer.unshift(...events);
|
|
944
1103
|
return;
|
|
945
1104
|
}
|
|
946
1105
|
}
|
|
@@ -1064,7 +1223,8 @@ function randomSessionIdFallback() {
|
|
|
1064
1223
|
if (g.crypto?.getRandomValues) {
|
|
1065
1224
|
const bytes = new Uint8Array(16);
|
|
1066
1225
|
g.crypto.getRandomValues(bytes);
|
|
1067
|
-
|
|
1226
|
+
const hex = Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
1227
|
+
return `s-${hex}`;
|
|
1068
1228
|
}
|
|
1069
1229
|
throw new Error(
|
|
1070
1230
|
"[lessonkit] createSessionId requires crypto.randomUUID or crypto.getRandomValues"
|
|
@@ -1072,7 +1232,9 @@ function randomSessionIdFallback() {
|
|
|
1072
1232
|
}
|
|
1073
1233
|
function createSessionId() {
|
|
1074
1234
|
const g = globalThis;
|
|
1075
|
-
if (g.crypto?.randomUUID)
|
|
1235
|
+
if (g.crypto?.randomUUID) {
|
|
1236
|
+
return `s-${g.crypto.randomUUID().replace(/-/g, "")}`;
|
|
1237
|
+
}
|
|
1076
1238
|
return randomSessionIdFallback();
|
|
1077
1239
|
}
|
|
1078
1240
|
|
|
@@ -1380,6 +1542,90 @@ var TELEMETRY_EVENT_REGISTRY = {
|
|
|
1380
1542
|
data: opts.data
|
|
1381
1543
|
};
|
|
1382
1544
|
}
|
|
1545
|
+
},
|
|
1546
|
+
image_juxtaposition_changed: {
|
|
1547
|
+
build: (opts, base) => ({
|
|
1548
|
+
name: "image_juxtaposition_changed",
|
|
1549
|
+
...base,
|
|
1550
|
+
lessonId: opts.lessonId,
|
|
1551
|
+
data: opts.data
|
|
1552
|
+
})
|
|
1553
|
+
},
|
|
1554
|
+
timeline_event_viewed: {
|
|
1555
|
+
build: (opts, base) => ({
|
|
1556
|
+
name: "timeline_event_viewed",
|
|
1557
|
+
...base,
|
|
1558
|
+
lessonId: opts.lessonId,
|
|
1559
|
+
data: opts.data
|
|
1560
|
+
})
|
|
1561
|
+
},
|
|
1562
|
+
image_sequence_changed: {
|
|
1563
|
+
build: (opts, base) => ({
|
|
1564
|
+
name: "image_sequence_changed",
|
|
1565
|
+
...base,
|
|
1566
|
+
lessonId: opts.lessonId,
|
|
1567
|
+
data: opts.data
|
|
1568
|
+
})
|
|
1569
|
+
},
|
|
1570
|
+
audio_recording_started: {
|
|
1571
|
+
build: (opts, base) => ({
|
|
1572
|
+
name: "audio_recording_started",
|
|
1573
|
+
...base,
|
|
1574
|
+
lessonId: opts.lessonId,
|
|
1575
|
+
data: opts.data
|
|
1576
|
+
})
|
|
1577
|
+
},
|
|
1578
|
+
audio_recording_completed: {
|
|
1579
|
+
build: (opts, base) => ({
|
|
1580
|
+
name: "audio_recording_completed",
|
|
1581
|
+
...base,
|
|
1582
|
+
lessonId: opts.lessonId,
|
|
1583
|
+
data: opts.data
|
|
1584
|
+
})
|
|
1585
|
+
},
|
|
1586
|
+
qr_content_revealed: {
|
|
1587
|
+
build: (opts, base) => ({
|
|
1588
|
+
name: "qr_content_revealed",
|
|
1589
|
+
...base,
|
|
1590
|
+
lessonId: opts.lessonId,
|
|
1591
|
+
data: opts.data
|
|
1592
|
+
})
|
|
1593
|
+
},
|
|
1594
|
+
advent_door_opened: {
|
|
1595
|
+
build: (opts, base) => ({
|
|
1596
|
+
name: "advent_door_opened",
|
|
1597
|
+
...base,
|
|
1598
|
+
lessonId: opts.lessonId,
|
|
1599
|
+
data: opts.data
|
|
1600
|
+
})
|
|
1601
|
+
},
|
|
1602
|
+
map_stage_viewed: {
|
|
1603
|
+
requiresLessonId: true,
|
|
1604
|
+
build: (opts, base) => {
|
|
1605
|
+
if (opts.name !== "map_stage_viewed") throw new Error("unexpected event");
|
|
1606
|
+
const lessonId = opts.lessonId;
|
|
1607
|
+
if (!lessonId) throw new Error("map_stage_viewed requires active lessonId");
|
|
1608
|
+
return {
|
|
1609
|
+
name: "map_stage_viewed",
|
|
1610
|
+
...base,
|
|
1611
|
+
lessonId,
|
|
1612
|
+
data: opts.data
|
|
1613
|
+
};
|
|
1614
|
+
}
|
|
1615
|
+
},
|
|
1616
|
+
map_exit_selected: {
|
|
1617
|
+
requiresLessonId: true,
|
|
1618
|
+
build: (opts, base) => {
|
|
1619
|
+
if (opts.name !== "map_exit_selected") throw new Error("unexpected event");
|
|
1620
|
+
const lessonId = opts.lessonId;
|
|
1621
|
+
if (!lessonId) throw new Error("map_exit_selected requires active lessonId");
|
|
1622
|
+
return {
|
|
1623
|
+
name: "map_exit_selected",
|
|
1624
|
+
...base,
|
|
1625
|
+
lessonId,
|
|
1626
|
+
data: opts.data
|
|
1627
|
+
};
|
|
1628
|
+
}
|
|
1383
1629
|
}
|
|
1384
1630
|
};
|
|
1385
1631
|
function buildTelemetryEventFromRegistry(opts) {
|
|
@@ -1540,13 +1786,15 @@ function createMemoryBackedSessionStorage(session) {
|
|
|
1540
1786
|
);
|
|
1541
1787
|
}
|
|
1542
1788
|
};
|
|
1789
|
+
const bypassCacheForKey = (key) => key === "lessonkit:sessionId" || key.startsWith("lessonkit:course_started");
|
|
1543
1790
|
return {
|
|
1544
1791
|
getItem: (key) => {
|
|
1545
1792
|
if (tombstones.has(key)) return null;
|
|
1546
|
-
if (memory.has(key)) return memory.get(key);
|
|
1793
|
+
if (!bypassCacheForKey(key) && memory.has(key)) return memory.get(key);
|
|
1547
1794
|
try {
|
|
1548
1795
|
const value = session.getItem(key);
|
|
1549
1796
|
if (value !== null) memory.set(key, value);
|
|
1797
|
+
else if (bypassCacheForKey(key)) memory.delete(key);
|
|
1550
1798
|
return value;
|
|
1551
1799
|
} catch {
|
|
1552
1800
|
return memory.get(key) ?? null;
|
|
@@ -1554,12 +1802,15 @@ function createMemoryBackedSessionStorage(session) {
|
|
|
1554
1802
|
},
|
|
1555
1803
|
setItem: (key, value) => {
|
|
1556
1804
|
tombstones.delete(key);
|
|
1557
|
-
memory.set(key, value);
|
|
1558
1805
|
try {
|
|
1559
1806
|
session.setItem(key, value);
|
|
1807
|
+
memory.set(key, value);
|
|
1560
1808
|
return true;
|
|
1561
1809
|
} catch {
|
|
1562
1810
|
warnPersistFailure();
|
|
1811
|
+
if (!bypassCacheForKey(key)) {
|
|
1812
|
+
memory.set(key, value);
|
|
1813
|
+
}
|
|
1563
1814
|
return false;
|
|
1564
1815
|
}
|
|
1565
1816
|
},
|
|
@@ -1701,7 +1952,17 @@ function resolveSessionId(storage, provided) {
|
|
|
1701
1952
|
}
|
|
1702
1953
|
}
|
|
1703
1954
|
const existing = storage.getItem(SESSION_STORAGE_KEY);
|
|
1704
|
-
if (existing)
|
|
1955
|
+
if (existing) {
|
|
1956
|
+
const trimmedExisting = existing.trim();
|
|
1957
|
+
const validatedExisting = validateId(trimmedExisting);
|
|
1958
|
+
if (validatedExisting.ok) return validatedExisting.id;
|
|
1959
|
+
storage.removeItem?.(SESSION_STORAGE_KEY);
|
|
1960
|
+
if (isDevEnvironment2()) {
|
|
1961
|
+
console.warn(
|
|
1962
|
+
`[lessonkit] Invalid stored sessionId "${existing}"; generating a new id.`
|
|
1963
|
+
);
|
|
1964
|
+
}
|
|
1965
|
+
}
|
|
1705
1966
|
const volatile = volatileSessionIds.get(storage);
|
|
1706
1967
|
if (volatile) return volatile;
|
|
1707
1968
|
const id = createSessionId();
|
|
@@ -1803,7 +2064,17 @@ function tryEmitCourseStarted(ctx, deps, alreadyEmittedToSink) {
|
|
|
1803
2064
|
const flightKey = `${ctx.sessionId}:${ctx.courseId}`;
|
|
1804
2065
|
const marked = hasCourseStarted(ctx.storage, ctx.sessionId, ctx.courseId);
|
|
1805
2066
|
if (alreadyEmittedToSink) {
|
|
1806
|
-
|
|
2067
|
+
const markPersisted = marked ? true : markCourseStarted(ctx.storage, ctx.sessionId, ctx.courseId);
|
|
2068
|
+
return Promise.resolve({
|
|
2069
|
+
emitted: true,
|
|
2070
|
+
marked: markPersisted
|
|
2071
|
+
});
|
|
2072
|
+
}
|
|
2073
|
+
if (marked && hasCourseStartedEmittedToTracking(ctx.storage, ctx.sessionId, ctx.courseId)) {
|
|
2074
|
+
return Promise.resolve({
|
|
2075
|
+
emitted: true,
|
|
2076
|
+
marked: true
|
|
2077
|
+
});
|
|
1807
2078
|
}
|
|
1808
2079
|
const existing = courseStartedEmitFlights.get(flightKey);
|
|
1809
2080
|
if (existing) {
|
|
@@ -1812,15 +2083,16 @@ function tryEmitCourseStarted(ctx, deps, alreadyEmittedToSink) {
|
|
|
1812
2083
|
const flight = Promise.resolve().then(() => {
|
|
1813
2084
|
try {
|
|
1814
2085
|
const emitted = deps.emitCourseStartedEvent(ctx);
|
|
1815
|
-
|
|
1816
|
-
markCourseStarted(ctx.storage, ctx.sessionId, ctx.courseId);
|
|
1817
|
-
}
|
|
2086
|
+
const markPersisted = emitted && !marked ? markCourseStarted(ctx.storage, ctx.sessionId, ctx.courseId) : marked;
|
|
1818
2087
|
return {
|
|
1819
2088
|
emitted,
|
|
1820
|
-
marked:
|
|
2089
|
+
marked: markPersisted
|
|
1821
2090
|
};
|
|
1822
2091
|
} catch {
|
|
1823
|
-
return {
|
|
2092
|
+
return {
|
|
2093
|
+
emitted: false,
|
|
2094
|
+
marked: hasCourseStarted(ctx.storage, ctx.sessionId, ctx.courseId)
|
|
2095
|
+
};
|
|
1824
2096
|
} finally {
|
|
1825
2097
|
if (courseStartedEmitFlights.get(flightKey) === flight) {
|
|
1826
2098
|
courseStartedEmitFlights.delete(flightKey);
|
|
@@ -1856,7 +2128,16 @@ function completeCourseWithTelemetry(opts) {
|
|
|
1856
2128
|
});
|
|
1857
2129
|
}
|
|
1858
2130
|
const result = opts.progress.completeCourse();
|
|
1859
|
-
if (!result.didComplete)
|
|
2131
|
+
if (!result.didComplete) {
|
|
2132
|
+
const after = opts.progress.getState();
|
|
2133
|
+
if (after.activeLessonId) {
|
|
2134
|
+
const lessonResult = opts.progress.completeLesson(after.activeLessonId, opts.nowMs);
|
|
2135
|
+
if (lessonResult.didComplete) {
|
|
2136
|
+
opts.emitLessonCompleted(after.activeLessonId, lessonResult.durationMs);
|
|
2137
|
+
}
|
|
2138
|
+
}
|
|
2139
|
+
return false;
|
|
2140
|
+
}
|
|
1860
2141
|
opts.emitCourseCompleted();
|
|
1861
2142
|
return true;
|
|
1862
2143
|
}
|
|
@@ -1979,6 +2260,10 @@ function resolvePluginHost(plugins) {
|
|
|
1979
2260
|
if (Array.isArray(plugins) && plugins.length > 0) return createPluginRegistry(plugins);
|
|
1980
2261
|
return null;
|
|
1981
2262
|
}
|
|
2263
|
+
function pluginListFingerprint(plugins) {
|
|
2264
|
+
if (!plugins || !Array.isArray(plugins)) return null;
|
|
2265
|
+
return plugins.map((p) => `${p.id}\0${p.version ?? ""}`).join("\n");
|
|
2266
|
+
}
|
|
1982
2267
|
function warnRuntimeV1Deprecated() {
|
|
1983
2268
|
const g = globalThis;
|
|
1984
2269
|
if (typeof g.process !== "undefined" && g.process.env?.NODE_ENV === "production") return;
|
|
@@ -1991,12 +2276,19 @@ function createLessonkitRuntime(config, ports = {}) {
|
|
|
1991
2276
|
const storage = ports.storage ?? createSessionStoragePort();
|
|
1992
2277
|
const clock = ports.clock ?? createDefaultClock();
|
|
1993
2278
|
const configSnapshot = { ...config };
|
|
2279
|
+
const hasExplicitSessionId = Boolean(configSnapshot.session?.sessionId?.trim());
|
|
2280
|
+
let autoSessionId;
|
|
1994
2281
|
let sessionId = resolveSessionId(storage, configSnapshot.session?.sessionId);
|
|
2282
|
+
if (!hasExplicitSessionId) {
|
|
2283
|
+
autoSessionId = sessionId;
|
|
2284
|
+
}
|
|
1995
2285
|
let attemptId = configSnapshot.session?.attemptId;
|
|
1996
2286
|
let user = configSnapshot.session?.user;
|
|
1997
2287
|
let courseId = configSnapshot.courseId;
|
|
2288
|
+
let configuredSessionId = configSnapshot.session?.sessionId;
|
|
1998
2289
|
let progress = createProgressController();
|
|
1999
2290
|
let pluginHost = resolvePluginHost(configSnapshot.plugins);
|
|
2291
|
+
let pluginFingerprint = pluginListFingerprint(configSnapshot.plugins);
|
|
2000
2292
|
let disposed = false;
|
|
2001
2293
|
const getPluginCtx = () => buildPluginContext({
|
|
2002
2294
|
courseId,
|
|
@@ -2008,12 +2300,6 @@ function createLessonkitRuntime(config, ports = {}) {
|
|
|
2008
2300
|
pluginHost?.setupAll(getPluginCtx());
|
|
2009
2301
|
}
|
|
2010
2302
|
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
2303
|
const applyPluginsToEvent = (event) => {
|
|
2018
2304
|
if (!pluginHost) return event;
|
|
2019
2305
|
return pluginHost.runTelemetry(event, getPluginCtx());
|
|
@@ -2035,7 +2321,6 @@ function createLessonkitRuntime(config, ports = {}) {
|
|
|
2035
2321
|
const event = buildAndApply(name, data, lessonId);
|
|
2036
2322
|
if (event) emitFn(event);
|
|
2037
2323
|
};
|
|
2038
|
-
syncSessionFromConfig(configSnapshot);
|
|
2039
2324
|
const track = (name, data, emit, lessonId) => {
|
|
2040
2325
|
if (disposed) return;
|
|
2041
2326
|
const event = buildAndApply(name, data, lessonId);
|
|
@@ -2077,21 +2362,52 @@ function createLessonkitRuntime(config, ports = {}) {
|
|
|
2077
2362
|
if (next.autoCompleteOnLessonSwitch !== void 0) {
|
|
2078
2363
|
configSnapshot.autoCompleteOnLessonSwitch = next.autoCompleteOnLessonSwitch;
|
|
2079
2364
|
}
|
|
2365
|
+
if (next.courseId !== void 0) {
|
|
2366
|
+
courseId = next.courseId;
|
|
2367
|
+
}
|
|
2080
2368
|
if (next.session !== void 0) {
|
|
2369
|
+
const previousSessionId = sessionId;
|
|
2081
2370
|
configSnapshot.session = { ...configSnapshot.session, ...next.session };
|
|
2371
|
+
const explicitSessionId = configSnapshot.session?.sessionId?.trim();
|
|
2372
|
+
if (explicitSessionId) {
|
|
2373
|
+
sessionId = resolveSessionId(storage, explicitSessionId);
|
|
2374
|
+
autoSessionId = void 0;
|
|
2375
|
+
} else {
|
|
2376
|
+
sessionId = autoSessionId ?? resolveSessionId(storage, void 0);
|
|
2377
|
+
if (!autoSessionId) autoSessionId = sessionId;
|
|
2378
|
+
}
|
|
2379
|
+
attemptId = configSnapshot.session?.attemptId;
|
|
2380
|
+
user = configSnapshot.session?.user;
|
|
2381
|
+
if (previousSessionId !== sessionId) {
|
|
2382
|
+
const prevExplicit = configuredSessionId?.trim();
|
|
2383
|
+
const nextExplicit = configSnapshot.session?.sessionId?.trim();
|
|
2384
|
+
const isExplicitLearnerSwap = Boolean(prevExplicit) && Boolean(nextExplicit) && prevExplicit !== nextExplicit;
|
|
2385
|
+
if (!isExplicitLearnerSwap) {
|
|
2386
|
+
migrateCourseStartedMark(storage, previousSessionId, sessionId, courseId);
|
|
2387
|
+
}
|
|
2388
|
+
}
|
|
2389
|
+
configuredSessionId = configSnapshot.session?.sessionId;
|
|
2082
2390
|
}
|
|
2083
|
-
syncSessionFromConfig(configSnapshot);
|
|
2084
2391
|
const sessionKeyAfter = JSON.stringify({ sessionId, attemptId, user });
|
|
2085
2392
|
if (next.courseId !== void 0 && next.courseId !== previousCourseId) {
|
|
2086
2393
|
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
2394
|
if (!configSnapshot.deferPluginSetup) {
|
|
2395
|
+
pluginHost?.disposeAll();
|
|
2093
2396
|
pluginHost?.setupAll(getPluginCtx());
|
|
2094
2397
|
}
|
|
2398
|
+
}
|
|
2399
|
+
if (next.plugins !== void 0) {
|
|
2400
|
+
const nextFingerprint = pluginListFingerprint(next.plugins);
|
|
2401
|
+
const pluginsChanged = next.plugins !== configSnapshot.plugins || nextFingerprint !== null && nextFingerprint !== pluginFingerprint;
|
|
2402
|
+
if (pluginsChanged) {
|
|
2403
|
+
pluginHost?.disposeAll();
|
|
2404
|
+
configSnapshot.plugins = next.plugins;
|
|
2405
|
+
pluginFingerprint = nextFingerprint;
|
|
2406
|
+
pluginHost = resolvePluginHost(configSnapshot.plugins);
|
|
2407
|
+
if (!configSnapshot.deferPluginSetup) {
|
|
2408
|
+
pluginHost?.setupAll(getPluginCtx());
|
|
2409
|
+
}
|
|
2410
|
+
}
|
|
2095
2411
|
} else if (next.session !== void 0 && sessionKeyBefore !== sessionKeyAfter && pluginHost && !configSnapshot.deferPluginSetup) {
|
|
2096
2412
|
pluginHost.disposeAll();
|
|
2097
2413
|
pluginHost.setupAll(getPluginCtx());
|
|
@@ -2146,15 +2462,17 @@ function createLessonkitRuntime(config, ports = {}) {
|
|
|
2146
2462
|
configSnapshot.courseId = nextCourseId;
|
|
2147
2463
|
courseId = nextCourseId;
|
|
2148
2464
|
progress = createProgressController();
|
|
2149
|
-
pluginHost?.disposeAll();
|
|
2150
2465
|
if (!configSnapshot.deferPluginSetup) {
|
|
2466
|
+
pluginHost?.disposeAll();
|
|
2151
2467
|
pluginHost?.setupAll(getPluginCtx());
|
|
2152
2468
|
}
|
|
2153
2469
|
},
|
|
2154
2470
|
dispose() {
|
|
2155
2471
|
if (disposed) return;
|
|
2156
2472
|
disposed = true;
|
|
2157
|
-
|
|
2473
|
+
if (!configSnapshot.deferPluginSetup) {
|
|
2474
|
+
pluginHost?.disposeAll();
|
|
2475
|
+
}
|
|
2158
2476
|
}
|
|
2159
2477
|
};
|
|
2160
2478
|
}
|
|
@@ -2178,10 +2496,12 @@ function defineLifecyclePlugin(plugin) {
|
|
|
2178
2496
|
BRANCH_NODE_ALLOWED_CHILD_TYPES,
|
|
2179
2497
|
COMPOUND_MAX_NESTING_DEPTH,
|
|
2180
2498
|
COMPOUND_RESUME_SCHEMA_VERSION,
|
|
2499
|
+
GAME_MAP_ALLOWED_CHILD_TYPES,
|
|
2181
2500
|
ID_MAX_LENGTH,
|
|
2182
2501
|
ID_PATTERN,
|
|
2183
2502
|
INTERACTIVE_BOOK_ALLOWED_CHILD_TYPES,
|
|
2184
2503
|
INTERACTIVE_VIDEO_ALLOWED_CHILD_TYPES,
|
|
2504
|
+
MAP_STAGE_ALLOWED_CHILD_TYPES,
|
|
2185
2505
|
PAGE_ALLOWED_CHILD_TYPES,
|
|
2186
2506
|
SESSION_STORAGE_KEY,
|
|
2187
2507
|
SLIDE_ALLOWED_CHILD_TYPES,
|