@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.js
CHANGED
|
@@ -36,7 +36,7 @@ import {
|
|
|
36
36
|
tryEmitCourseStarted,
|
|
37
37
|
validateId,
|
|
38
38
|
warnDev
|
|
39
|
-
} from "./chunk-
|
|
39
|
+
} from "./chunk-NGCHHJSM.js";
|
|
40
40
|
|
|
41
41
|
// src/assertNever.ts
|
|
42
42
|
function assertNever(value, message = "Unexpected value") {
|
|
@@ -117,7 +117,7 @@ function buildLessonkitUrn(parts) {
|
|
|
117
117
|
urn += `:block:${blockId}`;
|
|
118
118
|
}
|
|
119
119
|
if (parts.nodeId !== void 0) {
|
|
120
|
-
const nodeId = assertValidId(parts.nodeId, "
|
|
120
|
+
const nodeId = assertValidId(parts.nodeId, "nodeId");
|
|
121
121
|
if (parts.blockId === void 0) {
|
|
122
122
|
throw new Error("buildLessonkitUrn: nodeId requires blockId");
|
|
123
123
|
}
|
|
@@ -187,9 +187,11 @@ function parseCompoundResumeState(raw, opts) {
|
|
|
187
187
|
opts?.onDroppedChildKeys?.(droppedChildKeys);
|
|
188
188
|
}
|
|
189
189
|
const activeChapterIndex = typeof obj.activeChapterIndex === "number" && Number.isFinite(obj.activeChapterIndex) ? obj.activeChapterIndex : void 0;
|
|
190
|
+
const rawPageIndex = Math.max(0, Math.floor(obj.activePageIndex));
|
|
191
|
+
const activePageIndex = typeof opts?.pageCount === "number" && opts.pageCount > 0 ? clampCompoundPageIndex(rawPageIndex, opts.pageCount) : rawPageIndex;
|
|
190
192
|
return {
|
|
191
193
|
schemaVersion: COMPOUND_RESUME_SCHEMA_VERSION,
|
|
192
|
-
activePageIndex
|
|
194
|
+
activePageIndex,
|
|
193
195
|
...activeChapterIndex !== void 0 ? { activeChapterIndex: Math.max(0, Math.floor(activeChapterIndex)) } : {},
|
|
194
196
|
childStates
|
|
195
197
|
};
|
|
@@ -273,6 +275,16 @@ var PAGE_ALLOWED_CHILD_TYPES = [
|
|
|
273
275
|
"ImageSlider",
|
|
274
276
|
"Embed",
|
|
275
277
|
"Chart",
|
|
278
|
+
"Table",
|
|
279
|
+
"ImageJuxtaposition",
|
|
280
|
+
"Timeline",
|
|
281
|
+
"ImageSequence",
|
|
282
|
+
"Collage",
|
|
283
|
+
"AudioRecorder",
|
|
284
|
+
"CombinationLock",
|
|
285
|
+
"QrContent",
|
|
286
|
+
"Crossword",
|
|
287
|
+
"AdventCalendar",
|
|
276
288
|
"ProgressTracker"
|
|
277
289
|
];
|
|
278
290
|
var BRANCH_NODE_ALLOWED_CHILD_TYPES = [
|
|
@@ -307,9 +319,64 @@ var BRANCH_NODE_ALLOWED_CHILD_TYPES = [
|
|
|
307
319
|
"ImageSlider",
|
|
308
320
|
"Embed",
|
|
309
321
|
"Chart",
|
|
322
|
+
"Table",
|
|
323
|
+
"ImageJuxtaposition",
|
|
324
|
+
"Timeline",
|
|
325
|
+
"ImageSequence",
|
|
326
|
+
"Collage",
|
|
327
|
+
"AudioRecorder",
|
|
328
|
+
"CombinationLock",
|
|
329
|
+
"QrContent",
|
|
330
|
+
"Crossword",
|
|
331
|
+
"AdventCalendar",
|
|
310
332
|
"BranchChoice"
|
|
311
333
|
];
|
|
312
334
|
var BRANCHING_SCENARIO_ALLOWED_CHILD_TYPES = ["BranchNode"];
|
|
335
|
+
var GAME_MAP_ALLOWED_CHILD_TYPES = ["MapStage"];
|
|
336
|
+
var MAP_STAGE_ALLOWED_CHILD_TYPES = [
|
|
337
|
+
"Text",
|
|
338
|
+
"Heading",
|
|
339
|
+
"Image",
|
|
340
|
+
"Video",
|
|
341
|
+
"Scenario",
|
|
342
|
+
"Reflection",
|
|
343
|
+
"Quiz",
|
|
344
|
+
"KnowledgeCheck",
|
|
345
|
+
"TrueFalse",
|
|
346
|
+
"FillInTheBlanks",
|
|
347
|
+
"DragAndDrop",
|
|
348
|
+
"DragTheWords",
|
|
349
|
+
"MarkTheWords",
|
|
350
|
+
"Summary",
|
|
351
|
+
"ImagePairing",
|
|
352
|
+
"ImageSequencing",
|
|
353
|
+
"MemoryGame",
|
|
354
|
+
"InformationWall",
|
|
355
|
+
"ParallaxSlideshow",
|
|
356
|
+
"Questionnaire",
|
|
357
|
+
"Essay",
|
|
358
|
+
"ArithmeticQuiz",
|
|
359
|
+
"Accordion",
|
|
360
|
+
"DialogCards",
|
|
361
|
+
"Flashcards",
|
|
362
|
+
"ImageHotspots",
|
|
363
|
+
"FindHotspot",
|
|
364
|
+
"FindMultipleHotspots",
|
|
365
|
+
"ImageSlider",
|
|
366
|
+
"Embed",
|
|
367
|
+
"Chart",
|
|
368
|
+
"Table",
|
|
369
|
+
"ImageJuxtaposition",
|
|
370
|
+
"Timeline",
|
|
371
|
+
"ImageSequence",
|
|
372
|
+
"Collage",
|
|
373
|
+
"AudioRecorder",
|
|
374
|
+
"CombinationLock",
|
|
375
|
+
"QrContent",
|
|
376
|
+
"Crossword",
|
|
377
|
+
"AdventCalendar",
|
|
378
|
+
"MapExit"
|
|
379
|
+
];
|
|
313
380
|
var INTERACTIVE_BOOK_ALLOWED_CHILD_TYPES = ["Page"];
|
|
314
381
|
var SLIDE_ALLOWED_CHILD_TYPES = [
|
|
315
382
|
"Text",
|
|
@@ -342,7 +409,17 @@ var SLIDE_ALLOWED_CHILD_TYPES = [
|
|
|
342
409
|
"FindMultipleHotspots",
|
|
343
410
|
"ImageSlider",
|
|
344
411
|
"Embed",
|
|
345
|
-
"Chart"
|
|
412
|
+
"Chart",
|
|
413
|
+
"Table",
|
|
414
|
+
"ImageJuxtaposition",
|
|
415
|
+
"Timeline",
|
|
416
|
+
"ImageSequence",
|
|
417
|
+
"Collage",
|
|
418
|
+
"AudioRecorder",
|
|
419
|
+
"CombinationLock",
|
|
420
|
+
"QrContent",
|
|
421
|
+
"Crossword",
|
|
422
|
+
"AdventCalendar"
|
|
346
423
|
];
|
|
347
424
|
var SLIDE_DECK_ALLOWED_CHILD_TYPES = ["Slide"];
|
|
348
425
|
var TIMED_CUE_ALLOWED_CHILD_TYPES = [
|
|
@@ -386,7 +463,9 @@ var ALLOWLISTS = {
|
|
|
386
463
|
InteractiveVideo: INTERACTIVE_VIDEO_ALLOWED_CHILD_TYPES,
|
|
387
464
|
AssessmentSequence: ASSESSMENT_SEQUENCE_ALLOWED_CHILD_TYPES,
|
|
388
465
|
BranchingScenario: BRANCHING_SCENARIO_ALLOWED_CHILD_TYPES,
|
|
389
|
-
BranchNode: BRANCH_NODE_ALLOWED_CHILD_TYPES
|
|
466
|
+
BranchNode: BRANCH_NODE_ALLOWED_CHILD_TYPES,
|
|
467
|
+
GameMap: GAME_MAP_ALLOWED_CHILD_TYPES,
|
|
468
|
+
MapStage: MAP_STAGE_ALLOWED_CHILD_TYPES
|
|
390
469
|
};
|
|
391
470
|
var COMPOUND_MAX_NESTING_DEPTH = {
|
|
392
471
|
Page: 1,
|
|
@@ -397,7 +476,9 @@ var COMPOUND_MAX_NESTING_DEPTH = {
|
|
|
397
476
|
InteractiveVideo: 2,
|
|
398
477
|
AssessmentSequence: 1,
|
|
399
478
|
BranchingScenario: 2,
|
|
400
|
-
BranchNode: 1
|
|
479
|
+
BranchNode: 1,
|
|
480
|
+
GameMap: 2,
|
|
481
|
+
MapStage: 1
|
|
401
482
|
};
|
|
402
483
|
function getAllowedChildTypes(parent) {
|
|
403
484
|
return ALLOWLISTS[parent];
|
|
@@ -702,6 +783,78 @@ var TELEMETRY_EVENT_CATALOG_V3 = [
|
|
|
702
783
|
dataFields: ["blockId", "fromNodeId", "toNodeId", "label", "scoreWeight"],
|
|
703
784
|
xapiVerb: "http://adlnet.gov/expapi/verbs/answered",
|
|
704
785
|
urnPattern: "urn:lessonkit:course:{courseId}:lesson:{lessonId}:block:{blockId}:node:{toNodeId}"
|
|
786
|
+
},
|
|
787
|
+
{
|
|
788
|
+
name: "image_juxtaposition_changed",
|
|
789
|
+
description: "Learner adjusted the before/after divider",
|
|
790
|
+
requiredFields: ["courseId", "sessionId", "timestamp"],
|
|
791
|
+
dataFields: ["blockId", "position"],
|
|
792
|
+
xapiVerb: "http://adlnet.gov/expapi/verbs/interacted",
|
|
793
|
+
urnPattern: "urn:lessonkit:course:{courseId}:lesson:{lessonId}:block:{blockId}"
|
|
794
|
+
},
|
|
795
|
+
{
|
|
796
|
+
name: "timeline_event_viewed",
|
|
797
|
+
description: "Learner focused a timeline event",
|
|
798
|
+
requiredFields: ["courseId", "sessionId", "timestamp"],
|
|
799
|
+
dataFields: ["blockId", "eventId"],
|
|
800
|
+
xapiVerb: "http://adlnet.gov/expapi/verbs/experienced",
|
|
801
|
+
urnPattern: "urn:lessonkit:course:{courseId}:lesson:{lessonId}:block:{blockId}"
|
|
802
|
+
},
|
|
803
|
+
{
|
|
804
|
+
name: "image_sequence_changed",
|
|
805
|
+
description: "Learner changed the image sequence frame",
|
|
806
|
+
requiredFields: ["courseId", "sessionId", "timestamp"],
|
|
807
|
+
dataFields: ["blockId", "frameIndex"],
|
|
808
|
+
xapiVerb: "http://adlnet.gov/expapi/verbs/interacted",
|
|
809
|
+
urnPattern: "urn:lessonkit:course:{courseId}:lesson:{lessonId}:block:{blockId}"
|
|
810
|
+
},
|
|
811
|
+
{
|
|
812
|
+
name: "audio_recording_started",
|
|
813
|
+
description: "Learner started an audio recording",
|
|
814
|
+
requiredFields: ["courseId", "sessionId", "timestamp"],
|
|
815
|
+
dataFields: ["blockId"],
|
|
816
|
+
xapiVerb: "http://adlnet.gov/expapi/verbs/interacted",
|
|
817
|
+
urnPattern: "urn:lessonkit:course:{courseId}:lesson:{lessonId}:block:{blockId}"
|
|
818
|
+
},
|
|
819
|
+
{
|
|
820
|
+
name: "audio_recording_completed",
|
|
821
|
+
description: "Learner completed an audio recording",
|
|
822
|
+
requiredFields: ["courseId", "sessionId", "timestamp"],
|
|
823
|
+
dataFields: ["blockId"],
|
|
824
|
+
xapiVerb: "http://adlnet.gov/expapi/verbs/completed",
|
|
825
|
+
urnPattern: "urn:lessonkit:course:{courseId}:lesson:{lessonId}:block:{blockId}"
|
|
826
|
+
},
|
|
827
|
+
{
|
|
828
|
+
name: "qr_content_revealed",
|
|
829
|
+
description: "Learner revealed QR hidden content",
|
|
830
|
+
requiredFields: ["courseId", "sessionId", "timestamp"],
|
|
831
|
+
dataFields: ["blockId"],
|
|
832
|
+
xapiVerb: "http://adlnet.gov/expapi/verbs/experienced",
|
|
833
|
+
urnPattern: "urn:lessonkit:course:{courseId}:lesson:{lessonId}:block:{blockId}"
|
|
834
|
+
},
|
|
835
|
+
{
|
|
836
|
+
name: "advent_door_opened",
|
|
837
|
+
description: "Learner opened an advent calendar door",
|
|
838
|
+
requiredFields: ["courseId", "sessionId", "timestamp"],
|
|
839
|
+
dataFields: ["blockId", "doorId", "day"],
|
|
840
|
+
xapiVerb: "http://adlnet.gov/expapi/verbs/interacted",
|
|
841
|
+
urnPattern: "urn:lessonkit:course:{courseId}:lesson:{lessonId}:block:{blockId}"
|
|
842
|
+
},
|
|
843
|
+
{
|
|
844
|
+
name: "map_stage_viewed",
|
|
845
|
+
description: "Learner viewed a stage in a GameMap",
|
|
846
|
+
requiredFields: ["courseId", "lessonId", "sessionId", "timestamp"],
|
|
847
|
+
dataFields: ["blockId", "stageId", "stageIndex", "stageLabel"],
|
|
848
|
+
xapiVerb: "http://adlnet.gov/expapi/verbs/experienced",
|
|
849
|
+
urnPattern: "urn:lessonkit:course:{courseId}:lesson:{lessonId}:block:{blockId}:stage:{stageId}"
|
|
850
|
+
},
|
|
851
|
+
{
|
|
852
|
+
name: "map_exit_selected",
|
|
853
|
+
description: "Learner selected a map exit in a GameMap",
|
|
854
|
+
requiredFields: ["courseId", "lessonId", "sessionId", "timestamp"],
|
|
855
|
+
dataFields: ["blockId", "fromStageId", "toStageId", "label", "scoreWeight"],
|
|
856
|
+
xapiVerb: "http://adlnet.gov/expapi/verbs/answered",
|
|
857
|
+
urnPattern: "urn:lessonkit:course:{courseId}:lesson:{lessonId}:block:{blockId}:stage:{toStageId}"
|
|
705
858
|
}
|
|
706
859
|
];
|
|
707
860
|
function buildTelemetryCatalogV3() {
|
|
@@ -798,6 +951,10 @@ function createTrackingClient(opts) {
|
|
|
798
951
|
const runFlush = () => {
|
|
799
952
|
if (!buffer.length) return Promise.resolve(true);
|
|
800
953
|
const events = buffer.splice(0, buffer.length);
|
|
954
|
+
for (const event of events) {
|
|
955
|
+
const key = eventDedupKey(event);
|
|
956
|
+
if (key) pendingDeliverIds.add(key);
|
|
957
|
+
}
|
|
801
958
|
let succeeded = false;
|
|
802
959
|
return Promise.resolve().then(async () => {
|
|
803
960
|
if (batchSink) {
|
|
@@ -807,7 +964,7 @@ function createTrackingClient(opts) {
|
|
|
807
964
|
try {
|
|
808
965
|
await sink?.(events[i]);
|
|
809
966
|
} catch {
|
|
810
|
-
buffer.unshift(...events
|
|
967
|
+
buffer.unshift(...events);
|
|
811
968
|
return;
|
|
812
969
|
}
|
|
813
970
|
}
|
|
@@ -1142,6 +1299,10 @@ function resolvePluginHost(plugins) {
|
|
|
1142
1299
|
if (Array.isArray(plugins) && plugins.length > 0) return createPluginRegistry(plugins);
|
|
1143
1300
|
return null;
|
|
1144
1301
|
}
|
|
1302
|
+
function pluginListFingerprint(plugins) {
|
|
1303
|
+
if (!plugins || !Array.isArray(plugins)) return null;
|
|
1304
|
+
return plugins.map((p) => `${p.id}\0${p.version ?? ""}`).join("\n");
|
|
1305
|
+
}
|
|
1145
1306
|
function warnRuntimeV1Deprecated() {
|
|
1146
1307
|
const g = globalThis;
|
|
1147
1308
|
if (typeof g.process !== "undefined" && g.process.env?.NODE_ENV === "production") return;
|
|
@@ -1154,12 +1315,19 @@ function createLessonkitRuntime(config, ports = {}) {
|
|
|
1154
1315
|
const storage = ports.storage ?? createSessionStoragePort();
|
|
1155
1316
|
const clock = ports.clock ?? createDefaultClock();
|
|
1156
1317
|
const configSnapshot = { ...config };
|
|
1318
|
+
const hasExplicitSessionId = Boolean(configSnapshot.session?.sessionId?.trim());
|
|
1319
|
+
let autoSessionId;
|
|
1157
1320
|
let sessionId = resolveSessionId(storage, configSnapshot.session?.sessionId);
|
|
1321
|
+
if (!hasExplicitSessionId) {
|
|
1322
|
+
autoSessionId = sessionId;
|
|
1323
|
+
}
|
|
1158
1324
|
let attemptId = configSnapshot.session?.attemptId;
|
|
1159
1325
|
let user = configSnapshot.session?.user;
|
|
1160
1326
|
let courseId = configSnapshot.courseId;
|
|
1327
|
+
let configuredSessionId = configSnapshot.session?.sessionId;
|
|
1161
1328
|
let progress = createProgressController();
|
|
1162
1329
|
let pluginHost = resolvePluginHost(configSnapshot.plugins);
|
|
1330
|
+
let pluginFingerprint = pluginListFingerprint(configSnapshot.plugins);
|
|
1163
1331
|
let disposed = false;
|
|
1164
1332
|
const getPluginCtx = () => buildPluginContext({
|
|
1165
1333
|
courseId,
|
|
@@ -1171,12 +1339,6 @@ function createLessonkitRuntime(config, ports = {}) {
|
|
|
1171
1339
|
pluginHost?.setupAll(getPluginCtx());
|
|
1172
1340
|
}
|
|
1173
1341
|
const getSession = () => ({ sessionId, attemptId, user });
|
|
1174
|
-
const syncSessionFromConfig = (next) => {
|
|
1175
|
-
sessionId = resolveSessionId(storage, next.session?.sessionId);
|
|
1176
|
-
attemptId = next.session?.attemptId;
|
|
1177
|
-
user = next.session?.user;
|
|
1178
|
-
courseId = next.courseId;
|
|
1179
|
-
};
|
|
1180
1342
|
const applyPluginsToEvent = (event) => {
|
|
1181
1343
|
if (!pluginHost) return event;
|
|
1182
1344
|
return pluginHost.runTelemetry(event, getPluginCtx());
|
|
@@ -1198,7 +1360,6 @@ function createLessonkitRuntime(config, ports = {}) {
|
|
|
1198
1360
|
const event = buildAndApply(name, data, lessonId);
|
|
1199
1361
|
if (event) emitFn(event);
|
|
1200
1362
|
};
|
|
1201
|
-
syncSessionFromConfig(configSnapshot);
|
|
1202
1363
|
const track = (name, data, emit, lessonId) => {
|
|
1203
1364
|
if (disposed) return;
|
|
1204
1365
|
const event = buildAndApply(name, data, lessonId);
|
|
@@ -1240,21 +1401,52 @@ function createLessonkitRuntime(config, ports = {}) {
|
|
|
1240
1401
|
if (next.autoCompleteOnLessonSwitch !== void 0) {
|
|
1241
1402
|
configSnapshot.autoCompleteOnLessonSwitch = next.autoCompleteOnLessonSwitch;
|
|
1242
1403
|
}
|
|
1404
|
+
if (next.courseId !== void 0) {
|
|
1405
|
+
courseId = next.courseId;
|
|
1406
|
+
}
|
|
1243
1407
|
if (next.session !== void 0) {
|
|
1408
|
+
const previousSessionId = sessionId;
|
|
1244
1409
|
configSnapshot.session = { ...configSnapshot.session, ...next.session };
|
|
1410
|
+
const explicitSessionId = configSnapshot.session?.sessionId?.trim();
|
|
1411
|
+
if (explicitSessionId) {
|
|
1412
|
+
sessionId = resolveSessionId(storage, explicitSessionId);
|
|
1413
|
+
autoSessionId = void 0;
|
|
1414
|
+
} else {
|
|
1415
|
+
sessionId = autoSessionId ?? resolveSessionId(storage, void 0);
|
|
1416
|
+
if (!autoSessionId) autoSessionId = sessionId;
|
|
1417
|
+
}
|
|
1418
|
+
attemptId = configSnapshot.session?.attemptId;
|
|
1419
|
+
user = configSnapshot.session?.user;
|
|
1420
|
+
if (previousSessionId !== sessionId) {
|
|
1421
|
+
const prevExplicit = configuredSessionId?.trim();
|
|
1422
|
+
const nextExplicit = configSnapshot.session?.sessionId?.trim();
|
|
1423
|
+
const isExplicitLearnerSwap = Boolean(prevExplicit) && Boolean(nextExplicit) && prevExplicit !== nextExplicit;
|
|
1424
|
+
if (!isExplicitLearnerSwap) {
|
|
1425
|
+
migrateCourseStartedMark(storage, previousSessionId, sessionId, courseId);
|
|
1426
|
+
}
|
|
1427
|
+
}
|
|
1428
|
+
configuredSessionId = configSnapshot.session?.sessionId;
|
|
1245
1429
|
}
|
|
1246
|
-
syncSessionFromConfig(configSnapshot);
|
|
1247
1430
|
const sessionKeyAfter = JSON.stringify({ sessionId, attemptId, user });
|
|
1248
1431
|
if (next.courseId !== void 0 && next.courseId !== previousCourseId) {
|
|
1249
1432
|
progress = createProgressController();
|
|
1250
|
-
}
|
|
1251
|
-
if (next.plugins !== void 0 && next.plugins !== configSnapshot.plugins) {
|
|
1252
|
-
pluginHost?.disposeAll();
|
|
1253
|
-
configSnapshot.plugins = next.plugins;
|
|
1254
|
-
pluginHost = resolvePluginHost(configSnapshot.plugins);
|
|
1255
1433
|
if (!configSnapshot.deferPluginSetup) {
|
|
1434
|
+
pluginHost?.disposeAll();
|
|
1256
1435
|
pluginHost?.setupAll(getPluginCtx());
|
|
1257
1436
|
}
|
|
1437
|
+
}
|
|
1438
|
+
if (next.plugins !== void 0) {
|
|
1439
|
+
const nextFingerprint = pluginListFingerprint(next.plugins);
|
|
1440
|
+
const pluginsChanged = next.plugins !== configSnapshot.plugins || nextFingerprint !== null && nextFingerprint !== pluginFingerprint;
|
|
1441
|
+
if (pluginsChanged) {
|
|
1442
|
+
pluginHost?.disposeAll();
|
|
1443
|
+
configSnapshot.plugins = next.plugins;
|
|
1444
|
+
pluginFingerprint = nextFingerprint;
|
|
1445
|
+
pluginHost = resolvePluginHost(configSnapshot.plugins);
|
|
1446
|
+
if (!configSnapshot.deferPluginSetup) {
|
|
1447
|
+
pluginHost?.setupAll(getPluginCtx());
|
|
1448
|
+
}
|
|
1449
|
+
}
|
|
1258
1450
|
} else if (next.session !== void 0 && sessionKeyBefore !== sessionKeyAfter && pluginHost && !configSnapshot.deferPluginSetup) {
|
|
1259
1451
|
pluginHost.disposeAll();
|
|
1260
1452
|
pluginHost.setupAll(getPluginCtx());
|
|
@@ -1309,15 +1501,17 @@ function createLessonkitRuntime(config, ports = {}) {
|
|
|
1309
1501
|
configSnapshot.courseId = nextCourseId;
|
|
1310
1502
|
courseId = nextCourseId;
|
|
1311
1503
|
progress = createProgressController();
|
|
1312
|
-
pluginHost?.disposeAll();
|
|
1313
1504
|
if (!configSnapshot.deferPluginSetup) {
|
|
1505
|
+
pluginHost?.disposeAll();
|
|
1314
1506
|
pluginHost?.setupAll(getPluginCtx());
|
|
1315
1507
|
}
|
|
1316
1508
|
},
|
|
1317
1509
|
dispose() {
|
|
1318
1510
|
if (disposed) return;
|
|
1319
1511
|
disposed = true;
|
|
1320
|
-
|
|
1512
|
+
if (!configSnapshot.deferPluginSetup) {
|
|
1513
|
+
pluginHost?.disposeAll();
|
|
1514
|
+
}
|
|
1321
1515
|
}
|
|
1322
1516
|
};
|
|
1323
1517
|
}
|
|
@@ -1340,10 +1534,12 @@ export {
|
|
|
1340
1534
|
BRANCH_NODE_ALLOWED_CHILD_TYPES,
|
|
1341
1535
|
COMPOUND_MAX_NESTING_DEPTH,
|
|
1342
1536
|
COMPOUND_RESUME_SCHEMA_VERSION,
|
|
1537
|
+
GAME_MAP_ALLOWED_CHILD_TYPES,
|
|
1343
1538
|
ID_MAX_LENGTH,
|
|
1344
1539
|
ID_PATTERN,
|
|
1345
1540
|
INTERACTIVE_BOOK_ALLOWED_CHILD_TYPES,
|
|
1346
1541
|
INTERACTIVE_VIDEO_ALLOWED_CHILD_TYPES,
|
|
1542
|
+
MAP_STAGE_ALLOWED_CHILD_TYPES,
|
|
1347
1543
|
PAGE_ALLOWED_CHILD_TYPES,
|
|
1348
1544
|
SESSION_STORAGE_KEY,
|
|
1349
1545
|
SLIDE_ALLOWED_CHILD_TYPES,
|
|
@@ -21,7 +21,7 @@ declare const ID_PATTERN: RegExp;
|
|
|
21
21
|
declare const ID_MAX_LENGTH = 64;
|
|
22
22
|
|
|
23
23
|
/** H5P-aligned interaction kinds for assessment telemetry and xAPI. */
|
|
24
|
-
type AssessmentInteractionType = "mcq" | "trueFalse" | "fillInBlanks" | "markTheWords" | "dragTheWords" | "dragAndDrop" | "assessmentSequence" | "findHotspot" | "findMultipleHotspots" | "summary" | "imagePairing" | "imageSequencing" | "essay" | "arithmeticQuiz" | "memoryGame";
|
|
24
|
+
type AssessmentInteractionType = "mcq" | "trueFalse" | "fillInBlanks" | "markTheWords" | "dragTheWords" | "dragAndDrop" | "assessmentSequence" | "findHotspot" | "findMultipleHotspots" | "summary" | "imagePairing" | "imageSequencing" | "essay" | "arithmeticQuiz" | "memoryGame" | "combinationLock" | "crossword" | "wordSearch";
|
|
25
25
|
/** Serializable resume blob for a single assessment block. */
|
|
26
26
|
type AssessmentResumeState = Record<string, unknown>;
|
|
27
27
|
/** Behaviour flags aligned with H5P question types. */
|
|
@@ -65,7 +65,7 @@ type McqAssessmentProps = AssessmentBaseProps & {
|
|
|
65
65
|
answer: string;
|
|
66
66
|
};
|
|
67
67
|
|
|
68
|
-
type TelemetryEventName = "course_started" | "course_completed" | "lesson_started" | "lesson_completed" | "lesson_time_on_task" | "quiz_answered" | "quiz_completed" | "assessment_answered" | "assessment_completed" | "interaction" | "book_page_viewed" | "slide_viewed" | "compound_page_viewed" | "hotspot_opened" | "accordion_section_toggled" | "flashcard_flipped" | "image_slider_changed" | "video_cue_reached" | "video_segment_completed" | "memory_card_flipped" | "information_wall_search" | "parallax_slide_viewed" | "questionnaire_submitted" | "branch_node_viewed" | "branch_selected";
|
|
68
|
+
type TelemetryEventName = "course_started" | "course_completed" | "lesson_started" | "lesson_completed" | "lesson_time_on_task" | "quiz_answered" | "quiz_completed" | "assessment_answered" | "assessment_completed" | "interaction" | "book_page_viewed" | "slide_viewed" | "compound_page_viewed" | "hotspot_opened" | "accordion_section_toggled" | "flashcard_flipped" | "image_slider_changed" | "video_cue_reached" | "video_segment_completed" | "memory_card_flipped" | "information_wall_search" | "parallax_slide_viewed" | "questionnaire_submitted" | "branch_node_viewed" | "branch_selected" | "image_juxtaposition_changed" | "timeline_event_viewed" | "image_sequence_changed" | "audio_recording_started" | "audio_recording_completed" | "qr_content_revealed" | "advent_door_opened" | "map_stage_viewed" | "map_exit_selected";
|
|
69
69
|
type TelemetryUser = {
|
|
70
70
|
id?: string;
|
|
71
71
|
email?: string;
|
|
@@ -196,6 +196,42 @@ type BranchSelectedData = {
|
|
|
196
196
|
label: string;
|
|
197
197
|
scoreWeight?: number;
|
|
198
198
|
};
|
|
199
|
+
type ImageJuxtapositionChangedData = {
|
|
200
|
+
blockId: BlockId;
|
|
201
|
+
position: number;
|
|
202
|
+
};
|
|
203
|
+
type TimelineEventViewedData = {
|
|
204
|
+
blockId: BlockId;
|
|
205
|
+
eventId: string;
|
|
206
|
+
};
|
|
207
|
+
type ImageSequenceChangedData = {
|
|
208
|
+
blockId: BlockId;
|
|
209
|
+
frameIndex: number;
|
|
210
|
+
};
|
|
211
|
+
type AudioRecordingData = {
|
|
212
|
+
blockId: BlockId;
|
|
213
|
+
};
|
|
214
|
+
type QrContentRevealedData = {
|
|
215
|
+
blockId: BlockId;
|
|
216
|
+
};
|
|
217
|
+
type AdventDoorOpenedData = {
|
|
218
|
+
blockId: BlockId;
|
|
219
|
+
doorId: string;
|
|
220
|
+
day: number;
|
|
221
|
+
};
|
|
222
|
+
type MapStageViewedData = {
|
|
223
|
+
blockId: BlockId;
|
|
224
|
+
stageId: string;
|
|
225
|
+
stageIndex: number;
|
|
226
|
+
stageLabel?: string;
|
|
227
|
+
};
|
|
228
|
+
type MapExitSelectedData = {
|
|
229
|
+
blockId: BlockId;
|
|
230
|
+
fromStageId: string;
|
|
231
|
+
toStageId: string;
|
|
232
|
+
label: string;
|
|
233
|
+
scoreWeight?: number;
|
|
234
|
+
};
|
|
199
235
|
type TelemetryEvent = (TelemetryEventBase & {
|
|
200
236
|
name: "course_started";
|
|
201
237
|
lessonId?: LessonId;
|
|
@@ -296,6 +332,42 @@ type TelemetryEvent = (TelemetryEventBase & {
|
|
|
296
332
|
name: "branch_selected";
|
|
297
333
|
lessonId: LessonId;
|
|
298
334
|
data: BranchSelectedData;
|
|
335
|
+
}) | (TelemetryEventBase & {
|
|
336
|
+
name: "image_juxtaposition_changed";
|
|
337
|
+
lessonId?: LessonId;
|
|
338
|
+
data: ImageJuxtapositionChangedData;
|
|
339
|
+
}) | (TelemetryEventBase & {
|
|
340
|
+
name: "timeline_event_viewed";
|
|
341
|
+
lessonId?: LessonId;
|
|
342
|
+
data: TimelineEventViewedData;
|
|
343
|
+
}) | (TelemetryEventBase & {
|
|
344
|
+
name: "image_sequence_changed";
|
|
345
|
+
lessonId?: LessonId;
|
|
346
|
+
data: ImageSequenceChangedData;
|
|
347
|
+
}) | (TelemetryEventBase & {
|
|
348
|
+
name: "audio_recording_started";
|
|
349
|
+
lessonId?: LessonId;
|
|
350
|
+
data: AudioRecordingData;
|
|
351
|
+
}) | (TelemetryEventBase & {
|
|
352
|
+
name: "audio_recording_completed";
|
|
353
|
+
lessonId?: LessonId;
|
|
354
|
+
data: AudioRecordingData;
|
|
355
|
+
}) | (TelemetryEventBase & {
|
|
356
|
+
name: "qr_content_revealed";
|
|
357
|
+
lessonId?: LessonId;
|
|
358
|
+
data: QrContentRevealedData;
|
|
359
|
+
}) | (TelemetryEventBase & {
|
|
360
|
+
name: "advent_door_opened";
|
|
361
|
+
lessonId?: LessonId;
|
|
362
|
+
data: AdventDoorOpenedData;
|
|
363
|
+
}) | (TelemetryEventBase & {
|
|
364
|
+
name: "map_stage_viewed";
|
|
365
|
+
lessonId: LessonId;
|
|
366
|
+
data: MapStageViewedData;
|
|
367
|
+
}) | (TelemetryEventBase & {
|
|
368
|
+
name: "map_exit_selected";
|
|
369
|
+
lessonId: LessonId;
|
|
370
|
+
data: MapExitSelectedData;
|
|
299
371
|
});
|
|
300
372
|
/** Payload shape for a telemetry event name. */
|
|
301
373
|
type TelemetryDataFor<N extends TelemetryEventName> = Extract<TelemetryEvent, {
|
|
@@ -446,6 +518,42 @@ type BuildTelemetryEventInput = (BuildTelemetryEventContext & {
|
|
|
446
518
|
name: "branch_selected";
|
|
447
519
|
lessonId?: LessonId;
|
|
448
520
|
data: BranchSelectedData;
|
|
521
|
+
}) | (BuildTelemetryEventContext & {
|
|
522
|
+
name: "image_juxtaposition_changed";
|
|
523
|
+
lessonId?: LessonId;
|
|
524
|
+
data: ImageJuxtapositionChangedData;
|
|
525
|
+
}) | (BuildTelemetryEventContext & {
|
|
526
|
+
name: "timeline_event_viewed";
|
|
527
|
+
lessonId?: LessonId;
|
|
528
|
+
data: TimelineEventViewedData;
|
|
529
|
+
}) | (BuildTelemetryEventContext & {
|
|
530
|
+
name: "image_sequence_changed";
|
|
531
|
+
lessonId?: LessonId;
|
|
532
|
+
data: ImageSequenceChangedData;
|
|
533
|
+
}) | (BuildTelemetryEventContext & {
|
|
534
|
+
name: "audio_recording_started";
|
|
535
|
+
lessonId?: LessonId;
|
|
536
|
+
data: AudioRecordingData;
|
|
537
|
+
}) | (BuildTelemetryEventContext & {
|
|
538
|
+
name: "audio_recording_completed";
|
|
539
|
+
lessonId?: LessonId;
|
|
540
|
+
data: AudioRecordingData;
|
|
541
|
+
}) | (BuildTelemetryEventContext & {
|
|
542
|
+
name: "qr_content_revealed";
|
|
543
|
+
lessonId?: LessonId;
|
|
544
|
+
data: QrContentRevealedData;
|
|
545
|
+
}) | (BuildTelemetryEventContext & {
|
|
546
|
+
name: "advent_door_opened";
|
|
547
|
+
lessonId?: LessonId;
|
|
548
|
+
data: AdventDoorOpenedData;
|
|
549
|
+
}) | (BuildTelemetryEventContext & {
|
|
550
|
+
name: "map_stage_viewed";
|
|
551
|
+
lessonId?: LessonId;
|
|
552
|
+
data: MapStageViewedData;
|
|
553
|
+
}) | (BuildTelemetryEventContext & {
|
|
554
|
+
name: "map_exit_selected";
|
|
555
|
+
lessonId?: LessonId;
|
|
556
|
+
data: MapExitSelectedData;
|
|
449
557
|
});
|
|
450
558
|
|
|
451
559
|
/** Reset dev-warning state (tests only). */
|
|
@@ -453,6 +561,18 @@ declare function resetTelemetryBuilderWarningsForTests(): void;
|
|
|
453
561
|
/**
|
|
454
562
|
* Build a typed telemetry event from a catalog event name and context.
|
|
455
563
|
* Validates lesson-scoped events require `lessonId`.
|
|
564
|
+
*
|
|
565
|
+
* @example
|
|
566
|
+
* ```ts
|
|
567
|
+
* import { buildTelemetryEvent } from "@lessonkit/core";
|
|
568
|
+
*
|
|
569
|
+
* const event = buildTelemetryEvent({
|
|
570
|
+
* name: "lesson_completed",
|
|
571
|
+
* courseId: "sec-101",
|
|
572
|
+
* lessonId: "phishing-101",
|
|
573
|
+
* sessionId: "tab-abc",
|
|
574
|
+
* });
|
|
575
|
+
* ```
|
|
456
576
|
*/
|
|
457
577
|
declare function buildTelemetryEvent(opts: BuildTelemetryEventInput): TelemetryEvent;
|
|
458
578
|
/**
|
|
@@ -592,12 +712,38 @@ declare function tryEmitCourseStarted(ctx: CourseLifecycleContext, deps: CourseL
|
|
|
592
712
|
}>;
|
|
593
713
|
declare function buildCourseStartedTelemetryEvent(ctx: CourseLifecycleContext): TelemetryEvent;
|
|
594
714
|
type LessonCompletionEmitter = (lessonId: LessonId, durationMs?: number) => void;
|
|
715
|
+
/**
|
|
716
|
+
* Mark a lesson complete in progress state and emit `lesson_completed` when newly completed.
|
|
717
|
+
*
|
|
718
|
+
* @example
|
|
719
|
+
* ```ts
|
|
720
|
+
* completeLessonWithTelemetry({
|
|
721
|
+
* progress,
|
|
722
|
+
* lessonId: "lesson-1",
|
|
723
|
+
* nowMs: Date.now(),
|
|
724
|
+
* emitLessonCompleted: (id, durationMs) => track("lesson_completed", { lessonId: id, durationMs }),
|
|
725
|
+
* });
|
|
726
|
+
* ```
|
|
727
|
+
*/
|
|
595
728
|
declare function completeLessonWithTelemetry(opts: {
|
|
596
729
|
progress: ProgressController;
|
|
597
730
|
lessonId: LessonId;
|
|
598
731
|
nowMs: number;
|
|
599
732
|
emitLessonCompleted: LessonCompletionEmitter;
|
|
600
733
|
}): boolean;
|
|
734
|
+
/**
|
|
735
|
+
* Complete the active lesson (if any), then mark the course complete and emit `course_completed`.
|
|
736
|
+
*
|
|
737
|
+
* @example
|
|
738
|
+
* ```ts
|
|
739
|
+
* completeCourseWithTelemetry({
|
|
740
|
+
* progress,
|
|
741
|
+
* nowMs: Date.now(),
|
|
742
|
+
* emitLessonCompleted: (id) => track("lesson_completed", { lessonId: id }),
|
|
743
|
+
* emitCourseCompleted: () => track("course_completed", {}),
|
|
744
|
+
* });
|
|
745
|
+
* ```
|
|
746
|
+
*/
|
|
601
747
|
declare function completeCourseWithTelemetry(opts: {
|
|
602
748
|
progress: ProgressController;
|
|
603
749
|
nowMs: number;
|