@lessonkit/core 1.3.0 → 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 +285 -36
- package/dist/index.d.cts +16 -481
- package/dist/index.d.ts +16 -481
- package/dist/index.js +216 -535
- 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;
|
|
@@ -522,12 +670,14 @@ function createTrackingClient(opts) {
|
|
|
522
670
|
}
|
|
523
671
|
const buffer = [];
|
|
524
672
|
let flushInFlight = null;
|
|
673
|
+
let inflightExitBatch = null;
|
|
525
674
|
let disposed = false;
|
|
526
675
|
let disposing = false;
|
|
527
676
|
let intervalId;
|
|
528
677
|
const runFlush = () => {
|
|
529
678
|
if (!buffer.length) return Promise.resolve(true);
|
|
530
679
|
const events = buffer.splice(0, buffer.length);
|
|
680
|
+
inflightExitBatch = events;
|
|
531
681
|
let succeeded = false;
|
|
532
682
|
return Promise.resolve().then(async () => {
|
|
533
683
|
if (batchSink) {
|
|
@@ -552,6 +702,8 @@ function createTrackingClient(opts) {
|
|
|
552
702
|
return runFlush();
|
|
553
703
|
}
|
|
554
704
|
return succeeded;
|
|
705
|
+
}).finally(() => {
|
|
706
|
+
inflightExitBatch = null;
|
|
555
707
|
});
|
|
556
708
|
};
|
|
557
709
|
const flush = () => {
|
|
@@ -582,23 +734,44 @@ function createTrackingClient(opts) {
|
|
|
582
734
|
};
|
|
583
735
|
intervalId = flushIntervalMs > 0 ? globalThis.setInterval(() => void flush(), flushIntervalMs) : void 0;
|
|
584
736
|
intervalId?.unref?.();
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
);
|
|
595
|
-
}
|
|
596
|
-
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
|
+
);
|
|
597
746
|
}
|
|
598
|
-
|
|
599
|
-
|
|
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();
|
|
600
757
|
},
|
|
601
758
|
flush,
|
|
759
|
+
flushOnExit: opts?.exitBatchSink ? () => {
|
|
760
|
+
const fromBuffer = buffer.splice(0, buffer.length);
|
|
761
|
+
const fromInflight = inflightExitBatch ? [...inflightExitBatch] : [];
|
|
762
|
+
const events = [...fromInflight, ...fromBuffer];
|
|
763
|
+
if (!events.length) return;
|
|
764
|
+
try {
|
|
765
|
+
const result = opts.exitBatchSink(events);
|
|
766
|
+
if (result != null && typeof result.catch === "function") {
|
|
767
|
+
void result.catch(() => {
|
|
768
|
+
buffer.unshift(...events);
|
|
769
|
+
});
|
|
770
|
+
}
|
|
771
|
+
} catch {
|
|
772
|
+
buffer.unshift(...events);
|
|
773
|
+
}
|
|
774
|
+
} : void 0,
|
|
602
775
|
dispose: () => {
|
|
603
776
|
if (disposed || disposing) return Promise.resolve();
|
|
604
777
|
disposing = true;
|
|
@@ -614,266 +787,6 @@ function createTrackingClient(opts) {
|
|
|
614
787
|
};
|
|
615
788
|
}
|
|
616
789
|
|
|
617
|
-
// src/ids.ts
|
|
618
|
-
function createSessionId() {
|
|
619
|
-
const g = globalThis;
|
|
620
|
-
if (g.crypto?.randomUUID) return g.crypto.randomUUID();
|
|
621
|
-
return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 11)}`;
|
|
622
|
-
}
|
|
623
|
-
|
|
624
|
-
// src/time.ts
|
|
625
|
-
function nowIso() {
|
|
626
|
-
return (/* @__PURE__ */ new Date()).toISOString();
|
|
627
|
-
}
|
|
628
|
-
|
|
629
|
-
// src/telemetry/eventRegistry.ts
|
|
630
|
-
function resolveLessonId(opts, eventName) {
|
|
631
|
-
const lessonId = opts.lessonId ?? opts.data?.lessonId;
|
|
632
|
-
if (!lessonId) throw new Error(`${eventName} requires lessonId`);
|
|
633
|
-
return lessonId;
|
|
634
|
-
}
|
|
635
|
-
function withLessonScopedData(name, base, lessonId, data) {
|
|
636
|
-
return { name, ...base, lessonId, data: { ...data, lessonId } };
|
|
637
|
-
}
|
|
638
|
-
var TELEMETRY_EVENT_REGISTRY = {
|
|
639
|
-
course_started: {
|
|
640
|
-
build: (_opts, base) => ({ name: "course_started", ...base })
|
|
641
|
-
},
|
|
642
|
-
course_completed: {
|
|
643
|
-
build: (_opts, base) => ({ name: "course_completed", ...base })
|
|
644
|
-
},
|
|
645
|
-
lesson_started: {
|
|
646
|
-
requiresLessonId: true,
|
|
647
|
-
build: (opts, base) => {
|
|
648
|
-
if (opts.name !== "lesson_started") throw new Error("unexpected event");
|
|
649
|
-
const lessonId = resolveLessonId(opts, "lesson_started");
|
|
650
|
-
return withLessonScopedData("lesson_started", base, lessonId, opts.data);
|
|
651
|
-
}
|
|
652
|
-
},
|
|
653
|
-
lesson_completed: {
|
|
654
|
-
requiresLessonId: true,
|
|
655
|
-
build: (opts, base) => {
|
|
656
|
-
if (opts.name !== "lesson_completed") throw new Error("unexpected event");
|
|
657
|
-
const lessonId = resolveLessonId(opts, opts.name);
|
|
658
|
-
return withLessonScopedData(opts.name, base, lessonId, opts.data);
|
|
659
|
-
}
|
|
660
|
-
},
|
|
661
|
-
lesson_time_on_task: {
|
|
662
|
-
requiresLessonId: true,
|
|
663
|
-
build: (opts, base) => {
|
|
664
|
-
if (opts.name !== "lesson_time_on_task") throw new Error("unexpected event");
|
|
665
|
-
const lessonId = resolveLessonId(opts, opts.name);
|
|
666
|
-
return withLessonScopedData(opts.name, base, lessonId, opts.data);
|
|
667
|
-
}
|
|
668
|
-
},
|
|
669
|
-
quiz_answered: {
|
|
670
|
-
requiresLessonId: true,
|
|
671
|
-
tryBuildMissingLessonWarning: "quiz",
|
|
672
|
-
build: (opts, base) => {
|
|
673
|
-
if (opts.name !== "quiz_answered") throw new Error("unexpected event");
|
|
674
|
-
const lessonId = opts.lessonId;
|
|
675
|
-
if (!lessonId) throw new Error("quiz_answered requires active lessonId");
|
|
676
|
-
return {
|
|
677
|
-
name: "quiz_answered",
|
|
678
|
-
...base,
|
|
679
|
-
lessonId,
|
|
680
|
-
data: opts.data
|
|
681
|
-
};
|
|
682
|
-
}
|
|
683
|
-
},
|
|
684
|
-
quiz_completed: {
|
|
685
|
-
requiresLessonId: true,
|
|
686
|
-
tryBuildMissingLessonWarning: "quiz",
|
|
687
|
-
build: (opts, base) => {
|
|
688
|
-
if (opts.name !== "quiz_completed") throw new Error("unexpected event");
|
|
689
|
-
const lessonId = opts.lessonId;
|
|
690
|
-
if (!lessonId) throw new Error("quiz_completed requires active lessonId");
|
|
691
|
-
return {
|
|
692
|
-
name: "quiz_completed",
|
|
693
|
-
...base,
|
|
694
|
-
lessonId,
|
|
695
|
-
data: opts.data
|
|
696
|
-
};
|
|
697
|
-
}
|
|
698
|
-
},
|
|
699
|
-
assessment_answered: {
|
|
700
|
-
requiresLessonId: true,
|
|
701
|
-
tryBuildMissingLessonWarning: "assessment",
|
|
702
|
-
build: (opts, base) => {
|
|
703
|
-
if (opts.name !== "assessment_answered") throw new Error("unexpected event");
|
|
704
|
-
const lessonId = opts.lessonId;
|
|
705
|
-
if (!lessonId) throw new Error("assessment_answered requires active lessonId");
|
|
706
|
-
return {
|
|
707
|
-
name: "assessment_answered",
|
|
708
|
-
...base,
|
|
709
|
-
lessonId,
|
|
710
|
-
data: opts.data
|
|
711
|
-
};
|
|
712
|
-
}
|
|
713
|
-
},
|
|
714
|
-
assessment_completed: {
|
|
715
|
-
requiresLessonId: true,
|
|
716
|
-
tryBuildMissingLessonWarning: "assessment",
|
|
717
|
-
build: (opts, base) => {
|
|
718
|
-
if (opts.name !== "assessment_completed") throw new Error("unexpected event");
|
|
719
|
-
const lessonId = opts.lessonId;
|
|
720
|
-
if (!lessonId) throw new Error("assessment_completed requires active lessonId");
|
|
721
|
-
return {
|
|
722
|
-
name: "assessment_completed",
|
|
723
|
-
...base,
|
|
724
|
-
lessonId,
|
|
725
|
-
data: opts.data
|
|
726
|
-
};
|
|
727
|
-
}
|
|
728
|
-
},
|
|
729
|
-
interaction: {
|
|
730
|
-
build: (opts, base) => {
|
|
731
|
-
if (opts.name !== "interaction") throw new Error("unexpected event");
|
|
732
|
-
return {
|
|
733
|
-
name: "interaction",
|
|
734
|
-
...base,
|
|
735
|
-
lessonId: opts.lessonId,
|
|
736
|
-
data: opts.data
|
|
737
|
-
};
|
|
738
|
-
}
|
|
739
|
-
},
|
|
740
|
-
book_page_viewed: {
|
|
741
|
-
requiresLessonId: true,
|
|
742
|
-
build: (opts, base) => {
|
|
743
|
-
if (opts.name !== "book_page_viewed") throw new Error("unexpected event");
|
|
744
|
-
const lessonId = opts.lessonId;
|
|
745
|
-
if (!lessonId) throw new Error("book_page_viewed requires active lessonId");
|
|
746
|
-
return {
|
|
747
|
-
name: "book_page_viewed",
|
|
748
|
-
...base,
|
|
749
|
-
lessonId,
|
|
750
|
-
data: opts.data
|
|
751
|
-
};
|
|
752
|
-
}
|
|
753
|
-
},
|
|
754
|
-
slide_viewed: {
|
|
755
|
-
requiresLessonId: true,
|
|
756
|
-
build: (opts, base) => {
|
|
757
|
-
if (opts.name !== "slide_viewed") throw new Error("unexpected event");
|
|
758
|
-
const lessonId = opts.lessonId;
|
|
759
|
-
if (!lessonId) throw new Error("slide_viewed requires active lessonId");
|
|
760
|
-
return {
|
|
761
|
-
name: "slide_viewed",
|
|
762
|
-
...base,
|
|
763
|
-
lessonId,
|
|
764
|
-
data: opts.data
|
|
765
|
-
};
|
|
766
|
-
}
|
|
767
|
-
},
|
|
768
|
-
compound_page_viewed: {
|
|
769
|
-
requiresLessonId: true,
|
|
770
|
-
build: (opts, base) => {
|
|
771
|
-
if (opts.name !== "compound_page_viewed") throw new Error("unexpected event");
|
|
772
|
-
const lessonId = opts.lessonId;
|
|
773
|
-
if (!lessonId) throw new Error("compound_page_viewed requires active lessonId");
|
|
774
|
-
return {
|
|
775
|
-
name: "compound_page_viewed",
|
|
776
|
-
...base,
|
|
777
|
-
lessonId,
|
|
778
|
-
data: opts.data
|
|
779
|
-
};
|
|
780
|
-
}
|
|
781
|
-
},
|
|
782
|
-
hotspot_opened: {
|
|
783
|
-
build: (opts, base) => {
|
|
784
|
-
if (opts.name !== "hotspot_opened") throw new Error("unexpected event");
|
|
785
|
-
return {
|
|
786
|
-
name: "hotspot_opened",
|
|
787
|
-
...base,
|
|
788
|
-
lessonId: opts.lessonId,
|
|
789
|
-
data: opts.data
|
|
790
|
-
};
|
|
791
|
-
}
|
|
792
|
-
},
|
|
793
|
-
accordion_section_toggled: {
|
|
794
|
-
build: (opts, base) => {
|
|
795
|
-
if (opts.name !== "accordion_section_toggled") throw new Error("unexpected event");
|
|
796
|
-
return {
|
|
797
|
-
name: "accordion_section_toggled",
|
|
798
|
-
...base,
|
|
799
|
-
lessonId: opts.lessonId,
|
|
800
|
-
data: opts.data
|
|
801
|
-
};
|
|
802
|
-
}
|
|
803
|
-
},
|
|
804
|
-
flashcard_flipped: {
|
|
805
|
-
build: (opts, base) => {
|
|
806
|
-
if (opts.name !== "flashcard_flipped") throw new Error("unexpected event");
|
|
807
|
-
return {
|
|
808
|
-
name: "flashcard_flipped",
|
|
809
|
-
...base,
|
|
810
|
-
lessonId: opts.lessonId,
|
|
811
|
-
data: opts.data
|
|
812
|
-
};
|
|
813
|
-
}
|
|
814
|
-
},
|
|
815
|
-
image_slider_changed: {
|
|
816
|
-
build: (opts, base) => {
|
|
817
|
-
if (opts.name !== "image_slider_changed") throw new Error("unexpected event");
|
|
818
|
-
return {
|
|
819
|
-
name: "image_slider_changed",
|
|
820
|
-
...base,
|
|
821
|
-
lessonId: opts.lessonId,
|
|
822
|
-
data: opts.data
|
|
823
|
-
};
|
|
824
|
-
}
|
|
825
|
-
}
|
|
826
|
-
};
|
|
827
|
-
function buildTelemetryEventFromRegistry(opts) {
|
|
828
|
-
const entry = TELEMETRY_EVENT_REGISTRY[opts.name];
|
|
829
|
-
if (!entry) {
|
|
830
|
-
throw new Error("Unexpected value");
|
|
831
|
-
}
|
|
832
|
-
const base = {
|
|
833
|
-
timestamp: opts.timestamp ?? nowIso(),
|
|
834
|
-
courseId: opts.courseId,
|
|
835
|
-
sessionId: opts.sessionId,
|
|
836
|
-
attemptId: opts.attemptId,
|
|
837
|
-
user: opts.user
|
|
838
|
-
};
|
|
839
|
-
return entry.build(opts, base);
|
|
840
|
-
}
|
|
841
|
-
function getTelemetryEventRegistryEntry(name) {
|
|
842
|
-
return TELEMETRY_EVENT_REGISTRY[name];
|
|
843
|
-
}
|
|
844
|
-
|
|
845
|
-
// src/telemetryBuilder.ts
|
|
846
|
-
var warnedMissingQuizLesson = false;
|
|
847
|
-
var warnedMissingAssessmentLesson = false;
|
|
848
|
-
function resetTelemetryBuilderWarningsForTests() {
|
|
849
|
-
warnedMissingQuizLesson = false;
|
|
850
|
-
warnedMissingAssessmentLesson = false;
|
|
851
|
-
}
|
|
852
|
-
function buildTelemetryEvent(opts) {
|
|
853
|
-
return buildTelemetryEventFromRegistry(opts);
|
|
854
|
-
}
|
|
855
|
-
function tryBuildTelemetryEvent(opts) {
|
|
856
|
-
const entry = getTelemetryEventRegistryEntry(opts.name);
|
|
857
|
-
if (entry.requiresLessonId && !opts.lessonId && entry.tryBuildMissingLessonWarning) {
|
|
858
|
-
if (isDevEnvironment()) {
|
|
859
|
-
if (entry.tryBuildMissingLessonWarning === "quiz" && !warnedMissingQuizLesson) {
|
|
860
|
-
warnedMissingQuizLesson = true;
|
|
861
|
-
console.warn(
|
|
862
|
-
`[lessonkit] ${opts.name} skipped: wrap <Quiz> in <Lesson> so an active lessonId is available`
|
|
863
|
-
);
|
|
864
|
-
}
|
|
865
|
-
if (entry.tryBuildMissingLessonWarning === "assessment" && !warnedMissingAssessmentLesson) {
|
|
866
|
-
warnedMissingAssessmentLesson = true;
|
|
867
|
-
console.warn(
|
|
868
|
-
`[lessonkit] ${opts.name} skipped: wrap assessment blocks in <Lesson> so an active lessonId is available`
|
|
869
|
-
);
|
|
870
|
-
}
|
|
871
|
-
}
|
|
872
|
-
return null;
|
|
873
|
-
}
|
|
874
|
-
return buildTelemetryEvent(opts);
|
|
875
|
-
}
|
|
876
|
-
|
|
877
790
|
// src/telemetryPipeline.ts
|
|
878
791
|
function invokeSink(sink, event, emitCtx) {
|
|
879
792
|
invokePipelineSink(sink.id, () => sink.emit(event, emitCtx));
|
|
@@ -903,109 +816,6 @@ function createTrackingPipelineSink(id, track) {
|
|
|
903
816
|
};
|
|
904
817
|
}
|
|
905
818
|
|
|
906
|
-
// src/ports.ts
|
|
907
|
-
function createDefaultClock() {
|
|
908
|
-
return {
|
|
909
|
-
nowMs: () => Date.now(),
|
|
910
|
-
nowIso: () => (/* @__PURE__ */ new Date()).toISOString()
|
|
911
|
-
};
|
|
912
|
-
}
|
|
913
|
-
function createNoopStorage() {
|
|
914
|
-
return {
|
|
915
|
-
getItem: () => null,
|
|
916
|
-
setItem: () => true
|
|
917
|
-
};
|
|
918
|
-
}
|
|
919
|
-
function createMemoryBackedSessionStorage(session) {
|
|
920
|
-
const memory = /* @__PURE__ */ new Map();
|
|
921
|
-
let warnedPersistFailure = false;
|
|
922
|
-
const warnPersistFailure = () => {
|
|
923
|
-
if (warnedPersistFailure) return;
|
|
924
|
-
warnedPersistFailure = true;
|
|
925
|
-
const g = globalThis;
|
|
926
|
-
if (typeof g.process !== "undefined" && g.process.env?.NODE_ENV === "development") {
|
|
927
|
-
console.warn(
|
|
928
|
-
"[lessonkit] sessionStorage is unavailable or failed; using in-memory session dedupe for this tab (may reset on full reload)."
|
|
929
|
-
);
|
|
930
|
-
}
|
|
931
|
-
};
|
|
932
|
-
return {
|
|
933
|
-
getItem: (key) => {
|
|
934
|
-
if (memory.has(key)) return memory.get(key);
|
|
935
|
-
try {
|
|
936
|
-
const value = session.getItem(key);
|
|
937
|
-
if (value !== null) memory.set(key, value);
|
|
938
|
-
return value;
|
|
939
|
-
} catch {
|
|
940
|
-
return memory.get(key) ?? null;
|
|
941
|
-
}
|
|
942
|
-
},
|
|
943
|
-
setItem: (key, value) => {
|
|
944
|
-
memory.set(key, value);
|
|
945
|
-
try {
|
|
946
|
-
session.setItem(key, value);
|
|
947
|
-
return true;
|
|
948
|
-
} catch {
|
|
949
|
-
warnPersistFailure();
|
|
950
|
-
return false;
|
|
951
|
-
}
|
|
952
|
-
},
|
|
953
|
-
removeItem: (key) => {
|
|
954
|
-
memory.delete(key);
|
|
955
|
-
try {
|
|
956
|
-
session.removeItem(key);
|
|
957
|
-
} catch {
|
|
958
|
-
warnPersistFailure();
|
|
959
|
-
}
|
|
960
|
-
},
|
|
961
|
-
resetForTests: () => {
|
|
962
|
-
memory.clear();
|
|
963
|
-
}
|
|
964
|
-
};
|
|
965
|
-
}
|
|
966
|
-
function resetStoragePortForTests(storage) {
|
|
967
|
-
storage.resetForTests?.();
|
|
968
|
-
}
|
|
969
|
-
function createInMemorySessionStoragePort() {
|
|
970
|
-
const memory = /* @__PURE__ */ new Map();
|
|
971
|
-
return {
|
|
972
|
-
getItem: (key) => memory.get(key) ?? null,
|
|
973
|
-
setItem: (key, value) => {
|
|
974
|
-
memory.set(key, value);
|
|
975
|
-
return true;
|
|
976
|
-
},
|
|
977
|
-
removeItem: (key) => {
|
|
978
|
-
memory.delete(key);
|
|
979
|
-
},
|
|
980
|
-
resetForTests: () => {
|
|
981
|
-
memory.clear();
|
|
982
|
-
}
|
|
983
|
-
};
|
|
984
|
-
}
|
|
985
|
-
function resolveBrowserSessionStorage() {
|
|
986
|
-
try {
|
|
987
|
-
if (typeof sessionStorage === "undefined" || sessionStorage == null) {
|
|
988
|
-
return null;
|
|
989
|
-
}
|
|
990
|
-
return sessionStorage;
|
|
991
|
-
} catch {
|
|
992
|
-
return null;
|
|
993
|
-
}
|
|
994
|
-
}
|
|
995
|
-
function createSessionStoragePort() {
|
|
996
|
-
const session = resolveBrowserSessionStorage();
|
|
997
|
-
if (!session) {
|
|
998
|
-
return createInMemorySessionStoragePort();
|
|
999
|
-
}
|
|
1000
|
-
return createMemoryBackedSessionStorage(session);
|
|
1001
|
-
}
|
|
1002
|
-
function createGlobalTimer() {
|
|
1003
|
-
return {
|
|
1004
|
-
setInterval: (fn, ms) => globalThis.setInterval(fn, ms),
|
|
1005
|
-
clearInterval: (id) => globalThis.clearInterval(id)
|
|
1006
|
-
};
|
|
1007
|
-
}
|
|
1008
|
-
|
|
1009
819
|
// src/progress.ts
|
|
1010
820
|
function createProgressController() {
|
|
1011
821
|
let activeLessonId;
|
|
@@ -1048,143 +858,6 @@ function createProgressController() {
|
|
|
1048
858
|
};
|
|
1049
859
|
}
|
|
1050
860
|
|
|
1051
|
-
// src/session.ts
|
|
1052
|
-
var SESSION_STORAGE_KEY = "lessonkit:sessionId";
|
|
1053
|
-
var volatileSessionIds = /* @__PURE__ */ new WeakMap();
|
|
1054
|
-
var sharedVolatileSessionId = null;
|
|
1055
|
-
function isDevEnvironment2() {
|
|
1056
|
-
const g = globalThis;
|
|
1057
|
-
return typeof g.process !== "undefined" && g.process.env?.NODE_ENV !== "production";
|
|
1058
|
-
}
|
|
1059
|
-
function getTabSessionId(storage) {
|
|
1060
|
-
return storage.getItem(SESSION_STORAGE_KEY);
|
|
1061
|
-
}
|
|
1062
|
-
var COURSE_STARTED_PREFIX = "lessonkit:course_started:";
|
|
1063
|
-
var COURSE_STARTED_TRACKING_PREFIX = "lessonkit:course_started_tracking:";
|
|
1064
|
-
var COURSE_STARTED_PIPELINE_PREFIX = "lessonkit:course_started_pipeline:";
|
|
1065
|
-
function resolveSessionId(storage, provided) {
|
|
1066
|
-
if (provided !== void 0) {
|
|
1067
|
-
const trimmed = provided.trim();
|
|
1068
|
-
if (trimmed.length > 0) return trimmed;
|
|
1069
|
-
}
|
|
1070
|
-
const existing = storage.getItem(SESSION_STORAGE_KEY);
|
|
1071
|
-
if (existing) return existing;
|
|
1072
|
-
const volatile = volatileSessionIds.get(storage);
|
|
1073
|
-
if (volatile) return volatile;
|
|
1074
|
-
const id = createSessionId();
|
|
1075
|
-
const persisted = storage.setItem(SESSION_STORAGE_KEY, id);
|
|
1076
|
-
if (!persisted) {
|
|
1077
|
-
if (!sharedVolatileSessionId) {
|
|
1078
|
-
sharedVolatileSessionId = id;
|
|
1079
|
-
}
|
|
1080
|
-
volatileSessionIds.set(storage, sharedVolatileSessionId);
|
|
1081
|
-
if (isDevEnvironment2()) {
|
|
1082
|
-
console.warn(
|
|
1083
|
-
"[lessonkit] session id could not be persisted; reusing in-memory id for this tab."
|
|
1084
|
-
);
|
|
1085
|
-
}
|
|
1086
|
-
return sharedVolatileSessionId;
|
|
1087
|
-
}
|
|
1088
|
-
return id;
|
|
1089
|
-
}
|
|
1090
|
-
function courseStartedStorageKey(sessionId, courseId) {
|
|
1091
|
-
return `${COURSE_STARTED_PREFIX}${sessionId}:${courseId ?? ""}`;
|
|
1092
|
-
}
|
|
1093
|
-
function courseStartedTrackingStorageKey(sessionId, courseId) {
|
|
1094
|
-
return `${COURSE_STARTED_TRACKING_PREFIX}${sessionId}:${courseId ?? ""}`;
|
|
1095
|
-
}
|
|
1096
|
-
function courseStartedPipelineStorageKey(sessionId, courseId) {
|
|
1097
|
-
return `${COURSE_STARTED_PIPELINE_PREFIX}${sessionId}:${courseId ?? ""}`;
|
|
1098
|
-
}
|
|
1099
|
-
function hasCourseStarted(storage, sessionId, courseId) {
|
|
1100
|
-
if (!courseId) return false;
|
|
1101
|
-
return storage.getItem(courseStartedStorageKey(sessionId, courseId)) === "1";
|
|
1102
|
-
}
|
|
1103
|
-
function markCourseStarted(storage, sessionId, courseId) {
|
|
1104
|
-
if (!courseId) return false;
|
|
1105
|
-
return storage.setItem(courseStartedStorageKey(sessionId, courseId), "1");
|
|
1106
|
-
}
|
|
1107
|
-
function hasCourseStartedEmittedToTracking(storage, sessionId, courseId) {
|
|
1108
|
-
if (!courseId) return false;
|
|
1109
|
-
return storage.getItem(courseStartedTrackingStorageKey(sessionId, courseId)) === "1";
|
|
1110
|
-
}
|
|
1111
|
-
function markCourseStartedEmittedToTracking(storage, sessionId, courseId) {
|
|
1112
|
-
if (!courseId) return;
|
|
1113
|
-
storage.setItem(courseStartedTrackingStorageKey(sessionId, courseId), "1");
|
|
1114
|
-
}
|
|
1115
|
-
function hasCourseStartedPipelineDelivered(storage, sessionId, courseId) {
|
|
1116
|
-
if (!courseId) return false;
|
|
1117
|
-
return storage.getItem(courseStartedPipelineStorageKey(sessionId, courseId)) === "1";
|
|
1118
|
-
}
|
|
1119
|
-
function markCourseStartedPipelineDelivered(storage, sessionId, courseId) {
|
|
1120
|
-
if (!courseId) return;
|
|
1121
|
-
storage.setItem(courseStartedPipelineStorageKey(sessionId, courseId), "1");
|
|
1122
|
-
}
|
|
1123
|
-
function resetSharedVolatileSessionIdForTests() {
|
|
1124
|
-
sharedVolatileSessionId = null;
|
|
1125
|
-
}
|
|
1126
|
-
function migrateCourseStartedMark(storage, fromSessionId, toSessionId, courseId) {
|
|
1127
|
-
if (!courseId || fromSessionId === toSessionId) return;
|
|
1128
|
-
if (hasCourseStarted(storage, fromSessionId, courseId)) {
|
|
1129
|
-
markCourseStarted(storage, toSessionId, courseId);
|
|
1130
|
-
storage.removeItem?.(courseStartedStorageKey(fromSessionId, courseId));
|
|
1131
|
-
}
|
|
1132
|
-
if (hasCourseStartedEmittedToTracking(storage, fromSessionId, courseId)) {
|
|
1133
|
-
markCourseStartedEmittedToTracking(storage, toSessionId, courseId);
|
|
1134
|
-
storage.removeItem?.(courseStartedTrackingStorageKey(fromSessionId, courseId));
|
|
1135
|
-
}
|
|
1136
|
-
if (hasCourseStartedPipelineDelivered(storage, fromSessionId, courseId)) {
|
|
1137
|
-
markCourseStartedPipelineDelivered(storage, toSessionId, courseId);
|
|
1138
|
-
storage.removeItem?.(courseStartedPipelineStorageKey(fromSessionId, courseId));
|
|
1139
|
-
}
|
|
1140
|
-
}
|
|
1141
|
-
|
|
1142
|
-
// src/runtime/courseLifecycle.ts
|
|
1143
|
-
function tryEmitCourseStarted(ctx, deps, alreadyEmittedToSink) {
|
|
1144
|
-
const marked = hasCourseStarted(ctx.storage, ctx.sessionId, ctx.courseId);
|
|
1145
|
-
if (alreadyEmittedToSink) {
|
|
1146
|
-
return { emitted: true, marked };
|
|
1147
|
-
}
|
|
1148
|
-
const emitted = deps.emitCourseStartedEvent(ctx);
|
|
1149
|
-
if (emitted && !marked) {
|
|
1150
|
-
markCourseStarted(ctx.storage, ctx.sessionId, ctx.courseId);
|
|
1151
|
-
}
|
|
1152
|
-
return {
|
|
1153
|
-
emitted,
|
|
1154
|
-
marked: hasCourseStarted(ctx.storage, ctx.sessionId, ctx.courseId)
|
|
1155
|
-
};
|
|
1156
|
-
}
|
|
1157
|
-
function buildCourseStartedTelemetryEvent(ctx) {
|
|
1158
|
-
return buildTelemetryEvent({
|
|
1159
|
-
name: "course_started",
|
|
1160
|
-
courseId: ctx.courseId,
|
|
1161
|
-
sessionId: ctx.sessionId,
|
|
1162
|
-
attemptId: ctx.attemptId,
|
|
1163
|
-
user: ctx.user
|
|
1164
|
-
});
|
|
1165
|
-
}
|
|
1166
|
-
function completeLessonWithTelemetry(opts) {
|
|
1167
|
-
const result = opts.progress.completeLesson(opts.lessonId, opts.nowMs);
|
|
1168
|
-
if (!result.didComplete) return false;
|
|
1169
|
-
opts.emitLessonCompleted(opts.lessonId, result.durationMs);
|
|
1170
|
-
return true;
|
|
1171
|
-
}
|
|
1172
|
-
function completeCourseWithTelemetry(opts) {
|
|
1173
|
-
const current = opts.progress.getState();
|
|
1174
|
-
if (current.activeLessonId) {
|
|
1175
|
-
completeLessonWithTelemetry({
|
|
1176
|
-
progress: opts.progress,
|
|
1177
|
-
lessonId: current.activeLessonId,
|
|
1178
|
-
nowMs: opts.nowMs,
|
|
1179
|
-
emitLessonCompleted: opts.emitLessonCompleted
|
|
1180
|
-
});
|
|
1181
|
-
}
|
|
1182
|
-
const result = opts.progress.completeCourse();
|
|
1183
|
-
if (!result.didComplete) return false;
|
|
1184
|
-
opts.emitCourseCompleted();
|
|
1185
|
-
return true;
|
|
1186
|
-
}
|
|
1187
|
-
|
|
1188
861
|
// src/plugins/context.ts
|
|
1189
862
|
function buildPluginContext(opts) {
|
|
1190
863
|
return {
|
|
@@ -1390,11 +1063,13 @@ function createLessonkitRuntime(config, ports = {}) {
|
|
|
1390
1063
|
if (next.courseId !== void 0 && next.courseId !== previousCourseId) {
|
|
1391
1064
|
progress = createProgressController();
|
|
1392
1065
|
}
|
|
1393
|
-
if (next.plugins !== void 0 && next.plugins !==
|
|
1066
|
+
if (next.plugins !== void 0 && next.plugins !== configSnapshot.plugins) {
|
|
1394
1067
|
pluginHost?.disposeAll();
|
|
1395
1068
|
configSnapshot.plugins = next.plugins;
|
|
1396
1069
|
pluginHost = resolvePluginHost(configSnapshot.plugins);
|
|
1397
|
-
|
|
1070
|
+
if (!configSnapshot.deferPluginSetup) {
|
|
1071
|
+
pluginHost?.setupAll(getPluginCtx());
|
|
1072
|
+
}
|
|
1398
1073
|
} else if (next.session !== void 0 && sessionKeyBefore !== sessionKeyAfter && pluginHost && !configSnapshot.deferPluginSetup) {
|
|
1399
1074
|
pluginHost.disposeAll();
|
|
1400
1075
|
pluginHost.setupAll(getPluginCtx());
|
|
@@ -1404,17 +1079,17 @@ function createLessonkitRuntime(config, ports = {}) {
|
|
|
1404
1079
|
const wrapped = wrapEmitFn(emitFn);
|
|
1405
1080
|
const current = progress.getState();
|
|
1406
1081
|
if (current.activeLessonId === lessonId) return;
|
|
1407
|
-
if (current.completedLessonIds.has(lessonId)) {
|
|
1408
|
-
progress.setActiveLesson(lessonId, clock.nowMs());
|
|
1409
|
-
return;
|
|
1410
|
-
}
|
|
1411
1082
|
const previous = current.activeLessonId;
|
|
1412
|
-
if (previous && previous !== lessonId) {
|
|
1083
|
+
if (previous && previous !== lessonId && !current.completedLessonIds.has(previous)) {
|
|
1413
1084
|
const completed = progress.completeLesson(previous, clock.nowMs());
|
|
1414
1085
|
if (completed.didComplete) {
|
|
1415
1086
|
emitLessonCompletedEvents(previous, completed.durationMs, wrapped);
|
|
1416
1087
|
}
|
|
1417
1088
|
}
|
|
1089
|
+
if (current.completedLessonIds.has(lessonId)) {
|
|
1090
|
+
progress.setActiveLesson(lessonId, clock.nowMs());
|
|
1091
|
+
return;
|
|
1092
|
+
}
|
|
1418
1093
|
progress.setActiveLesson(lessonId, clock.nowMs());
|
|
1419
1094
|
wrapped("lesson_started", { lessonId }, lessonId);
|
|
1420
1095
|
},
|
|
@@ -1435,9 +1110,12 @@ function createLessonkitRuntime(config, ports = {}) {
|
|
|
1435
1110
|
});
|
|
1436
1111
|
},
|
|
1437
1112
|
track,
|
|
1438
|
-
scoreAssessment(input,
|
|
1113
|
+
scoreAssessment(input, lessonId) {
|
|
1439
1114
|
if (!pluginHost) return null;
|
|
1440
|
-
return pluginHost.scoreAssessment(
|
|
1115
|
+
return pluginHost.scoreAssessment(
|
|
1116
|
+
{ ...input, lessonId: input.lessonId ?? lessonId },
|
|
1117
|
+
getPluginCtx()
|
|
1118
|
+
);
|
|
1441
1119
|
},
|
|
1442
1120
|
resetForCourseChange(nextCourseId) {
|
|
1443
1121
|
configSnapshot.courseId = nextCourseId;
|
|
@@ -1463,11 +1141,13 @@ function defineLifecyclePlugin(plugin) {
|
|
|
1463
1141
|
export {
|
|
1464
1142
|
ACCORDION_FORBIDDEN_CHILD_TYPES,
|
|
1465
1143
|
ASSESSMENT_SEQUENCE_ALLOWED_CHILD_TYPES,
|
|
1144
|
+
BLOCKS_14_PAGE_SLIDE,
|
|
1466
1145
|
COMPOUND_MAX_NESTING_DEPTH,
|
|
1467
1146
|
COMPOUND_RESUME_SCHEMA_VERSION,
|
|
1468
1147
|
ID_MAX_LENGTH,
|
|
1469
1148
|
ID_PATTERN,
|
|
1470
1149
|
INTERACTIVE_BOOK_ALLOWED_CHILD_TYPES,
|
|
1150
|
+
INTERACTIVE_VIDEO_ALLOWED_CHILD_TYPES,
|
|
1471
1151
|
PAGE_ALLOWED_CHILD_TYPES,
|
|
1472
1152
|
SESSION_STORAGE_KEY,
|
|
1473
1153
|
SLIDE_ALLOWED_CHILD_TYPES,
|
|
@@ -1475,6 +1155,7 @@ export {
|
|
|
1475
1155
|
TELEMETRY_EVENT_CATALOG,
|
|
1476
1156
|
TELEMETRY_EVENT_CATALOG_V2,
|
|
1477
1157
|
TELEMETRY_EVENT_CATALOG_V3,
|
|
1158
|
+
TIMED_CUE_ALLOWED_CHILD_TYPES,
|
|
1478
1159
|
assertNever,
|
|
1479
1160
|
assertValidId,
|
|
1480
1161
|
buildCourseStartedTelemetryEvent,
|