@lessonkit/core 1.3.1 → 1.4.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-PEWFPVQ6.js +628 -0
- package/dist/index.cjs +244 -25
- package/dist/index.d.cts +14 -483
- package/dist/index.d.ts +14 -483
- package/dist/index.js +196 -545
- package/dist/testing-BhVGckZ5.d.cts +569 -0
- package/dist/testing-BhVGckZ5.d.ts +569 -0
- package/dist/testing.cjs +60 -0
- package/dist/testing.d.cts +1 -0
- package/dist/testing.d.ts +1 -0
- package/dist/testing.js +12 -0
- package/package.json +9 -4
- package/telemetry-catalog.v3.json +170 -14
package/dist/index.js
CHANGED
|
@@ -1,3 +1,33 @@
|
|
|
1
|
+
import {
|
|
2
|
+
SESSION_STORAGE_KEY,
|
|
3
|
+
buildCourseStartedTelemetryEvent,
|
|
4
|
+
buildTelemetryEvent,
|
|
5
|
+
completeCourseWithTelemetry,
|
|
6
|
+
completeLessonWithTelemetry,
|
|
7
|
+
createDefaultClock,
|
|
8
|
+
createGlobalTimer,
|
|
9
|
+
createNoopStorage,
|
|
10
|
+
createSessionId,
|
|
11
|
+
createSessionStoragePort,
|
|
12
|
+
getTabSessionId,
|
|
13
|
+
hasCourseStarted,
|
|
14
|
+
hasCourseStartedEmittedToTracking,
|
|
15
|
+
hasCourseStartedPipelineDelivered,
|
|
16
|
+
isDevEnvironment,
|
|
17
|
+
markCourseStarted,
|
|
18
|
+
markCourseStartedEmittedToTracking,
|
|
19
|
+
markCourseStartedPipelineDelivered,
|
|
20
|
+
migrateCourseStartedMark,
|
|
21
|
+
nowIso,
|
|
22
|
+
resetSharedVolatileSessionIdForTests,
|
|
23
|
+
resetStoragePortForTests,
|
|
24
|
+
resetTelemetryBuilderWarningsForTests,
|
|
25
|
+
resolveSessionId,
|
|
26
|
+
tryBuildTelemetryEvent,
|
|
27
|
+
tryEmitCourseStarted,
|
|
28
|
+
warnDev
|
|
29
|
+
} from "./chunk-PEWFPVQ6.js";
|
|
30
|
+
|
|
1
31
|
// src/identityTypes.ts
|
|
2
32
|
var ID_PATTERN = /^[a-zA-Z][a-zA-Z0-9_-]{0,63}$/;
|
|
3
33
|
var ID_MAX_LENGTH = 64;
|
|
@@ -184,16 +214,6 @@ function parseCompoundResumeState(raw) {
|
|
|
184
214
|
};
|
|
185
215
|
}
|
|
186
216
|
|
|
187
|
-
// src/internal/env.ts
|
|
188
|
-
function isDevEnvironment() {
|
|
189
|
-
const g = globalThis;
|
|
190
|
-
return typeof g.process !== "undefined" && g.process.env?.NODE_ENV !== "production";
|
|
191
|
-
}
|
|
192
|
-
function warnDev(message, err) {
|
|
193
|
-
if (!isDevEnvironment()) return;
|
|
194
|
-
console.warn(message, err instanceof Error ? err.message : err);
|
|
195
|
-
}
|
|
196
|
-
|
|
197
217
|
// src/compoundState.ts
|
|
198
218
|
var COMPOUND_STATE_PREFIX = "lessonkit:compound:";
|
|
199
219
|
function compoundStateStorageKey(courseId, compoundId) {
|
|
@@ -224,10 +244,23 @@ function clearCompoundState(storage, courseId, compoundId) {
|
|
|
224
244
|
}
|
|
225
245
|
|
|
226
246
|
// src/compoundAllowlists.ts
|
|
247
|
+
var PAGE_AND_SLIDE_14_BLOCKS = [
|
|
248
|
+
"Video",
|
|
249
|
+
"Summary",
|
|
250
|
+
"ImagePairing",
|
|
251
|
+
"ImageSequencing",
|
|
252
|
+
"MemoryGame",
|
|
253
|
+
"InformationWall",
|
|
254
|
+
"ParallaxSlideshow",
|
|
255
|
+
"Questionnaire",
|
|
256
|
+
"Essay",
|
|
257
|
+
"ArithmeticQuiz"
|
|
258
|
+
];
|
|
227
259
|
var PAGE_ALLOWED_CHILD_TYPES = [
|
|
228
260
|
"Text",
|
|
229
261
|
"Heading",
|
|
230
262
|
"Image",
|
|
263
|
+
"Video",
|
|
231
264
|
"Scenario",
|
|
232
265
|
"Reflection",
|
|
233
266
|
"Quiz",
|
|
@@ -237,6 +270,15 @@ var PAGE_ALLOWED_CHILD_TYPES = [
|
|
|
237
270
|
"DragAndDrop",
|
|
238
271
|
"DragTheWords",
|
|
239
272
|
"MarkTheWords",
|
|
273
|
+
"Summary",
|
|
274
|
+
"ImagePairing",
|
|
275
|
+
"ImageSequencing",
|
|
276
|
+
"MemoryGame",
|
|
277
|
+
"InformationWall",
|
|
278
|
+
"ParallaxSlideshow",
|
|
279
|
+
"Questionnaire",
|
|
280
|
+
"Essay",
|
|
281
|
+
"ArithmeticQuiz",
|
|
240
282
|
"Accordion",
|
|
241
283
|
"DialogCards",
|
|
242
284
|
"Flashcards",
|
|
@@ -251,6 +293,7 @@ var SLIDE_ALLOWED_CHILD_TYPES = [
|
|
|
251
293
|
"Text",
|
|
252
294
|
"Heading",
|
|
253
295
|
"Image",
|
|
296
|
+
"Video",
|
|
254
297
|
"Scenario",
|
|
255
298
|
"Reflection",
|
|
256
299
|
"Quiz",
|
|
@@ -260,6 +303,15 @@ var SLIDE_ALLOWED_CHILD_TYPES = [
|
|
|
260
303
|
"DragAndDrop",
|
|
261
304
|
"DragTheWords",
|
|
262
305
|
"MarkTheWords",
|
|
306
|
+
"Summary",
|
|
307
|
+
"ImagePairing",
|
|
308
|
+
"ImageSequencing",
|
|
309
|
+
"MemoryGame",
|
|
310
|
+
"InformationWall",
|
|
311
|
+
"ParallaxSlideshow",
|
|
312
|
+
"Questionnaire",
|
|
313
|
+
"Essay",
|
|
314
|
+
"ArithmeticQuiz",
|
|
263
315
|
"Accordion",
|
|
264
316
|
"DialogCards",
|
|
265
317
|
"Flashcards",
|
|
@@ -269,6 +321,22 @@ var SLIDE_ALLOWED_CHILD_TYPES = [
|
|
|
269
321
|
"ImageSlider"
|
|
270
322
|
];
|
|
271
323
|
var SLIDE_DECK_ALLOWED_CHILD_TYPES = ["Slide"];
|
|
324
|
+
var TIMED_CUE_ALLOWED_CHILD_TYPES = [
|
|
325
|
+
"Text",
|
|
326
|
+
"Heading",
|
|
327
|
+
"Image",
|
|
328
|
+
"Quiz",
|
|
329
|
+
"TrueFalse",
|
|
330
|
+
"FillInTheBlanks",
|
|
331
|
+
"Summary",
|
|
332
|
+
"ImagePairing",
|
|
333
|
+
"ImageSequencing",
|
|
334
|
+
"MemoryGame",
|
|
335
|
+
"Questionnaire",
|
|
336
|
+
"Essay",
|
|
337
|
+
"ArithmeticQuiz"
|
|
338
|
+
];
|
|
339
|
+
var INTERACTIVE_VIDEO_ALLOWED_CHILD_TYPES = ["TimedCue"];
|
|
272
340
|
var ASSESSMENT_SEQUENCE_ALLOWED_CHILD_TYPES = [
|
|
273
341
|
"TrueFalse",
|
|
274
342
|
"FillInTheBlanks",
|
|
@@ -278,13 +346,20 @@ var ASSESSMENT_SEQUENCE_ALLOWED_CHILD_TYPES = [
|
|
|
278
346
|
"Quiz",
|
|
279
347
|
"KnowledgeCheck",
|
|
280
348
|
"FindHotspot",
|
|
281
|
-
"FindMultipleHotspots"
|
|
349
|
+
"FindMultipleHotspots",
|
|
350
|
+
"Summary",
|
|
351
|
+
"ImagePairing",
|
|
352
|
+
"ImageSequencing",
|
|
353
|
+
"ArithmeticQuiz",
|
|
354
|
+
"Essay"
|
|
282
355
|
];
|
|
283
356
|
var ALLOWLISTS = {
|
|
284
357
|
Page: PAGE_ALLOWED_CHILD_TYPES,
|
|
285
358
|
InteractiveBook: INTERACTIVE_BOOK_ALLOWED_CHILD_TYPES,
|
|
286
359
|
Slide: SLIDE_ALLOWED_CHILD_TYPES,
|
|
287
360
|
SlideDeck: SLIDE_DECK_ALLOWED_CHILD_TYPES,
|
|
361
|
+
TimedCue: TIMED_CUE_ALLOWED_CHILD_TYPES,
|
|
362
|
+
InteractiveVideo: INTERACTIVE_VIDEO_ALLOWED_CHILD_TYPES,
|
|
288
363
|
AssessmentSequence: ASSESSMENT_SEQUENCE_ALLOWED_CHILD_TYPES
|
|
289
364
|
};
|
|
290
365
|
var COMPOUND_MAX_NESTING_DEPTH = {
|
|
@@ -292,6 +367,8 @@ var COMPOUND_MAX_NESTING_DEPTH = {
|
|
|
292
367
|
InteractiveBook: 2,
|
|
293
368
|
Slide: 1,
|
|
294
369
|
SlideDeck: 2,
|
|
370
|
+
TimedCue: 1,
|
|
371
|
+
InteractiveVideo: 2,
|
|
295
372
|
AssessmentSequence: 1
|
|
296
373
|
};
|
|
297
374
|
function getAllowedChildTypes(parent) {
|
|
@@ -301,6 +378,7 @@ function isChildTypeAllowed(parent, childType) {
|
|
|
301
378
|
return ALLOWLISTS[parent].includes(childType);
|
|
302
379
|
}
|
|
303
380
|
var ACCORDION_FORBIDDEN_CHILD_TYPES = ["Accordion"];
|
|
381
|
+
var BLOCKS_14_PAGE_SLIDE = PAGE_AND_SLIDE_14_BLOCKS;
|
|
304
382
|
|
|
305
383
|
// src/telemetryCatalog.ts
|
|
306
384
|
var telemetryCatalogVersion = 1;
|
|
@@ -456,6 +534,54 @@ var TELEMETRY_EVENT_CATALOG_V3 = [
|
|
|
456
534
|
dataFields: ["blockId", "slideIndex"],
|
|
457
535
|
xapiVerb: "http://adlnet.gov/expapi/verbs/experienced",
|
|
458
536
|
urnPattern: "urn:lessonkit:course:{courseId}:lesson:{lessonId}:block:{blockId}"
|
|
537
|
+
},
|
|
538
|
+
{
|
|
539
|
+
name: "video_cue_reached",
|
|
540
|
+
description: "Learner reached a timed cue in an Interactive Video",
|
|
541
|
+
requiredFields: ["courseId", "lessonId", "sessionId", "timestamp"],
|
|
542
|
+
dataFields: ["blockId", "cueIndex", "atSeconds", "cueLabel"],
|
|
543
|
+
xapiVerb: "http://adlnet.gov/expapi/verbs/experienced",
|
|
544
|
+
urnPattern: "urn:lessonkit:course:{courseId}:lesson:{lessonId}:block:{blockId}"
|
|
545
|
+
},
|
|
546
|
+
{
|
|
547
|
+
name: "video_segment_completed",
|
|
548
|
+
description: "Learner completed a timed segment in an Interactive Video",
|
|
549
|
+
requiredFields: ["courseId", "lessonId", "sessionId", "timestamp"],
|
|
550
|
+
dataFields: ["blockId", "segmentIndex", "atSeconds", "segmentLabel"],
|
|
551
|
+
xapiVerb: "http://adlnet.gov/expapi/verbs/completed",
|
|
552
|
+
urnPattern: "urn:lessonkit:course:{courseId}:lesson:{lessonId}:block:{blockId}"
|
|
553
|
+
},
|
|
554
|
+
{
|
|
555
|
+
name: "memory_card_flipped",
|
|
556
|
+
description: "Learner flipped a memory game card",
|
|
557
|
+
requiredFields: ["courseId", "sessionId", "timestamp"],
|
|
558
|
+
dataFields: ["blockId", "cardIndex", "face"],
|
|
559
|
+
xapiVerb: "http://adlnet.gov/expapi/verbs/experienced",
|
|
560
|
+
urnPattern: "urn:lessonkit:course:{courseId}:lesson:{lessonId}:block:{blockId}"
|
|
561
|
+
},
|
|
562
|
+
{
|
|
563
|
+
name: "information_wall_search",
|
|
564
|
+
description: "Learner searched an information wall",
|
|
565
|
+
requiredFields: ["courseId", "sessionId", "timestamp"],
|
|
566
|
+
dataFields: ["blockId", "query", "resultCount"],
|
|
567
|
+
xapiVerb: "http://adlnet.gov/expapi/verbs/experienced",
|
|
568
|
+
urnPattern: "urn:lessonkit:course:{courseId}:lesson:{lessonId}:block:{blockId}"
|
|
569
|
+
},
|
|
570
|
+
{
|
|
571
|
+
name: "parallax_slide_viewed",
|
|
572
|
+
description: "Learner viewed a slide in a parallax slideshow",
|
|
573
|
+
requiredFields: ["courseId", "sessionId", "timestamp"],
|
|
574
|
+
dataFields: ["blockId", "slideIndex"],
|
|
575
|
+
xapiVerb: "http://adlnet.gov/expapi/verbs/experienced",
|
|
576
|
+
urnPattern: "urn:lessonkit:course:{courseId}:lesson:{lessonId}:block:{blockId}"
|
|
577
|
+
},
|
|
578
|
+
{
|
|
579
|
+
name: "questionnaire_submitted",
|
|
580
|
+
description: "Learner submitted an unscored questionnaire",
|
|
581
|
+
requiredFields: ["courseId", "lessonId", "sessionId", "timestamp"],
|
|
582
|
+
dataFields: ["blockId", "fieldCount"],
|
|
583
|
+
xapiVerb: "http://adlnet.gov/expapi/verbs/completed",
|
|
584
|
+
urnPattern: "urn:lessonkit:course:{courseId}:lesson:{lessonId}:block:{blockId}"
|
|
459
585
|
}
|
|
460
586
|
];
|
|
461
587
|
function buildTelemetryCatalogV3() {
|
|
@@ -463,6 +589,18 @@ function buildTelemetryCatalogV3() {
|
|
|
463
589
|
}
|
|
464
590
|
|
|
465
591
|
// src/internal/sinkInvoke.ts
|
|
592
|
+
async function invokeTrackingSinkWithResult(sink, event) {
|
|
593
|
+
try {
|
|
594
|
+
const result = sink(event);
|
|
595
|
+
if (result != null && typeof result.then === "function") {
|
|
596
|
+
await result;
|
|
597
|
+
}
|
|
598
|
+
return true;
|
|
599
|
+
} catch (err) {
|
|
600
|
+
warnDev("[lessonkit] tracking sink failed:", err);
|
|
601
|
+
return false;
|
|
602
|
+
}
|
|
603
|
+
}
|
|
466
604
|
function invokeTrackingSink(sink, event) {
|
|
467
605
|
let result;
|
|
468
606
|
try {
|
|
@@ -509,7 +647,17 @@ function createTrackingClient(opts) {
|
|
|
509
647
|
return {
|
|
510
648
|
track: (event) => {
|
|
511
649
|
if (disposed2) return;
|
|
512
|
-
if (sink)
|
|
650
|
+
if (sink) {
|
|
651
|
+
try {
|
|
652
|
+
invokeTrackingSink(sink, event);
|
|
653
|
+
} catch {
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
},
|
|
657
|
+
deliver: async (event) => {
|
|
658
|
+
if (disposed2) return false;
|
|
659
|
+
if (!sink) return true;
|
|
660
|
+
return invokeTrackingSinkWithResult(sink, event);
|
|
513
661
|
},
|
|
514
662
|
dispose: () => {
|
|
515
663
|
disposed2 = true;
|
|
@@ -586,21 +734,26 @@ function createTrackingClient(opts) {
|
|
|
586
734
|
};
|
|
587
735
|
intervalId = flushIntervalMs > 0 ? globalThis.setInterval(() => void flush(), flushIntervalMs) : void 0;
|
|
588
736
|
intervalId?.unref?.();
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
);
|
|
599
|
-
}
|
|
600
|
-
return;
|
|
737
|
+
const track = (event) => {
|
|
738
|
+
if (disposed || disposing) return;
|
|
739
|
+
if (buffer.length >= maxBufferSize) {
|
|
740
|
+
opts?.onBufferDrop?.();
|
|
741
|
+
if (!warnedBufferCap && isDevEnvironment()) {
|
|
742
|
+
warnedBufferCap = true;
|
|
743
|
+
console.warn(
|
|
744
|
+
`[lessonkit] telemetry batch buffer capped at ${maxBufferSize} events; new events are dropped until the buffer drains.`
|
|
745
|
+
);
|
|
601
746
|
}
|
|
602
|
-
|
|
603
|
-
|
|
747
|
+
return;
|
|
748
|
+
}
|
|
749
|
+
buffer.push(event);
|
|
750
|
+
if (buffer.length >= maxBatchSize) void flush();
|
|
751
|
+
};
|
|
752
|
+
return {
|
|
753
|
+
track,
|
|
754
|
+
deliver: async (event) => {
|
|
755
|
+
track(event);
|
|
756
|
+
return flush();
|
|
604
757
|
},
|
|
605
758
|
flush,
|
|
606
759
|
flushOnExit: opts?.exitBatchSink ? () => {
|
|
@@ -634,266 +787,6 @@ function createTrackingClient(opts) {
|
|
|
634
787
|
};
|
|
635
788
|
}
|
|
636
789
|
|
|
637
|
-
// src/ids.ts
|
|
638
|
-
function createSessionId() {
|
|
639
|
-
const g = globalThis;
|
|
640
|
-
if (g.crypto?.randomUUID) return g.crypto.randomUUID();
|
|
641
|
-
return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 11)}`;
|
|
642
|
-
}
|
|
643
|
-
|
|
644
|
-
// src/time.ts
|
|
645
|
-
function nowIso() {
|
|
646
|
-
return (/* @__PURE__ */ new Date()).toISOString();
|
|
647
|
-
}
|
|
648
|
-
|
|
649
|
-
// src/telemetry/eventRegistry.ts
|
|
650
|
-
function resolveLessonId(opts, eventName) {
|
|
651
|
-
const lessonId = opts.lessonId ?? opts.data?.lessonId;
|
|
652
|
-
if (!lessonId) throw new Error(`${eventName} requires lessonId`);
|
|
653
|
-
return lessonId;
|
|
654
|
-
}
|
|
655
|
-
function withLessonScopedData(name, base, lessonId, data) {
|
|
656
|
-
return { name, ...base, lessonId, data: { ...data, lessonId } };
|
|
657
|
-
}
|
|
658
|
-
var TELEMETRY_EVENT_REGISTRY = {
|
|
659
|
-
course_started: {
|
|
660
|
-
build: (_opts, base) => ({ name: "course_started", ...base })
|
|
661
|
-
},
|
|
662
|
-
course_completed: {
|
|
663
|
-
build: (_opts, base) => ({ name: "course_completed", ...base })
|
|
664
|
-
},
|
|
665
|
-
lesson_started: {
|
|
666
|
-
requiresLessonId: true,
|
|
667
|
-
build: (opts, base) => {
|
|
668
|
-
if (opts.name !== "lesson_started") throw new Error("unexpected event");
|
|
669
|
-
const lessonId = resolveLessonId(opts, "lesson_started");
|
|
670
|
-
return withLessonScopedData("lesson_started", base, lessonId, opts.data);
|
|
671
|
-
}
|
|
672
|
-
},
|
|
673
|
-
lesson_completed: {
|
|
674
|
-
requiresLessonId: true,
|
|
675
|
-
build: (opts, base) => {
|
|
676
|
-
if (opts.name !== "lesson_completed") throw new Error("unexpected event");
|
|
677
|
-
const lessonId = resolveLessonId(opts, opts.name);
|
|
678
|
-
return withLessonScopedData(opts.name, base, lessonId, opts.data);
|
|
679
|
-
}
|
|
680
|
-
},
|
|
681
|
-
lesson_time_on_task: {
|
|
682
|
-
requiresLessonId: true,
|
|
683
|
-
build: (opts, base) => {
|
|
684
|
-
if (opts.name !== "lesson_time_on_task") throw new Error("unexpected event");
|
|
685
|
-
const lessonId = resolveLessonId(opts, opts.name);
|
|
686
|
-
return withLessonScopedData(opts.name, base, lessonId, opts.data);
|
|
687
|
-
}
|
|
688
|
-
},
|
|
689
|
-
quiz_answered: {
|
|
690
|
-
requiresLessonId: true,
|
|
691
|
-
tryBuildMissingLessonWarning: "quiz",
|
|
692
|
-
build: (opts, base) => {
|
|
693
|
-
if (opts.name !== "quiz_answered") throw new Error("unexpected event");
|
|
694
|
-
const lessonId = opts.lessonId;
|
|
695
|
-
if (!lessonId) throw new Error("quiz_answered requires active lessonId");
|
|
696
|
-
return {
|
|
697
|
-
name: "quiz_answered",
|
|
698
|
-
...base,
|
|
699
|
-
lessonId,
|
|
700
|
-
data: opts.data
|
|
701
|
-
};
|
|
702
|
-
}
|
|
703
|
-
},
|
|
704
|
-
quiz_completed: {
|
|
705
|
-
requiresLessonId: true,
|
|
706
|
-
tryBuildMissingLessonWarning: "quiz",
|
|
707
|
-
build: (opts, base) => {
|
|
708
|
-
if (opts.name !== "quiz_completed") throw new Error("unexpected event");
|
|
709
|
-
const lessonId = opts.lessonId;
|
|
710
|
-
if (!lessonId) throw new Error("quiz_completed requires active lessonId");
|
|
711
|
-
return {
|
|
712
|
-
name: "quiz_completed",
|
|
713
|
-
...base,
|
|
714
|
-
lessonId,
|
|
715
|
-
data: opts.data
|
|
716
|
-
};
|
|
717
|
-
}
|
|
718
|
-
},
|
|
719
|
-
assessment_answered: {
|
|
720
|
-
requiresLessonId: true,
|
|
721
|
-
tryBuildMissingLessonWarning: "assessment",
|
|
722
|
-
build: (opts, base) => {
|
|
723
|
-
if (opts.name !== "assessment_answered") throw new Error("unexpected event");
|
|
724
|
-
const lessonId = opts.lessonId;
|
|
725
|
-
if (!lessonId) throw new Error("assessment_answered requires active lessonId");
|
|
726
|
-
return {
|
|
727
|
-
name: "assessment_answered",
|
|
728
|
-
...base,
|
|
729
|
-
lessonId,
|
|
730
|
-
data: opts.data
|
|
731
|
-
};
|
|
732
|
-
}
|
|
733
|
-
},
|
|
734
|
-
assessment_completed: {
|
|
735
|
-
requiresLessonId: true,
|
|
736
|
-
tryBuildMissingLessonWarning: "assessment",
|
|
737
|
-
build: (opts, base) => {
|
|
738
|
-
if (opts.name !== "assessment_completed") throw new Error("unexpected event");
|
|
739
|
-
const lessonId = opts.lessonId;
|
|
740
|
-
if (!lessonId) throw new Error("assessment_completed requires active lessonId");
|
|
741
|
-
return {
|
|
742
|
-
name: "assessment_completed",
|
|
743
|
-
...base,
|
|
744
|
-
lessonId,
|
|
745
|
-
data: opts.data
|
|
746
|
-
};
|
|
747
|
-
}
|
|
748
|
-
},
|
|
749
|
-
interaction: {
|
|
750
|
-
build: (opts, base) => {
|
|
751
|
-
if (opts.name !== "interaction") throw new Error("unexpected event");
|
|
752
|
-
return {
|
|
753
|
-
name: "interaction",
|
|
754
|
-
...base,
|
|
755
|
-
lessonId: opts.lessonId,
|
|
756
|
-
data: opts.data
|
|
757
|
-
};
|
|
758
|
-
}
|
|
759
|
-
},
|
|
760
|
-
book_page_viewed: {
|
|
761
|
-
requiresLessonId: true,
|
|
762
|
-
build: (opts, base) => {
|
|
763
|
-
if (opts.name !== "book_page_viewed") throw new Error("unexpected event");
|
|
764
|
-
const lessonId = opts.lessonId;
|
|
765
|
-
if (!lessonId) throw new Error("book_page_viewed requires active lessonId");
|
|
766
|
-
return {
|
|
767
|
-
name: "book_page_viewed",
|
|
768
|
-
...base,
|
|
769
|
-
lessonId,
|
|
770
|
-
data: opts.data
|
|
771
|
-
};
|
|
772
|
-
}
|
|
773
|
-
},
|
|
774
|
-
slide_viewed: {
|
|
775
|
-
requiresLessonId: true,
|
|
776
|
-
build: (opts, base) => {
|
|
777
|
-
if (opts.name !== "slide_viewed") throw new Error("unexpected event");
|
|
778
|
-
const lessonId = opts.lessonId;
|
|
779
|
-
if (!lessonId) throw new Error("slide_viewed requires active lessonId");
|
|
780
|
-
return {
|
|
781
|
-
name: "slide_viewed",
|
|
782
|
-
...base,
|
|
783
|
-
lessonId,
|
|
784
|
-
data: opts.data
|
|
785
|
-
};
|
|
786
|
-
}
|
|
787
|
-
},
|
|
788
|
-
compound_page_viewed: {
|
|
789
|
-
requiresLessonId: true,
|
|
790
|
-
build: (opts, base) => {
|
|
791
|
-
if (opts.name !== "compound_page_viewed") throw new Error("unexpected event");
|
|
792
|
-
const lessonId = opts.lessonId;
|
|
793
|
-
if (!lessonId) throw new Error("compound_page_viewed requires active lessonId");
|
|
794
|
-
return {
|
|
795
|
-
name: "compound_page_viewed",
|
|
796
|
-
...base,
|
|
797
|
-
lessonId,
|
|
798
|
-
data: opts.data
|
|
799
|
-
};
|
|
800
|
-
}
|
|
801
|
-
},
|
|
802
|
-
hotspot_opened: {
|
|
803
|
-
build: (opts, base) => {
|
|
804
|
-
if (opts.name !== "hotspot_opened") throw new Error("unexpected event");
|
|
805
|
-
return {
|
|
806
|
-
name: "hotspot_opened",
|
|
807
|
-
...base,
|
|
808
|
-
lessonId: opts.lessonId,
|
|
809
|
-
data: opts.data
|
|
810
|
-
};
|
|
811
|
-
}
|
|
812
|
-
},
|
|
813
|
-
accordion_section_toggled: {
|
|
814
|
-
build: (opts, base) => {
|
|
815
|
-
if (opts.name !== "accordion_section_toggled") throw new Error("unexpected event");
|
|
816
|
-
return {
|
|
817
|
-
name: "accordion_section_toggled",
|
|
818
|
-
...base,
|
|
819
|
-
lessonId: opts.lessonId,
|
|
820
|
-
data: opts.data
|
|
821
|
-
};
|
|
822
|
-
}
|
|
823
|
-
},
|
|
824
|
-
flashcard_flipped: {
|
|
825
|
-
build: (opts, base) => {
|
|
826
|
-
if (opts.name !== "flashcard_flipped") throw new Error("unexpected event");
|
|
827
|
-
return {
|
|
828
|
-
name: "flashcard_flipped",
|
|
829
|
-
...base,
|
|
830
|
-
lessonId: opts.lessonId,
|
|
831
|
-
data: opts.data
|
|
832
|
-
};
|
|
833
|
-
}
|
|
834
|
-
},
|
|
835
|
-
image_slider_changed: {
|
|
836
|
-
build: (opts, base) => {
|
|
837
|
-
if (opts.name !== "image_slider_changed") throw new Error("unexpected event");
|
|
838
|
-
return {
|
|
839
|
-
name: "image_slider_changed",
|
|
840
|
-
...base,
|
|
841
|
-
lessonId: opts.lessonId,
|
|
842
|
-
data: opts.data
|
|
843
|
-
};
|
|
844
|
-
}
|
|
845
|
-
}
|
|
846
|
-
};
|
|
847
|
-
function buildTelemetryEventFromRegistry(opts) {
|
|
848
|
-
const entry = TELEMETRY_EVENT_REGISTRY[opts.name];
|
|
849
|
-
if (!entry) {
|
|
850
|
-
throw new Error("Unexpected value");
|
|
851
|
-
}
|
|
852
|
-
const base = {
|
|
853
|
-
timestamp: opts.timestamp ?? nowIso(),
|
|
854
|
-
courseId: opts.courseId,
|
|
855
|
-
sessionId: opts.sessionId,
|
|
856
|
-
attemptId: opts.attemptId,
|
|
857
|
-
user: opts.user
|
|
858
|
-
};
|
|
859
|
-
return entry.build(opts, base);
|
|
860
|
-
}
|
|
861
|
-
function getTelemetryEventRegistryEntry(name) {
|
|
862
|
-
return TELEMETRY_EVENT_REGISTRY[name];
|
|
863
|
-
}
|
|
864
|
-
|
|
865
|
-
// src/telemetryBuilder.ts
|
|
866
|
-
var warnedMissingQuizLesson = false;
|
|
867
|
-
var warnedMissingAssessmentLesson = false;
|
|
868
|
-
function resetTelemetryBuilderWarningsForTests() {
|
|
869
|
-
warnedMissingQuizLesson = false;
|
|
870
|
-
warnedMissingAssessmentLesson = false;
|
|
871
|
-
}
|
|
872
|
-
function buildTelemetryEvent(opts) {
|
|
873
|
-
return buildTelemetryEventFromRegistry(opts);
|
|
874
|
-
}
|
|
875
|
-
function tryBuildTelemetryEvent(opts) {
|
|
876
|
-
const entry = getTelemetryEventRegistryEntry(opts.name);
|
|
877
|
-
if (entry.requiresLessonId && !opts.lessonId && entry.tryBuildMissingLessonWarning) {
|
|
878
|
-
if (isDevEnvironment()) {
|
|
879
|
-
if (entry.tryBuildMissingLessonWarning === "quiz" && !warnedMissingQuizLesson) {
|
|
880
|
-
warnedMissingQuizLesson = true;
|
|
881
|
-
console.warn(
|
|
882
|
-
`[lessonkit] ${opts.name} skipped: wrap <Quiz> in <Lesson> so an active lessonId is available`
|
|
883
|
-
);
|
|
884
|
-
}
|
|
885
|
-
if (entry.tryBuildMissingLessonWarning === "assessment" && !warnedMissingAssessmentLesson) {
|
|
886
|
-
warnedMissingAssessmentLesson = true;
|
|
887
|
-
console.warn(
|
|
888
|
-
`[lessonkit] ${opts.name} skipped: wrap assessment blocks in <Lesson> so an active lessonId is available`
|
|
889
|
-
);
|
|
890
|
-
}
|
|
891
|
-
}
|
|
892
|
-
return null;
|
|
893
|
-
}
|
|
894
|
-
return buildTelemetryEvent(opts);
|
|
895
|
-
}
|
|
896
|
-
|
|
897
790
|
// src/telemetryPipeline.ts
|
|
898
791
|
function invokeSink(sink, event, emitCtx) {
|
|
899
792
|
invokePipelineSink(sink.id, () => sink.emit(event, emitCtx));
|
|
@@ -923,109 +816,6 @@ function createTrackingPipelineSink(id, track) {
|
|
|
923
816
|
};
|
|
924
817
|
}
|
|
925
818
|
|
|
926
|
-
// src/ports.ts
|
|
927
|
-
function createDefaultClock() {
|
|
928
|
-
return {
|
|
929
|
-
nowMs: () => Date.now(),
|
|
930
|
-
nowIso: () => (/* @__PURE__ */ new Date()).toISOString()
|
|
931
|
-
};
|
|
932
|
-
}
|
|
933
|
-
function createNoopStorage() {
|
|
934
|
-
return {
|
|
935
|
-
getItem: () => null,
|
|
936
|
-
setItem: () => true
|
|
937
|
-
};
|
|
938
|
-
}
|
|
939
|
-
function createMemoryBackedSessionStorage(session) {
|
|
940
|
-
const memory = /* @__PURE__ */ new Map();
|
|
941
|
-
let warnedPersistFailure = false;
|
|
942
|
-
const warnPersistFailure = () => {
|
|
943
|
-
if (warnedPersistFailure) return;
|
|
944
|
-
warnedPersistFailure = true;
|
|
945
|
-
const g = globalThis;
|
|
946
|
-
if (typeof g.process !== "undefined" && g.process.env?.NODE_ENV === "development") {
|
|
947
|
-
console.warn(
|
|
948
|
-
"[lessonkit] sessionStorage is unavailable or failed; using in-memory session dedupe for this tab (may reset on full reload)."
|
|
949
|
-
);
|
|
950
|
-
}
|
|
951
|
-
};
|
|
952
|
-
return {
|
|
953
|
-
getItem: (key) => {
|
|
954
|
-
if (memory.has(key)) return memory.get(key);
|
|
955
|
-
try {
|
|
956
|
-
const value = session.getItem(key);
|
|
957
|
-
if (value !== null) memory.set(key, value);
|
|
958
|
-
return value;
|
|
959
|
-
} catch {
|
|
960
|
-
return memory.get(key) ?? null;
|
|
961
|
-
}
|
|
962
|
-
},
|
|
963
|
-
setItem: (key, value) => {
|
|
964
|
-
memory.set(key, value);
|
|
965
|
-
try {
|
|
966
|
-
session.setItem(key, value);
|
|
967
|
-
return true;
|
|
968
|
-
} catch {
|
|
969
|
-
warnPersistFailure();
|
|
970
|
-
return false;
|
|
971
|
-
}
|
|
972
|
-
},
|
|
973
|
-
removeItem: (key) => {
|
|
974
|
-
memory.delete(key);
|
|
975
|
-
try {
|
|
976
|
-
session.removeItem(key);
|
|
977
|
-
} catch {
|
|
978
|
-
warnPersistFailure();
|
|
979
|
-
}
|
|
980
|
-
},
|
|
981
|
-
resetForTests: () => {
|
|
982
|
-
memory.clear();
|
|
983
|
-
}
|
|
984
|
-
};
|
|
985
|
-
}
|
|
986
|
-
function resetStoragePortForTests(storage) {
|
|
987
|
-
storage.resetForTests?.();
|
|
988
|
-
}
|
|
989
|
-
function createInMemorySessionStoragePort() {
|
|
990
|
-
const memory = /* @__PURE__ */ new Map();
|
|
991
|
-
return {
|
|
992
|
-
getItem: (key) => memory.get(key) ?? null,
|
|
993
|
-
setItem: (key, value) => {
|
|
994
|
-
memory.set(key, value);
|
|
995
|
-
return true;
|
|
996
|
-
},
|
|
997
|
-
removeItem: (key) => {
|
|
998
|
-
memory.delete(key);
|
|
999
|
-
},
|
|
1000
|
-
resetForTests: () => {
|
|
1001
|
-
memory.clear();
|
|
1002
|
-
}
|
|
1003
|
-
};
|
|
1004
|
-
}
|
|
1005
|
-
function resolveBrowserSessionStorage() {
|
|
1006
|
-
try {
|
|
1007
|
-
if (typeof sessionStorage === "undefined" || sessionStorage == null) {
|
|
1008
|
-
return null;
|
|
1009
|
-
}
|
|
1010
|
-
return sessionStorage;
|
|
1011
|
-
} catch {
|
|
1012
|
-
return null;
|
|
1013
|
-
}
|
|
1014
|
-
}
|
|
1015
|
-
function createSessionStoragePort() {
|
|
1016
|
-
const session = resolveBrowserSessionStorage();
|
|
1017
|
-
if (!session) {
|
|
1018
|
-
return createInMemorySessionStoragePort();
|
|
1019
|
-
}
|
|
1020
|
-
return createMemoryBackedSessionStorage(session);
|
|
1021
|
-
}
|
|
1022
|
-
function createGlobalTimer() {
|
|
1023
|
-
return {
|
|
1024
|
-
setInterval: (fn, ms) => globalThis.setInterval(fn, ms),
|
|
1025
|
-
clearInterval: (id) => globalThis.clearInterval(id)
|
|
1026
|
-
};
|
|
1027
|
-
}
|
|
1028
|
-
|
|
1029
819
|
// src/progress.ts
|
|
1030
820
|
function createProgressController() {
|
|
1031
821
|
let activeLessonId;
|
|
@@ -1068,153 +858,6 @@ function createProgressController() {
|
|
|
1068
858
|
};
|
|
1069
859
|
}
|
|
1070
860
|
|
|
1071
|
-
// src/session.ts
|
|
1072
|
-
var SESSION_STORAGE_KEY = "lessonkit:sessionId";
|
|
1073
|
-
var volatileSessionIds = /* @__PURE__ */ new WeakMap();
|
|
1074
|
-
var sharedVolatileSessionId = null;
|
|
1075
|
-
function isDevEnvironment2() {
|
|
1076
|
-
const g = globalThis;
|
|
1077
|
-
return typeof g.process !== "undefined" && g.process.env?.NODE_ENV !== "production";
|
|
1078
|
-
}
|
|
1079
|
-
function getTabSessionId(storage) {
|
|
1080
|
-
return storage.getItem(SESSION_STORAGE_KEY);
|
|
1081
|
-
}
|
|
1082
|
-
var COURSE_STARTED_PREFIX = "lessonkit:course_started:";
|
|
1083
|
-
var COURSE_STARTED_TRACKING_PREFIX = "lessonkit:course_started_tracking:";
|
|
1084
|
-
var COURSE_STARTED_PIPELINE_PREFIX = "lessonkit:course_started_pipeline:";
|
|
1085
|
-
function resolveSessionId(storage, provided) {
|
|
1086
|
-
if (provided !== void 0) {
|
|
1087
|
-
const trimmed = provided.trim();
|
|
1088
|
-
if (trimmed.length > 0) return trimmed;
|
|
1089
|
-
}
|
|
1090
|
-
const existing = storage.getItem(SESSION_STORAGE_KEY);
|
|
1091
|
-
if (existing) return existing;
|
|
1092
|
-
const volatile = volatileSessionIds.get(storage);
|
|
1093
|
-
if (volatile) return volatile;
|
|
1094
|
-
const id = createSessionId();
|
|
1095
|
-
const persisted = storage.setItem(SESSION_STORAGE_KEY, id);
|
|
1096
|
-
if (!persisted) {
|
|
1097
|
-
if (!sharedVolatileSessionId) {
|
|
1098
|
-
sharedVolatileSessionId = id;
|
|
1099
|
-
}
|
|
1100
|
-
volatileSessionIds.set(storage, sharedVolatileSessionId);
|
|
1101
|
-
if (isDevEnvironment2()) {
|
|
1102
|
-
console.warn(
|
|
1103
|
-
"[lessonkit] session id could not be persisted; reusing in-memory id for this tab."
|
|
1104
|
-
);
|
|
1105
|
-
}
|
|
1106
|
-
return sharedVolatileSessionId;
|
|
1107
|
-
}
|
|
1108
|
-
return id;
|
|
1109
|
-
}
|
|
1110
|
-
function courseStartedStorageKey(sessionId, courseId) {
|
|
1111
|
-
return `${COURSE_STARTED_PREFIX}${sessionId}:${courseId ?? ""}`;
|
|
1112
|
-
}
|
|
1113
|
-
function courseStartedTrackingStorageKey(sessionId, courseId) {
|
|
1114
|
-
return `${COURSE_STARTED_TRACKING_PREFIX}${sessionId}:${courseId ?? ""}`;
|
|
1115
|
-
}
|
|
1116
|
-
function courseStartedPipelineStorageKey(sessionId, courseId) {
|
|
1117
|
-
return `${COURSE_STARTED_PIPELINE_PREFIX}${sessionId}:${courseId ?? ""}`;
|
|
1118
|
-
}
|
|
1119
|
-
function hasCourseStarted(storage, sessionId, courseId) {
|
|
1120
|
-
if (!courseId) return false;
|
|
1121
|
-
return storage.getItem(courseStartedStorageKey(sessionId, courseId)) === "1";
|
|
1122
|
-
}
|
|
1123
|
-
function markCourseStarted(storage, sessionId, courseId) {
|
|
1124
|
-
if (!courseId) return false;
|
|
1125
|
-
return storage.setItem(courseStartedStorageKey(sessionId, courseId), "1");
|
|
1126
|
-
}
|
|
1127
|
-
function hasCourseStartedEmittedToTracking(storage, sessionId, courseId) {
|
|
1128
|
-
if (!courseId) return false;
|
|
1129
|
-
return storage.getItem(courseStartedTrackingStorageKey(sessionId, courseId)) === "1";
|
|
1130
|
-
}
|
|
1131
|
-
function markCourseStartedEmittedToTracking(storage, sessionId, courseId) {
|
|
1132
|
-
if (!courseId) return false;
|
|
1133
|
-
return storage.setItem(courseStartedTrackingStorageKey(sessionId, courseId), "1");
|
|
1134
|
-
}
|
|
1135
|
-
function hasCourseStartedPipelineDelivered(storage, sessionId, courseId) {
|
|
1136
|
-
if (!courseId) return false;
|
|
1137
|
-
return storage.getItem(courseStartedPipelineStorageKey(sessionId, courseId)) === "1";
|
|
1138
|
-
}
|
|
1139
|
-
function markCourseStartedPipelineDelivered(storage, sessionId, courseId) {
|
|
1140
|
-
if (!courseId) return false;
|
|
1141
|
-
return storage.setItem(courseStartedPipelineStorageKey(sessionId, courseId), "1");
|
|
1142
|
-
}
|
|
1143
|
-
function resetSharedVolatileSessionIdForTests() {
|
|
1144
|
-
sharedVolatileSessionId = null;
|
|
1145
|
-
}
|
|
1146
|
-
function migrateCourseStartedMark(storage, fromSessionId, toSessionId, courseId) {
|
|
1147
|
-
if (!courseId || fromSessionId === toSessionId) return;
|
|
1148
|
-
if (hasCourseStarted(storage, fromSessionId, courseId)) {
|
|
1149
|
-
markCourseStarted(storage, toSessionId, courseId);
|
|
1150
|
-
storage.removeItem?.(courseStartedStorageKey(fromSessionId, courseId));
|
|
1151
|
-
}
|
|
1152
|
-
if (hasCourseStartedEmittedToTracking(storage, fromSessionId, courseId)) {
|
|
1153
|
-
markCourseStartedEmittedToTracking(storage, toSessionId, courseId);
|
|
1154
|
-
storage.removeItem?.(courseStartedTrackingStorageKey(fromSessionId, courseId));
|
|
1155
|
-
}
|
|
1156
|
-
if (hasCourseStartedPipelineDelivered(storage, fromSessionId, courseId)) {
|
|
1157
|
-
markCourseStartedPipelineDelivered(storage, toSessionId, courseId);
|
|
1158
|
-
storage.removeItem?.(courseStartedPipelineStorageKey(fromSessionId, courseId));
|
|
1159
|
-
}
|
|
1160
|
-
}
|
|
1161
|
-
|
|
1162
|
-
// src/runtime/courseLifecycle.ts
|
|
1163
|
-
var courseStartedEmitFlights = /* @__PURE__ */ new Set();
|
|
1164
|
-
function tryEmitCourseStarted(ctx, deps, alreadyEmittedToSink) {
|
|
1165
|
-
const flightKey = `${ctx.sessionId}:${ctx.courseId}`;
|
|
1166
|
-
const marked = hasCourseStarted(ctx.storage, ctx.sessionId, ctx.courseId);
|
|
1167
|
-
if (alreadyEmittedToSink) {
|
|
1168
|
-
return { emitted: true, marked };
|
|
1169
|
-
}
|
|
1170
|
-
if (courseStartedEmitFlights.has(flightKey)) {
|
|
1171
|
-
return { emitted: false, marked };
|
|
1172
|
-
}
|
|
1173
|
-
courseStartedEmitFlights.add(flightKey);
|
|
1174
|
-
try {
|
|
1175
|
-
const emitted = deps.emitCourseStartedEvent(ctx);
|
|
1176
|
-
if (emitted && !marked) {
|
|
1177
|
-
markCourseStarted(ctx.storage, ctx.sessionId, ctx.courseId);
|
|
1178
|
-
}
|
|
1179
|
-
return {
|
|
1180
|
-
emitted,
|
|
1181
|
-
marked: hasCourseStarted(ctx.storage, ctx.sessionId, ctx.courseId)
|
|
1182
|
-
};
|
|
1183
|
-
} finally {
|
|
1184
|
-
courseStartedEmitFlights.delete(flightKey);
|
|
1185
|
-
}
|
|
1186
|
-
}
|
|
1187
|
-
function buildCourseStartedTelemetryEvent(ctx) {
|
|
1188
|
-
return buildTelemetryEvent({
|
|
1189
|
-
name: "course_started",
|
|
1190
|
-
courseId: ctx.courseId,
|
|
1191
|
-
sessionId: ctx.sessionId,
|
|
1192
|
-
attemptId: ctx.attemptId,
|
|
1193
|
-
user: ctx.user
|
|
1194
|
-
});
|
|
1195
|
-
}
|
|
1196
|
-
function completeLessonWithTelemetry(opts) {
|
|
1197
|
-
const result = opts.progress.completeLesson(opts.lessonId, opts.nowMs);
|
|
1198
|
-
if (!result.didComplete) return false;
|
|
1199
|
-
opts.emitLessonCompleted(opts.lessonId, result.durationMs);
|
|
1200
|
-
return true;
|
|
1201
|
-
}
|
|
1202
|
-
function completeCourseWithTelemetry(opts) {
|
|
1203
|
-
const current = opts.progress.getState();
|
|
1204
|
-
if (current.activeLessonId) {
|
|
1205
|
-
completeLessonWithTelemetry({
|
|
1206
|
-
progress: opts.progress,
|
|
1207
|
-
lessonId: current.activeLessonId,
|
|
1208
|
-
nowMs: opts.nowMs,
|
|
1209
|
-
emitLessonCompleted: opts.emitLessonCompleted
|
|
1210
|
-
});
|
|
1211
|
-
}
|
|
1212
|
-
const result = opts.progress.completeCourse();
|
|
1213
|
-
if (!result.didComplete) return false;
|
|
1214
|
-
opts.emitCourseCompleted();
|
|
1215
|
-
return true;
|
|
1216
|
-
}
|
|
1217
|
-
|
|
1218
861
|
// src/plugins/context.ts
|
|
1219
862
|
function buildPluginContext(opts) {
|
|
1220
863
|
return {
|
|
@@ -1420,11 +1063,13 @@ function createLessonkitRuntime(config, ports = {}) {
|
|
|
1420
1063
|
if (next.courseId !== void 0 && next.courseId !== previousCourseId) {
|
|
1421
1064
|
progress = createProgressController();
|
|
1422
1065
|
}
|
|
1423
|
-
if (next.plugins !== void 0 && next.plugins !==
|
|
1066
|
+
if (next.plugins !== void 0 && next.plugins !== configSnapshot.plugins) {
|
|
1424
1067
|
pluginHost?.disposeAll();
|
|
1425
1068
|
configSnapshot.plugins = next.plugins;
|
|
1426
1069
|
pluginHost = resolvePluginHost(configSnapshot.plugins);
|
|
1427
|
-
|
|
1070
|
+
if (!configSnapshot.deferPluginSetup) {
|
|
1071
|
+
pluginHost?.setupAll(getPluginCtx());
|
|
1072
|
+
}
|
|
1428
1073
|
} else if (next.session !== void 0 && sessionKeyBefore !== sessionKeyAfter && pluginHost && !configSnapshot.deferPluginSetup) {
|
|
1429
1074
|
pluginHost.disposeAll();
|
|
1430
1075
|
pluginHost.setupAll(getPluginCtx());
|
|
@@ -1434,17 +1079,17 @@ function createLessonkitRuntime(config, ports = {}) {
|
|
|
1434
1079
|
const wrapped = wrapEmitFn(emitFn);
|
|
1435
1080
|
const current = progress.getState();
|
|
1436
1081
|
if (current.activeLessonId === lessonId) return;
|
|
1437
|
-
if (current.completedLessonIds.has(lessonId)) {
|
|
1438
|
-
progress.setActiveLesson(lessonId, clock.nowMs());
|
|
1439
|
-
return;
|
|
1440
|
-
}
|
|
1441
1082
|
const previous = current.activeLessonId;
|
|
1442
|
-
if (previous && previous !== lessonId) {
|
|
1083
|
+
if (previous && previous !== lessonId && !current.completedLessonIds.has(previous)) {
|
|
1443
1084
|
const completed = progress.completeLesson(previous, clock.nowMs());
|
|
1444
1085
|
if (completed.didComplete) {
|
|
1445
1086
|
emitLessonCompletedEvents(previous, completed.durationMs, wrapped);
|
|
1446
1087
|
}
|
|
1447
1088
|
}
|
|
1089
|
+
if (current.completedLessonIds.has(lessonId)) {
|
|
1090
|
+
progress.setActiveLesson(lessonId, clock.nowMs());
|
|
1091
|
+
return;
|
|
1092
|
+
}
|
|
1448
1093
|
progress.setActiveLesson(lessonId, clock.nowMs());
|
|
1449
1094
|
wrapped("lesson_started", { lessonId }, lessonId);
|
|
1450
1095
|
},
|
|
@@ -1465,9 +1110,12 @@ function createLessonkitRuntime(config, ports = {}) {
|
|
|
1465
1110
|
});
|
|
1466
1111
|
},
|
|
1467
1112
|
track,
|
|
1468
|
-
scoreAssessment(input,
|
|
1113
|
+
scoreAssessment(input, lessonId) {
|
|
1469
1114
|
if (!pluginHost) return null;
|
|
1470
|
-
return pluginHost.scoreAssessment(
|
|
1115
|
+
return pluginHost.scoreAssessment(
|
|
1116
|
+
{ ...input, lessonId: input.lessonId ?? lessonId },
|
|
1117
|
+
getPluginCtx()
|
|
1118
|
+
);
|
|
1471
1119
|
},
|
|
1472
1120
|
resetForCourseChange(nextCourseId) {
|
|
1473
1121
|
configSnapshot.courseId = nextCourseId;
|
|
@@ -1493,11 +1141,13 @@ function defineLifecyclePlugin(plugin) {
|
|
|
1493
1141
|
export {
|
|
1494
1142
|
ACCORDION_FORBIDDEN_CHILD_TYPES,
|
|
1495
1143
|
ASSESSMENT_SEQUENCE_ALLOWED_CHILD_TYPES,
|
|
1144
|
+
BLOCKS_14_PAGE_SLIDE,
|
|
1496
1145
|
COMPOUND_MAX_NESTING_DEPTH,
|
|
1497
1146
|
COMPOUND_RESUME_SCHEMA_VERSION,
|
|
1498
1147
|
ID_MAX_LENGTH,
|
|
1499
1148
|
ID_PATTERN,
|
|
1500
1149
|
INTERACTIVE_BOOK_ALLOWED_CHILD_TYPES,
|
|
1150
|
+
INTERACTIVE_VIDEO_ALLOWED_CHILD_TYPES,
|
|
1501
1151
|
PAGE_ALLOWED_CHILD_TYPES,
|
|
1502
1152
|
SESSION_STORAGE_KEY,
|
|
1503
1153
|
SLIDE_ALLOWED_CHILD_TYPES,
|
|
@@ -1505,6 +1155,7 @@ export {
|
|
|
1505
1155
|
TELEMETRY_EVENT_CATALOG,
|
|
1506
1156
|
TELEMETRY_EVENT_CATALOG_V2,
|
|
1507
1157
|
TELEMETRY_EVENT_CATALOG_V3,
|
|
1158
|
+
TIMED_CUE_ALLOWED_CHILD_TYPES,
|
|
1508
1159
|
assertNever,
|
|
1509
1160
|
assertValidId,
|
|
1510
1161
|
buildCourseStartedTelemetryEvent,
|