@lessonkit/core 1.0.2 → 1.2.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/index.cjs +626 -177
- package/dist/index.d.cts +264 -25
- package/dist/index.d.ts +264 -25
- package/dist/index.js +604 -177
- package/package.json +6 -2
- package/telemetry-catalog.v2.json +21 -0
- package/telemetry-catalog.v3.json +53 -0
package/dist/index.js
CHANGED
|
@@ -102,6 +102,122 @@ function buildLessonkitUrn(parts) {
|
|
|
102
102
|
return urn;
|
|
103
103
|
}
|
|
104
104
|
|
|
105
|
+
// src/compound.ts
|
|
106
|
+
var COMPOUND_RESUME_SCHEMA_VERSION = 1;
|
|
107
|
+
function createCompoundResumeState(input = {}) {
|
|
108
|
+
const childStates = {};
|
|
109
|
+
if (input.childStates) {
|
|
110
|
+
for (const [key, value] of Object.entries(input.childStates)) {
|
|
111
|
+
childStates[key] = value;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return {
|
|
115
|
+
schemaVersion: COMPOUND_RESUME_SCHEMA_VERSION,
|
|
116
|
+
activePageIndex: input.activePageIndex ?? 0,
|
|
117
|
+
...input.activeChapterIndex !== void 0 ? { activeChapterIndex: input.activeChapterIndex } : {},
|
|
118
|
+
childStates
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
function clampCompoundPageIndex(index, pageCount) {
|
|
122
|
+
if (pageCount < 1) return 0;
|
|
123
|
+
return Math.min(Math.max(0, Math.floor(index)), pageCount - 1);
|
|
124
|
+
}
|
|
125
|
+
function parseCompoundResumeState(raw) {
|
|
126
|
+
if (!raw || typeof raw !== "object") return null;
|
|
127
|
+
const obj = raw;
|
|
128
|
+
if (obj.schemaVersion !== COMPOUND_RESUME_SCHEMA_VERSION) return null;
|
|
129
|
+
if (typeof obj.activePageIndex !== "number" || !Number.isFinite(obj.activePageIndex)) return null;
|
|
130
|
+
const childStates = {};
|
|
131
|
+
if (obj.childStates && typeof obj.childStates === "object" && !Array.isArray(obj.childStates)) {
|
|
132
|
+
for (const [key, value] of Object.entries(obj.childStates)) {
|
|
133
|
+
if (value !== null && typeof value === "object" && !Array.isArray(value)) {
|
|
134
|
+
childStates[key] = value;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
const activeChapterIndex = typeof obj.activeChapterIndex === "number" && Number.isFinite(obj.activeChapterIndex) ? obj.activeChapterIndex : void 0;
|
|
139
|
+
return {
|
|
140
|
+
schemaVersion: COMPOUND_RESUME_SCHEMA_VERSION,
|
|
141
|
+
activePageIndex: Math.max(0, Math.floor(obj.activePageIndex)),
|
|
142
|
+
...activeChapterIndex !== void 0 ? { activeChapterIndex: Math.max(0, Math.floor(activeChapterIndex)) } : {},
|
|
143
|
+
childStates
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// src/compoundState.ts
|
|
148
|
+
var COMPOUND_STATE_PREFIX = "lessonkit:compound:";
|
|
149
|
+
function compoundStateStorageKey(courseId, compoundId) {
|
|
150
|
+
return `${COMPOUND_STATE_PREFIX}${courseId}:${compoundId}`;
|
|
151
|
+
}
|
|
152
|
+
function loadCompoundState(storage, courseId, compoundId) {
|
|
153
|
+
const raw = storage.getItem(compoundStateStorageKey(courseId, compoundId));
|
|
154
|
+
if (!raw) return null;
|
|
155
|
+
try {
|
|
156
|
+
return parseCompoundResumeState(JSON.parse(raw));
|
|
157
|
+
} catch {
|
|
158
|
+
return null;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
function saveCompoundState(storage, courseId, compoundId, state) {
|
|
162
|
+
storage.setItem(compoundStateStorageKey(courseId, compoundId), JSON.stringify(state));
|
|
163
|
+
}
|
|
164
|
+
function clearCompoundState(storage, courseId, compoundId) {
|
|
165
|
+
storage.removeItem?.(compoundStateStorageKey(courseId, compoundId));
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// src/compoundAllowlists.ts
|
|
169
|
+
var PAGE_ALLOWED_CHILD_TYPES = [
|
|
170
|
+
"Text",
|
|
171
|
+
"Heading",
|
|
172
|
+
"Image",
|
|
173
|
+
"Scenario",
|
|
174
|
+
"Reflection",
|
|
175
|
+
"Quiz",
|
|
176
|
+
"KnowledgeCheck",
|
|
177
|
+
"TrueFalse",
|
|
178
|
+
"FillInTheBlanks",
|
|
179
|
+
"DragAndDrop",
|
|
180
|
+
"DragTheWords",
|
|
181
|
+
"MarkTheWords",
|
|
182
|
+
"Accordion",
|
|
183
|
+
"DialogCards",
|
|
184
|
+
"Flashcards",
|
|
185
|
+
"ImageHotspots",
|
|
186
|
+
"FindHotspot",
|
|
187
|
+
"FindMultipleHotspots",
|
|
188
|
+
"ImageSlider",
|
|
189
|
+
"ProgressTracker"
|
|
190
|
+
];
|
|
191
|
+
var INTERACTIVE_BOOK_ALLOWED_CHILD_TYPES = ["Page"];
|
|
192
|
+
var ASSESSMENT_SEQUENCE_ALLOWED_CHILD_TYPES = [
|
|
193
|
+
"TrueFalse",
|
|
194
|
+
"FillInTheBlanks",
|
|
195
|
+
"DragAndDrop",
|
|
196
|
+
"DragTheWords",
|
|
197
|
+
"MarkTheWords",
|
|
198
|
+
"Quiz",
|
|
199
|
+
"KnowledgeCheck",
|
|
200
|
+
"FindHotspot",
|
|
201
|
+
"FindMultipleHotspots"
|
|
202
|
+
];
|
|
203
|
+
var ALLOWLISTS = {
|
|
204
|
+
Page: PAGE_ALLOWED_CHILD_TYPES,
|
|
205
|
+
InteractiveBook: INTERACTIVE_BOOK_ALLOWED_CHILD_TYPES,
|
|
206
|
+
AssessmentSequence: ASSESSMENT_SEQUENCE_ALLOWED_CHILD_TYPES
|
|
207
|
+
};
|
|
208
|
+
var COMPOUND_MAX_NESTING_DEPTH = {
|
|
209
|
+
Page: 1,
|
|
210
|
+
InteractiveBook: 2,
|
|
211
|
+
AssessmentSequence: 1
|
|
212
|
+
};
|
|
213
|
+
function getAllowedChildTypes(parent) {
|
|
214
|
+
return ALLOWLISTS[parent];
|
|
215
|
+
}
|
|
216
|
+
function isChildTypeAllowed(parent, childType) {
|
|
217
|
+
return ALLOWLISTS[parent].includes(childType);
|
|
218
|
+
}
|
|
219
|
+
var ACCORDION_FORBIDDEN_CHILD_TYPES = ["Accordion"];
|
|
220
|
+
|
|
105
221
|
// src/telemetryCatalog.ts
|
|
106
222
|
var telemetryCatalogVersion = 1;
|
|
107
223
|
var TELEMETRY_EVENT_CATALOG = [
|
|
@@ -174,35 +290,125 @@ function buildTelemetryCatalog() {
|
|
|
174
290
|
return TELEMETRY_EVENT_CATALOG.map((entry) => ({ ...entry }));
|
|
175
291
|
}
|
|
176
292
|
|
|
177
|
-
// src/
|
|
293
|
+
// src/telemetryCatalogV2.ts
|
|
294
|
+
var telemetryCatalogV2Version = 2;
|
|
295
|
+
var TELEMETRY_EVENT_CATALOG_V2 = [
|
|
296
|
+
{
|
|
297
|
+
name: "assessment_answered",
|
|
298
|
+
description: "Learner submitted an assessment interaction answer",
|
|
299
|
+
requiredFields: ["courseId", "lessonId", "sessionId", "timestamp"],
|
|
300
|
+
dataFields: ["checkId", "interactionType", "question", "response", "correct"],
|
|
301
|
+
xapiVerb: "http://adlnet.gov/expapi/verbs/answered",
|
|
302
|
+
urnPattern: "urn:lessonkit:course:{courseId}:lesson:{lessonId}:check:{checkId}"
|
|
303
|
+
},
|
|
304
|
+
{
|
|
305
|
+
name: "assessment_completed",
|
|
306
|
+
description: "Assessment interaction completed (passing criteria met)",
|
|
307
|
+
requiredFields: ["courseId", "lessonId", "sessionId", "timestamp"],
|
|
308
|
+
dataFields: ["checkId", "interactionType", "score", "maxScore", "passingScore"],
|
|
309
|
+
xapiVerb: "http://adlnet.gov/expapi/verbs/completed",
|
|
310
|
+
urnPattern: "urn:lessonkit:course:{courseId}:lesson:{lessonId}:check:{checkId}"
|
|
311
|
+
}
|
|
312
|
+
];
|
|
313
|
+
function buildTelemetryCatalogV2() {
|
|
314
|
+
return TELEMETRY_EVENT_CATALOG_V2.map((entry) => ({ ...entry }));
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// src/telemetryCatalogV3.ts
|
|
318
|
+
var telemetryCatalogV3Version = 3;
|
|
319
|
+
var TELEMETRY_EVENT_CATALOG_V3 = [
|
|
320
|
+
{
|
|
321
|
+
name: "book_page_viewed",
|
|
322
|
+
description: "Learner viewed a page/chapter in an Interactive Book",
|
|
323
|
+
requiredFields: ["courseId", "lessonId", "sessionId", "timestamp"],
|
|
324
|
+
dataFields: ["blockId", "pageIndex", "pageTitle"],
|
|
325
|
+
xapiVerb: "http://adlnet.gov/expapi/verbs/experienced",
|
|
326
|
+
urnPattern: "urn:lessonkit:course:{courseId}:lesson:{lessonId}:block:{blockId}"
|
|
327
|
+
},
|
|
328
|
+
{
|
|
329
|
+
name: "compound_page_viewed",
|
|
330
|
+
description: "Learner activated a page inside a compound container",
|
|
331
|
+
requiredFields: ["courseId", "lessonId", "sessionId", "timestamp"],
|
|
332
|
+
dataFields: ["blockId", "pageIndex", "parentType"],
|
|
333
|
+
xapiVerb: "http://adlnet.gov/expapi/verbs/experienced",
|
|
334
|
+
urnPattern: "urn:lessonkit:course:{courseId}:lesson:{lessonId}:block:{blockId}"
|
|
335
|
+
},
|
|
336
|
+
{
|
|
337
|
+
name: "hotspot_opened",
|
|
338
|
+
description: "Learner opened an image hotspot popover",
|
|
339
|
+
requiredFields: ["courseId", "sessionId", "timestamp"],
|
|
340
|
+
dataFields: ["blockId", "hotspotId"],
|
|
341
|
+
xapiVerb: "http://adlnet.gov/expapi/verbs/experienced",
|
|
342
|
+
urnPattern: "urn:lessonkit:course:{courseId}:lesson:{lessonId}:block:{blockId}"
|
|
343
|
+
},
|
|
344
|
+
{
|
|
345
|
+
name: "accordion_section_toggled",
|
|
346
|
+
description: "Learner expanded or collapsed an accordion section",
|
|
347
|
+
requiredFields: ["courseId", "sessionId", "timestamp"],
|
|
348
|
+
dataFields: ["blockId", "sectionId", "expanded"],
|
|
349
|
+
xapiVerb: "http://adlnet.gov/expapi/verbs/experienced",
|
|
350
|
+
urnPattern: "urn:lessonkit:course:{courseId}:lesson:{lessonId}:block:{blockId}"
|
|
351
|
+
},
|
|
352
|
+
{
|
|
353
|
+
name: "flashcard_flipped",
|
|
354
|
+
description: "Learner flipped a flashcard",
|
|
355
|
+
requiredFields: ["courseId", "sessionId", "timestamp"],
|
|
356
|
+
dataFields: ["blockId", "cardIndex", "face"],
|
|
357
|
+
xapiVerb: "http://adlnet.gov/expapi/verbs/experienced",
|
|
358
|
+
urnPattern: "urn:lessonkit:course:{courseId}:lesson:{lessonId}:block:{blockId}"
|
|
359
|
+
},
|
|
360
|
+
{
|
|
361
|
+
name: "image_slider_changed",
|
|
362
|
+
description: "Learner changed the active slide in an image slider",
|
|
363
|
+
requiredFields: ["courseId", "sessionId", "timestamp"],
|
|
364
|
+
dataFields: ["blockId", "slideIndex"],
|
|
365
|
+
xapiVerb: "http://adlnet.gov/expapi/verbs/experienced",
|
|
366
|
+
urnPattern: "urn:lessonkit:course:{courseId}:lesson:{lessonId}:block:{blockId}"
|
|
367
|
+
}
|
|
368
|
+
];
|
|
369
|
+
function buildTelemetryCatalogV3() {
|
|
370
|
+
return TELEMETRY_EVENT_CATALOG_V3.map((entry) => ({ ...entry }));
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// src/internal/env.ts
|
|
178
374
|
function isDevEnvironment() {
|
|
179
375
|
const g = globalThis;
|
|
180
376
|
return typeof g.process !== "undefined" && g.process.env?.NODE_ENV !== "production";
|
|
181
377
|
}
|
|
378
|
+
function warnDev(message, err) {
|
|
379
|
+
if (!isDevEnvironment()) return;
|
|
380
|
+
console.warn(message, err instanceof Error ? err.message : err);
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
// src/internal/sinkInvoke.ts
|
|
182
384
|
function invokeTrackingSink(sink, event) {
|
|
183
385
|
let result;
|
|
184
386
|
try {
|
|
185
387
|
result = sink(event);
|
|
186
388
|
} catch (err) {
|
|
187
|
-
|
|
188
|
-
console.warn(
|
|
189
|
-
"[lessonkit] tracking sink failed:",
|
|
190
|
-
err instanceof Error ? err.message : err
|
|
191
|
-
);
|
|
192
|
-
}
|
|
389
|
+
warnDev("[lessonkit] tracking sink failed:", err);
|
|
193
390
|
throw err;
|
|
194
391
|
}
|
|
195
392
|
if (result != null && typeof result.catch === "function") {
|
|
196
|
-
void result.catch((err) =>
|
|
197
|
-
if (isDevEnvironment()) {
|
|
198
|
-
console.warn(
|
|
199
|
-
"[lessonkit] tracking sink failed:",
|
|
200
|
-
err instanceof Error ? err.message : err
|
|
201
|
-
);
|
|
202
|
-
}
|
|
203
|
-
});
|
|
393
|
+
void result.catch((err) => warnDev("[lessonkit] tracking sink failed:", err));
|
|
204
394
|
}
|
|
205
395
|
}
|
|
396
|
+
function invokePipelineSink(sinkId, emit) {
|
|
397
|
+
let result;
|
|
398
|
+
try {
|
|
399
|
+
result = emit();
|
|
400
|
+
} catch (err) {
|
|
401
|
+
warnDev(`[lessonkit] telemetry sink "${sinkId}" failed:`, err);
|
|
402
|
+
return;
|
|
403
|
+
}
|
|
404
|
+
if (result != null && typeof result.catch === "function") {
|
|
405
|
+
void result.catch(
|
|
406
|
+
(err) => warnDev(`[lessonkit] telemetry sink "${sinkId}" failed:`, err)
|
|
407
|
+
);
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
// src/trackingClient.ts
|
|
206
412
|
function createTrackingClient(opts) {
|
|
207
413
|
const sink = opts?.sink;
|
|
208
414
|
const batchSink = opts?.batchSink;
|
|
@@ -320,81 +526,234 @@ function nowIso() {
|
|
|
320
526
|
return (/* @__PURE__ */ new Date()).toISOString();
|
|
321
527
|
}
|
|
322
528
|
|
|
323
|
-
// src/
|
|
324
|
-
var warnedMissingQuizLesson = false;
|
|
325
|
-
function isDevEnvironment2() {
|
|
326
|
-
const g = globalThis;
|
|
327
|
-
return typeof g.process !== "undefined" && g.process.env?.NODE_ENV !== "production";
|
|
328
|
-
}
|
|
329
|
-
function resetTelemetryBuilderWarningsForTests() {
|
|
330
|
-
warnedMissingQuizLesson = false;
|
|
331
|
-
}
|
|
529
|
+
// src/telemetry/eventRegistry.ts
|
|
332
530
|
function resolveLessonId(opts, eventName) {
|
|
333
531
|
const lessonId = opts.lessonId ?? opts.data?.lessonId;
|
|
334
532
|
if (!lessonId) throw new Error(`${eventName} requires lessonId`);
|
|
335
533
|
return lessonId;
|
|
336
534
|
}
|
|
337
|
-
function
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
535
|
+
function withLessonScopedData(name, base, lessonId, data) {
|
|
536
|
+
return { name, ...base, lessonId, data: { ...data, lessonId } };
|
|
537
|
+
}
|
|
538
|
+
var TELEMETRY_EVENT_REGISTRY = {
|
|
539
|
+
course_started: {
|
|
540
|
+
build: (_opts, base) => ({ name: "course_started", ...base })
|
|
541
|
+
},
|
|
542
|
+
course_completed: {
|
|
543
|
+
build: (_opts, base) => ({ name: "course_completed", ...base })
|
|
544
|
+
},
|
|
545
|
+
lesson_started: {
|
|
546
|
+
requiresLessonId: true,
|
|
547
|
+
build: (opts, base) => {
|
|
548
|
+
if (opts.name !== "lesson_started") throw new Error("unexpected event");
|
|
351
549
|
const lessonId = resolveLessonId(opts, "lesson_started");
|
|
550
|
+
return withLessonScopedData("lesson_started", base, lessonId, opts.data);
|
|
551
|
+
}
|
|
552
|
+
},
|
|
553
|
+
lesson_completed: {
|
|
554
|
+
requiresLessonId: true,
|
|
555
|
+
build: (opts, base) => {
|
|
556
|
+
if (opts.name !== "lesson_completed") throw new Error("unexpected event");
|
|
557
|
+
const lessonId = resolveLessonId(opts, opts.name);
|
|
558
|
+
return withLessonScopedData(opts.name, base, lessonId, opts.data);
|
|
559
|
+
}
|
|
560
|
+
},
|
|
561
|
+
lesson_time_on_task: {
|
|
562
|
+
requiresLessonId: true,
|
|
563
|
+
build: (opts, base) => {
|
|
564
|
+
if (opts.name !== "lesson_time_on_task") throw new Error("unexpected event");
|
|
565
|
+
const lessonId = resolveLessonId(opts, opts.name);
|
|
566
|
+
return withLessonScopedData(opts.name, base, lessonId, opts.data);
|
|
567
|
+
}
|
|
568
|
+
},
|
|
569
|
+
quiz_answered: {
|
|
570
|
+
requiresLessonId: true,
|
|
571
|
+
tryBuildMissingLessonWarning: "quiz",
|
|
572
|
+
build: (opts, base) => {
|
|
573
|
+
if (opts.name !== "quiz_answered") throw new Error("unexpected event");
|
|
574
|
+
const lessonId = opts.lessonId;
|
|
575
|
+
if (!lessonId) throw new Error("quiz_answered requires active lessonId");
|
|
352
576
|
return {
|
|
353
|
-
name: "
|
|
577
|
+
name: "quiz_answered",
|
|
354
578
|
...base,
|
|
355
579
|
lessonId,
|
|
356
|
-
data:
|
|
580
|
+
data: opts.data
|
|
357
581
|
};
|
|
358
582
|
}
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
583
|
+
},
|
|
584
|
+
quiz_completed: {
|
|
585
|
+
requiresLessonId: true,
|
|
586
|
+
tryBuildMissingLessonWarning: "quiz",
|
|
587
|
+
build: (opts, base) => {
|
|
588
|
+
if (opts.name !== "quiz_completed") throw new Error("unexpected event");
|
|
589
|
+
const lessonId = opts.lessonId;
|
|
590
|
+
if (!lessonId) throw new Error("quiz_completed requires active lessonId");
|
|
362
591
|
return {
|
|
363
|
-
name:
|
|
592
|
+
name: "quiz_completed",
|
|
364
593
|
...base,
|
|
365
594
|
lessonId,
|
|
366
|
-
data:
|
|
595
|
+
data: opts.data
|
|
367
596
|
};
|
|
368
597
|
}
|
|
369
|
-
|
|
598
|
+
},
|
|
599
|
+
assessment_answered: {
|
|
600
|
+
requiresLessonId: true,
|
|
601
|
+
tryBuildMissingLessonWarning: "assessment",
|
|
602
|
+
build: (opts, base) => {
|
|
603
|
+
if (opts.name !== "assessment_answered") throw new Error("unexpected event");
|
|
370
604
|
const lessonId = opts.lessonId;
|
|
371
|
-
if (!lessonId) throw new Error("
|
|
372
|
-
return {
|
|
605
|
+
if (!lessonId) throw new Error("assessment_answered requires active lessonId");
|
|
606
|
+
return {
|
|
607
|
+
name: "assessment_answered",
|
|
608
|
+
...base,
|
|
609
|
+
lessonId,
|
|
610
|
+
data: opts.data
|
|
611
|
+
};
|
|
373
612
|
}
|
|
374
|
-
|
|
613
|
+
},
|
|
614
|
+
assessment_completed: {
|
|
615
|
+
requiresLessonId: true,
|
|
616
|
+
tryBuildMissingLessonWarning: "assessment",
|
|
617
|
+
build: (opts, base) => {
|
|
618
|
+
if (opts.name !== "assessment_completed") throw new Error("unexpected event");
|
|
375
619
|
const lessonId = opts.lessonId;
|
|
376
|
-
if (!lessonId) throw new Error("
|
|
377
|
-
return {
|
|
620
|
+
if (!lessonId) throw new Error("assessment_completed requires active lessonId");
|
|
621
|
+
return {
|
|
622
|
+
name: "assessment_completed",
|
|
623
|
+
...base,
|
|
624
|
+
lessonId,
|
|
625
|
+
data: opts.data
|
|
626
|
+
};
|
|
378
627
|
}
|
|
379
|
-
|
|
628
|
+
},
|
|
629
|
+
interaction: {
|
|
630
|
+
build: (opts, base) => {
|
|
631
|
+
if (opts.name !== "interaction") throw new Error("unexpected event");
|
|
380
632
|
return {
|
|
381
633
|
name: "interaction",
|
|
382
634
|
...base,
|
|
383
635
|
lessonId: opts.lessonId,
|
|
384
636
|
data: opts.data
|
|
385
637
|
};
|
|
386
|
-
|
|
387
|
-
|
|
638
|
+
}
|
|
639
|
+
},
|
|
640
|
+
book_page_viewed: {
|
|
641
|
+
requiresLessonId: true,
|
|
642
|
+
build: (opts, base) => {
|
|
643
|
+
if (opts.name !== "book_page_viewed") throw new Error("unexpected event");
|
|
644
|
+
const lessonId = opts.lessonId;
|
|
645
|
+
if (!lessonId) throw new Error("book_page_viewed requires active lessonId");
|
|
646
|
+
return {
|
|
647
|
+
name: "book_page_viewed",
|
|
648
|
+
...base,
|
|
649
|
+
lessonId,
|
|
650
|
+
data: opts.data
|
|
651
|
+
};
|
|
652
|
+
}
|
|
653
|
+
},
|
|
654
|
+
compound_page_viewed: {
|
|
655
|
+
requiresLessonId: true,
|
|
656
|
+
build: (opts, base) => {
|
|
657
|
+
if (opts.name !== "compound_page_viewed") throw new Error("unexpected event");
|
|
658
|
+
const lessonId = opts.lessonId;
|
|
659
|
+
if (!lessonId) throw new Error("compound_page_viewed requires active lessonId");
|
|
660
|
+
return {
|
|
661
|
+
name: "compound_page_viewed",
|
|
662
|
+
...base,
|
|
663
|
+
lessonId,
|
|
664
|
+
data: opts.data
|
|
665
|
+
};
|
|
666
|
+
}
|
|
667
|
+
},
|
|
668
|
+
hotspot_opened: {
|
|
669
|
+
build: (opts, base) => {
|
|
670
|
+
if (opts.name !== "hotspot_opened") throw new Error("unexpected event");
|
|
671
|
+
return {
|
|
672
|
+
name: "hotspot_opened",
|
|
673
|
+
...base,
|
|
674
|
+
lessonId: opts.lessonId,
|
|
675
|
+
data: opts.data
|
|
676
|
+
};
|
|
677
|
+
}
|
|
678
|
+
},
|
|
679
|
+
accordion_section_toggled: {
|
|
680
|
+
build: (opts, base) => {
|
|
681
|
+
if (opts.name !== "accordion_section_toggled") throw new Error("unexpected event");
|
|
682
|
+
return {
|
|
683
|
+
name: "accordion_section_toggled",
|
|
684
|
+
...base,
|
|
685
|
+
lessonId: opts.lessonId,
|
|
686
|
+
data: opts.data
|
|
687
|
+
};
|
|
688
|
+
}
|
|
689
|
+
},
|
|
690
|
+
flashcard_flipped: {
|
|
691
|
+
build: (opts, base) => {
|
|
692
|
+
if (opts.name !== "flashcard_flipped") throw new Error("unexpected event");
|
|
693
|
+
return {
|
|
694
|
+
name: "flashcard_flipped",
|
|
695
|
+
...base,
|
|
696
|
+
lessonId: opts.lessonId,
|
|
697
|
+
data: opts.data
|
|
698
|
+
};
|
|
699
|
+
}
|
|
700
|
+
},
|
|
701
|
+
image_slider_changed: {
|
|
702
|
+
build: (opts, base) => {
|
|
703
|
+
if (opts.name !== "image_slider_changed") throw new Error("unexpected event");
|
|
704
|
+
return {
|
|
705
|
+
name: "image_slider_changed",
|
|
706
|
+
...base,
|
|
707
|
+
lessonId: opts.lessonId,
|
|
708
|
+
data: opts.data
|
|
709
|
+
};
|
|
710
|
+
}
|
|
388
711
|
}
|
|
712
|
+
};
|
|
713
|
+
function buildTelemetryEventFromRegistry(opts) {
|
|
714
|
+
const entry = TELEMETRY_EVENT_REGISTRY[opts.name];
|
|
715
|
+
if (!entry) {
|
|
716
|
+
throw new Error("Unexpected value");
|
|
717
|
+
}
|
|
718
|
+
const base = {
|
|
719
|
+
timestamp: opts.timestamp ?? nowIso(),
|
|
720
|
+
courseId: opts.courseId,
|
|
721
|
+
sessionId: opts.sessionId,
|
|
722
|
+
attemptId: opts.attemptId,
|
|
723
|
+
user: opts.user
|
|
724
|
+
};
|
|
725
|
+
return entry.build(opts, base);
|
|
726
|
+
}
|
|
727
|
+
function getTelemetryEventRegistryEntry(name) {
|
|
728
|
+
return TELEMETRY_EVENT_REGISTRY[name];
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
// src/telemetryBuilder.ts
|
|
732
|
+
var warnedMissingQuizLesson = false;
|
|
733
|
+
var warnedMissingAssessmentLesson = false;
|
|
734
|
+
function resetTelemetryBuilderWarningsForTests() {
|
|
735
|
+
warnedMissingQuizLesson = false;
|
|
736
|
+
warnedMissingAssessmentLesson = false;
|
|
737
|
+
}
|
|
738
|
+
function buildTelemetryEvent(opts) {
|
|
739
|
+
return buildTelemetryEventFromRegistry(opts);
|
|
389
740
|
}
|
|
390
741
|
function tryBuildTelemetryEvent(opts) {
|
|
391
|
-
const
|
|
392
|
-
if (
|
|
393
|
-
if (
|
|
394
|
-
warnedMissingQuizLesson
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
742
|
+
const entry = getTelemetryEventRegistryEntry(opts.name);
|
|
743
|
+
if (entry.requiresLessonId && !opts.lessonId && entry.tryBuildMissingLessonWarning) {
|
|
744
|
+
if (isDevEnvironment()) {
|
|
745
|
+
if (entry.tryBuildMissingLessonWarning === "quiz" && !warnedMissingQuizLesson) {
|
|
746
|
+
warnedMissingQuizLesson = true;
|
|
747
|
+
console.warn(
|
|
748
|
+
`[lessonkit] ${opts.name} skipped: wrap <Quiz> in <Lesson> so an active lessonId is available`
|
|
749
|
+
);
|
|
750
|
+
}
|
|
751
|
+
if (entry.tryBuildMissingLessonWarning === "assessment" && !warnedMissingAssessmentLesson) {
|
|
752
|
+
warnedMissingAssessmentLesson = true;
|
|
753
|
+
console.warn(
|
|
754
|
+
`[lessonkit] ${opts.name} skipped: wrap assessment blocks in <Lesson> so an active lessonId is available`
|
|
755
|
+
);
|
|
756
|
+
}
|
|
398
757
|
}
|
|
399
758
|
return null;
|
|
400
759
|
}
|
|
@@ -402,29 +761,8 @@ function tryBuildTelemetryEvent(opts) {
|
|
|
402
761
|
}
|
|
403
762
|
|
|
404
763
|
// src/telemetryPipeline.ts
|
|
405
|
-
function isDevEnvironment3() {
|
|
406
|
-
const g = globalThis;
|
|
407
|
-
return typeof g.process !== "undefined" && g.process.env?.NODE_ENV !== "production";
|
|
408
|
-
}
|
|
409
|
-
function warnSinkFailure(sinkId, err) {
|
|
410
|
-
if (isDevEnvironment3()) {
|
|
411
|
-
console.warn(
|
|
412
|
-
`[lessonkit] telemetry sink "${sinkId}" failed:`,
|
|
413
|
-
err instanceof Error ? err.message : err
|
|
414
|
-
);
|
|
415
|
-
}
|
|
416
|
-
}
|
|
417
764
|
function invokeSink(sink, event, emitCtx) {
|
|
418
|
-
|
|
419
|
-
try {
|
|
420
|
-
result = sink.emit(event, emitCtx);
|
|
421
|
-
} catch (err) {
|
|
422
|
-
warnSinkFailure(sink.id, err);
|
|
423
|
-
return;
|
|
424
|
-
}
|
|
425
|
-
if (result != null && typeof result.catch === "function") {
|
|
426
|
-
void result.catch((err) => warnSinkFailure(sink.id, err));
|
|
427
|
-
}
|
|
765
|
+
invokePipelineSink(sink.id, () => sink.emit(event, emitCtx));
|
|
428
766
|
}
|
|
429
767
|
function createTelemetryPipeline(sinks) {
|
|
430
768
|
const list = [...sinks];
|
|
@@ -700,101 +1038,13 @@ function completeCourseWithTelemetry(opts) {
|
|
|
700
1038
|
return true;
|
|
701
1039
|
}
|
|
702
1040
|
|
|
703
|
-
// src/
|
|
704
|
-
function
|
|
705
|
-
const storage = ports.storage ?? createSessionStoragePort();
|
|
706
|
-
const clock = ports.clock ?? createDefaultClock();
|
|
707
|
-
const configSnapshot = { ...config };
|
|
708
|
-
let sessionId = resolveSessionId(storage, configSnapshot.session?.sessionId);
|
|
709
|
-
let attemptId = configSnapshot.session?.attemptId;
|
|
710
|
-
let user = configSnapshot.session?.user;
|
|
711
|
-
let courseId = configSnapshot.courseId;
|
|
712
|
-
let progress = createProgressController();
|
|
713
|
-
const getSession = () => ({ sessionId, attemptId, user });
|
|
714
|
-
const syncSessionFromConfig = (next) => {
|
|
715
|
-
sessionId = resolveSessionId(storage, next.session?.sessionId);
|
|
716
|
-
attemptId = next.session?.attemptId;
|
|
717
|
-
user = next.session?.user;
|
|
718
|
-
courseId = next.courseId;
|
|
719
|
-
};
|
|
720
|
-
syncSessionFromConfig(configSnapshot);
|
|
721
|
-
const track = (name, data, emit, lessonId) => {
|
|
722
|
-
const event = tryBuildTelemetryEvent({
|
|
723
|
-
name,
|
|
724
|
-
courseId,
|
|
725
|
-
lessonId: lessonId ?? progress.getState().activeLessonId,
|
|
726
|
-
sessionId,
|
|
727
|
-
attemptId,
|
|
728
|
-
user,
|
|
729
|
-
data
|
|
730
|
-
});
|
|
731
|
-
if (!event) return;
|
|
732
|
-
emit(event);
|
|
733
|
-
};
|
|
734
|
-
const emitLessonCompleted = (lessonId, durationMs, emitFn) => {
|
|
735
|
-
emitFn("lesson_completed", { lessonId, durationMs }, lessonId);
|
|
736
|
-
if (durationMs !== void 0) {
|
|
737
|
-
emitFn("lesson_time_on_task", { lessonId, durationMs }, lessonId);
|
|
738
|
-
}
|
|
739
|
-
};
|
|
1041
|
+
// src/plugins/context.ts
|
|
1042
|
+
function buildPluginContext(opts) {
|
|
740
1043
|
return {
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
return progress;
|
|
746
|
-
},
|
|
747
|
-
getProgressState: () => progress.getState(),
|
|
748
|
-
getSession,
|
|
749
|
-
updateConfig(next) {
|
|
750
|
-
if (next.courseId !== void 0) configSnapshot.courseId = next.courseId;
|
|
751
|
-
if (next.runtimeVersion !== void 0) configSnapshot.runtimeVersion = next.runtimeVersion;
|
|
752
|
-
if (next.plugins !== void 0) configSnapshot.plugins = next.plugins;
|
|
753
|
-
if (next.session !== void 0) {
|
|
754
|
-
configSnapshot.session = { ...configSnapshot.session, ...next.session };
|
|
755
|
-
}
|
|
756
|
-
syncSessionFromConfig(configSnapshot);
|
|
757
|
-
},
|
|
758
|
-
setActiveLesson(lessonId, emitFn) {
|
|
759
|
-
const current = progress.getState();
|
|
760
|
-
if (current.activeLessonId === lessonId) return;
|
|
761
|
-
if (current.completedLessonIds.has(lessonId)) {
|
|
762
|
-
progress.setActiveLesson(lessonId, clock.nowMs());
|
|
763
|
-
return;
|
|
764
|
-
}
|
|
765
|
-
const previous = current.activeLessonId;
|
|
766
|
-
if (previous && previous !== lessonId) {
|
|
767
|
-
const completed = progress.completeLesson(previous, clock.nowMs());
|
|
768
|
-
if (completed.didComplete) {
|
|
769
|
-
emitLessonCompleted(previous, completed.durationMs, emitFn);
|
|
770
|
-
}
|
|
771
|
-
}
|
|
772
|
-
progress.setActiveLesson(lessonId, clock.nowMs());
|
|
773
|
-
emitFn("lesson_started", { lessonId }, lessonId);
|
|
774
|
-
},
|
|
775
|
-
completeLesson(lessonId, emitFn) {
|
|
776
|
-
const result = progress.completeLesson(lessonId, clock.nowMs());
|
|
777
|
-
if (!result.didComplete) return;
|
|
778
|
-
emitLessonCompleted(lessonId, result.durationMs, emitFn);
|
|
779
|
-
},
|
|
780
|
-
completeCourse(emitFn) {
|
|
781
|
-
const current = progress.getState();
|
|
782
|
-
if (current.activeLessonId) {
|
|
783
|
-
const lessonResult = progress.completeLesson(current.activeLessonId, clock.nowMs());
|
|
784
|
-
if (lessonResult.didComplete) {
|
|
785
|
-
emitLessonCompleted(current.activeLessonId, lessonResult.durationMs, emitFn);
|
|
786
|
-
}
|
|
787
|
-
}
|
|
788
|
-
const result = progress.completeCourse();
|
|
789
|
-
if (!result.didComplete) return;
|
|
790
|
-
emitFn("course_completed");
|
|
791
|
-
},
|
|
792
|
-
track,
|
|
793
|
-
resetForCourseChange(nextCourseId) {
|
|
794
|
-
configSnapshot.courseId = nextCourseId;
|
|
795
|
-
courseId = nextCourseId;
|
|
796
|
-
progress = createProgressController();
|
|
797
|
-
}
|
|
1044
|
+
courseId: opts.courseId,
|
|
1045
|
+
sessionId: opts.sessionId,
|
|
1046
|
+
attemptId: opts.attemptId,
|
|
1047
|
+
user: opts.user
|
|
798
1048
|
};
|
|
799
1049
|
}
|
|
800
1050
|
|
|
@@ -885,6 +1135,161 @@ function createPluginRegistry(plugins = []) {
|
|
|
885
1135
|
};
|
|
886
1136
|
}
|
|
887
1137
|
|
|
1138
|
+
// src/runtime/createLessonkitRuntime.ts
|
|
1139
|
+
function resolvePluginHost(plugins) {
|
|
1140
|
+
if (!plugins) return null;
|
|
1141
|
+
if (typeof plugins === "object" && "runTelemetry" in plugins) return plugins;
|
|
1142
|
+
if (Array.isArray(plugins) && plugins.length > 0) return createPluginRegistry(plugins);
|
|
1143
|
+
return null;
|
|
1144
|
+
}
|
|
1145
|
+
function warnRuntimeV1Deprecated() {
|
|
1146
|
+
const g = globalThis;
|
|
1147
|
+
if (typeof g.process !== "undefined" && g.process.env?.NODE_ENV === "production") return;
|
|
1148
|
+
console.warn(
|
|
1149
|
+
'[lessonkit] runtimeVersion "v1" is deprecated; use "v2" (default). v1 will be removed in LessonKit 2.0.'
|
|
1150
|
+
);
|
|
1151
|
+
}
|
|
1152
|
+
function createLessonkitRuntime(config, ports = {}) {
|
|
1153
|
+
if (config.runtimeVersion === "v1") warnRuntimeV1Deprecated();
|
|
1154
|
+
const storage = ports.storage ?? createSessionStoragePort();
|
|
1155
|
+
const clock = ports.clock ?? createDefaultClock();
|
|
1156
|
+
const configSnapshot = { ...config };
|
|
1157
|
+
let sessionId = resolveSessionId(storage, configSnapshot.session?.sessionId);
|
|
1158
|
+
let attemptId = configSnapshot.session?.attemptId;
|
|
1159
|
+
let user = configSnapshot.session?.user;
|
|
1160
|
+
let courseId = configSnapshot.courseId;
|
|
1161
|
+
let progress = createProgressController();
|
|
1162
|
+
let pluginHost = resolvePluginHost(configSnapshot.plugins);
|
|
1163
|
+
const getPluginCtx = () => buildPluginContext({
|
|
1164
|
+
courseId,
|
|
1165
|
+
sessionId,
|
|
1166
|
+
attemptId,
|
|
1167
|
+
user
|
|
1168
|
+
});
|
|
1169
|
+
const getSession = () => ({ sessionId, attemptId, user });
|
|
1170
|
+
const syncSessionFromConfig = (next) => {
|
|
1171
|
+
sessionId = resolveSessionId(storage, next.session?.sessionId);
|
|
1172
|
+
attemptId = next.session?.attemptId;
|
|
1173
|
+
user = next.session?.user;
|
|
1174
|
+
courseId = next.courseId;
|
|
1175
|
+
};
|
|
1176
|
+
const applyPluginsToEvent = (event) => {
|
|
1177
|
+
if (!pluginHost) return event;
|
|
1178
|
+
return pluginHost.runTelemetry(event, getPluginCtx());
|
|
1179
|
+
};
|
|
1180
|
+
const buildAndApply = (name, data, lessonId) => {
|
|
1181
|
+
const event = tryBuildTelemetryEvent({
|
|
1182
|
+
name,
|
|
1183
|
+
courseId,
|
|
1184
|
+
lessonId: lessonId ?? progress.getState().activeLessonId,
|
|
1185
|
+
sessionId,
|
|
1186
|
+
attemptId,
|
|
1187
|
+
user,
|
|
1188
|
+
data
|
|
1189
|
+
});
|
|
1190
|
+
if (!event) return null;
|
|
1191
|
+
return applyPluginsToEvent(event);
|
|
1192
|
+
};
|
|
1193
|
+
const wrapEmitFn = (emitFn) => {
|
|
1194
|
+
return (name, data, lessonId) => {
|
|
1195
|
+
const event = buildAndApply(name, data, lessonId);
|
|
1196
|
+
if (event === null) return;
|
|
1197
|
+
const eventLessonId = "lessonId" in event ? event.lessonId : lessonId;
|
|
1198
|
+
const eventData = "data" in event ? event.data : data;
|
|
1199
|
+
emitFn(event.name, eventData, eventLessonId);
|
|
1200
|
+
};
|
|
1201
|
+
};
|
|
1202
|
+
syncSessionFromConfig(configSnapshot);
|
|
1203
|
+
const track = (name, data, emit, lessonId) => {
|
|
1204
|
+
const event = buildAndApply(name, data, lessonId);
|
|
1205
|
+
if (!event) return;
|
|
1206
|
+
emit(event);
|
|
1207
|
+
};
|
|
1208
|
+
const emitLessonCompletedEvents = (lessonId, durationMs, emitFn) => {
|
|
1209
|
+
const wrapped = wrapEmitFn(emitFn);
|
|
1210
|
+
wrapped("lesson_completed", { lessonId, durationMs }, lessonId);
|
|
1211
|
+
if (durationMs !== void 0) {
|
|
1212
|
+
wrapped("lesson_time_on_task", { lessonId, durationMs }, lessonId);
|
|
1213
|
+
}
|
|
1214
|
+
};
|
|
1215
|
+
return {
|
|
1216
|
+
get config() {
|
|
1217
|
+
return configSnapshot;
|
|
1218
|
+
},
|
|
1219
|
+
get progress() {
|
|
1220
|
+
return progress;
|
|
1221
|
+
},
|
|
1222
|
+
get pluginHost() {
|
|
1223
|
+
return pluginHost;
|
|
1224
|
+
},
|
|
1225
|
+
getProgressState: () => progress.getState(),
|
|
1226
|
+
getSession,
|
|
1227
|
+
updateConfig(next) {
|
|
1228
|
+
if (next.plugins !== void 0 && next.plugins !== pluginHost) {
|
|
1229
|
+
pluginHost?.disposeAll();
|
|
1230
|
+
configSnapshot.plugins = next.plugins;
|
|
1231
|
+
pluginHost = resolvePluginHost(configSnapshot.plugins);
|
|
1232
|
+
}
|
|
1233
|
+
if (next.courseId !== void 0) configSnapshot.courseId = next.courseId;
|
|
1234
|
+
if (next.runtimeVersion !== void 0) {
|
|
1235
|
+
if (next.runtimeVersion === "v1") warnRuntimeV1Deprecated();
|
|
1236
|
+
configSnapshot.runtimeVersion = next.runtimeVersion;
|
|
1237
|
+
}
|
|
1238
|
+
if (next.session !== void 0) {
|
|
1239
|
+
configSnapshot.session = { ...configSnapshot.session, ...next.session };
|
|
1240
|
+
}
|
|
1241
|
+
syncSessionFromConfig(configSnapshot);
|
|
1242
|
+
},
|
|
1243
|
+
setActiveLesson(lessonId, emitFn) {
|
|
1244
|
+
const wrapped = wrapEmitFn(emitFn);
|
|
1245
|
+
const current = progress.getState();
|
|
1246
|
+
if (current.activeLessonId === lessonId) return;
|
|
1247
|
+
if (current.completedLessonIds.has(lessonId)) {
|
|
1248
|
+
progress.setActiveLesson(lessonId, clock.nowMs());
|
|
1249
|
+
return;
|
|
1250
|
+
}
|
|
1251
|
+
const previous = current.activeLessonId;
|
|
1252
|
+
if (previous && previous !== lessonId) {
|
|
1253
|
+
const completed = progress.completeLesson(previous, clock.nowMs());
|
|
1254
|
+
if (completed.didComplete) {
|
|
1255
|
+
emitLessonCompletedEvents(previous, completed.durationMs, wrapped);
|
|
1256
|
+
}
|
|
1257
|
+
}
|
|
1258
|
+
progress.setActiveLesson(lessonId, clock.nowMs());
|
|
1259
|
+
wrapped("lesson_started", { lessonId }, lessonId);
|
|
1260
|
+
},
|
|
1261
|
+
completeLesson(lessonId, emitFn) {
|
|
1262
|
+
completeLessonWithTelemetry({
|
|
1263
|
+
progress,
|
|
1264
|
+
lessonId,
|
|
1265
|
+
nowMs: clock.nowMs(),
|
|
1266
|
+
emitLessonCompleted: (id, durationMs) => emitLessonCompletedEvents(id, durationMs, wrapEmitFn(emitFn))
|
|
1267
|
+
});
|
|
1268
|
+
},
|
|
1269
|
+
completeCourse(emitFn) {
|
|
1270
|
+
completeCourseWithTelemetry({
|
|
1271
|
+
progress,
|
|
1272
|
+
nowMs: clock.nowMs(),
|
|
1273
|
+
emitLessonCompleted: (id, durationMs) => emitLessonCompletedEvents(id, durationMs, wrapEmitFn(emitFn)),
|
|
1274
|
+
emitCourseCompleted: () => wrapEmitFn(emitFn)("course_completed")
|
|
1275
|
+
});
|
|
1276
|
+
},
|
|
1277
|
+
track,
|
|
1278
|
+
scoreAssessment(input, _lessonId) {
|
|
1279
|
+
if (!pluginHost) return null;
|
|
1280
|
+
return pluginHost.scoreAssessment(input, getPluginCtx());
|
|
1281
|
+
},
|
|
1282
|
+
resetForCourseChange(nextCourseId) {
|
|
1283
|
+
configSnapshot.courseId = nextCourseId;
|
|
1284
|
+
courseId = nextCourseId;
|
|
1285
|
+
progress = createProgressController();
|
|
1286
|
+
},
|
|
1287
|
+
dispose() {
|
|
1288
|
+
pluginHost?.disposeAll();
|
|
1289
|
+
}
|
|
1290
|
+
};
|
|
1291
|
+
}
|
|
1292
|
+
|
|
888
1293
|
// src/plugins/define.ts
|
|
889
1294
|
function defineTelemetryPlugin(plugin) {
|
|
890
1295
|
return plugin;
|
|
@@ -896,18 +1301,33 @@ function defineLifecyclePlugin(plugin) {
|
|
|
896
1301
|
return plugin;
|
|
897
1302
|
}
|
|
898
1303
|
export {
|
|
1304
|
+
ACCORDION_FORBIDDEN_CHILD_TYPES,
|
|
1305
|
+
ASSESSMENT_SEQUENCE_ALLOWED_CHILD_TYPES,
|
|
1306
|
+
COMPOUND_MAX_NESTING_DEPTH,
|
|
1307
|
+
COMPOUND_RESUME_SCHEMA_VERSION,
|
|
899
1308
|
ID_MAX_LENGTH,
|
|
900
1309
|
ID_PATTERN,
|
|
1310
|
+
INTERACTIVE_BOOK_ALLOWED_CHILD_TYPES,
|
|
1311
|
+
PAGE_ALLOWED_CHILD_TYPES,
|
|
901
1312
|
SESSION_STORAGE_KEY,
|
|
902
1313
|
TELEMETRY_EVENT_CATALOG,
|
|
1314
|
+
TELEMETRY_EVENT_CATALOG_V2,
|
|
1315
|
+
TELEMETRY_EVENT_CATALOG_V3,
|
|
903
1316
|
assertNever,
|
|
904
1317
|
assertValidId,
|
|
905
1318
|
buildCourseStartedTelemetryEvent,
|
|
906
1319
|
buildLessonkitUrn,
|
|
1320
|
+
buildPluginContext,
|
|
907
1321
|
buildTelemetryCatalog,
|
|
1322
|
+
buildTelemetryCatalogV2,
|
|
1323
|
+
buildTelemetryCatalogV3,
|
|
908
1324
|
buildTelemetryEvent,
|
|
1325
|
+
clampCompoundPageIndex,
|
|
1326
|
+
clearCompoundState,
|
|
909
1327
|
completeCourseWithTelemetry,
|
|
910
1328
|
completeLessonWithTelemetry,
|
|
1329
|
+
compoundStateStorageKey,
|
|
1330
|
+
createCompoundResumeState,
|
|
911
1331
|
createDefaultClock,
|
|
912
1332
|
createGlobalTimer,
|
|
913
1333
|
createLessonkitRuntime,
|
|
@@ -923,10 +1343,13 @@ export {
|
|
|
923
1343
|
defineLifecyclePlugin,
|
|
924
1344
|
defineTelemetryPlugin,
|
|
925
1345
|
deriveId,
|
|
1346
|
+
getAllowedChildTypes,
|
|
926
1347
|
getTabSessionId,
|
|
927
1348
|
hasCourseStarted,
|
|
928
1349
|
hasCourseStartedEmittedToTracking,
|
|
929
1350
|
hasCourseStartedPipelineDelivered,
|
|
1351
|
+
isChildTypeAllowed,
|
|
1352
|
+
loadCompoundState,
|
|
930
1353
|
markCourseStarted,
|
|
931
1354
|
markCourseStartedEmittedToTracking,
|
|
932
1355
|
markCourseStartedPipelineDelivered,
|
|
@@ -934,12 +1357,16 @@ export {
|
|
|
934
1357
|
nowIso,
|
|
935
1358
|
parseBlockId,
|
|
936
1359
|
parseCheckId,
|
|
1360
|
+
parseCompoundResumeState,
|
|
937
1361
|
parseCourseId,
|
|
938
1362
|
parseLessonId,
|
|
939
1363
|
resetStoragePortForTests,
|
|
940
1364
|
resetTelemetryBuilderWarningsForTests,
|
|
941
1365
|
resolveSessionId,
|
|
1366
|
+
saveCompoundState,
|
|
942
1367
|
slugifyId,
|
|
1368
|
+
telemetryCatalogV2Version,
|
|
1369
|
+
telemetryCatalogV3Version,
|
|
943
1370
|
telemetryCatalogVersion,
|
|
944
1371
|
tryBuildTelemetryEvent,
|
|
945
1372
|
tryEmitCourseStarted,
|