@lessonkit/core 1.1.0 → 1.3.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 +786 -221
- package/dist/index.d.cts +215 -28
- package/dist/index.d.ts +215 -28
- package/dist/index.js +764 -221
- package/package.json +6 -2
- package/telemetry-catalog.v2.json +21 -0
- package/telemetry-catalog.v3.json +61 -0
package/dist/index.cjs
CHANGED
|
@@ -20,20 +20,35 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
20
20
|
// src/index.ts
|
|
21
21
|
var index_exports = {};
|
|
22
22
|
__export(index_exports, {
|
|
23
|
+
ACCORDION_FORBIDDEN_CHILD_TYPES: () => ACCORDION_FORBIDDEN_CHILD_TYPES,
|
|
24
|
+
ASSESSMENT_SEQUENCE_ALLOWED_CHILD_TYPES: () => ASSESSMENT_SEQUENCE_ALLOWED_CHILD_TYPES,
|
|
25
|
+
COMPOUND_MAX_NESTING_DEPTH: () => COMPOUND_MAX_NESTING_DEPTH,
|
|
26
|
+
COMPOUND_RESUME_SCHEMA_VERSION: () => COMPOUND_RESUME_SCHEMA_VERSION,
|
|
23
27
|
ID_MAX_LENGTH: () => ID_MAX_LENGTH,
|
|
24
28
|
ID_PATTERN: () => ID_PATTERN,
|
|
29
|
+
INTERACTIVE_BOOK_ALLOWED_CHILD_TYPES: () => INTERACTIVE_BOOK_ALLOWED_CHILD_TYPES,
|
|
30
|
+
PAGE_ALLOWED_CHILD_TYPES: () => PAGE_ALLOWED_CHILD_TYPES,
|
|
25
31
|
SESSION_STORAGE_KEY: () => SESSION_STORAGE_KEY,
|
|
32
|
+
SLIDE_ALLOWED_CHILD_TYPES: () => SLIDE_ALLOWED_CHILD_TYPES,
|
|
33
|
+
SLIDE_DECK_ALLOWED_CHILD_TYPES: () => SLIDE_DECK_ALLOWED_CHILD_TYPES,
|
|
26
34
|
TELEMETRY_EVENT_CATALOG: () => TELEMETRY_EVENT_CATALOG,
|
|
27
35
|
TELEMETRY_EVENT_CATALOG_V2: () => TELEMETRY_EVENT_CATALOG_V2,
|
|
36
|
+
TELEMETRY_EVENT_CATALOG_V3: () => TELEMETRY_EVENT_CATALOG_V3,
|
|
28
37
|
assertNever: () => assertNever,
|
|
29
38
|
assertValidId: () => assertValidId,
|
|
30
39
|
buildCourseStartedTelemetryEvent: () => buildCourseStartedTelemetryEvent,
|
|
31
40
|
buildLessonkitUrn: () => buildLessonkitUrn,
|
|
41
|
+
buildPluginContext: () => buildPluginContext,
|
|
32
42
|
buildTelemetryCatalog: () => buildTelemetryCatalog,
|
|
33
43
|
buildTelemetryCatalogV2: () => buildTelemetryCatalogV2,
|
|
44
|
+
buildTelemetryCatalogV3: () => buildTelemetryCatalogV3,
|
|
34
45
|
buildTelemetryEvent: () => buildTelemetryEvent,
|
|
46
|
+
clampCompoundPageIndex: () => clampCompoundPageIndex,
|
|
47
|
+
clearCompoundState: () => clearCompoundState,
|
|
35
48
|
completeCourseWithTelemetry: () => completeCourseWithTelemetry,
|
|
36
49
|
completeLessonWithTelemetry: () => completeLessonWithTelemetry,
|
|
50
|
+
compoundStateStorageKey: () => compoundStateStorageKey,
|
|
51
|
+
createCompoundResumeState: () => createCompoundResumeState,
|
|
37
52
|
createDefaultClock: () => createDefaultClock,
|
|
38
53
|
createGlobalTimer: () => createGlobalTimer,
|
|
39
54
|
createLessonkitRuntime: () => createLessonkitRuntime,
|
|
@@ -49,10 +64,13 @@ __export(index_exports, {
|
|
|
49
64
|
defineLifecyclePlugin: () => defineLifecyclePlugin,
|
|
50
65
|
defineTelemetryPlugin: () => defineTelemetryPlugin,
|
|
51
66
|
deriveId: () => deriveId,
|
|
67
|
+
getAllowedChildTypes: () => getAllowedChildTypes,
|
|
52
68
|
getTabSessionId: () => getTabSessionId,
|
|
53
69
|
hasCourseStarted: () => hasCourseStarted,
|
|
54
70
|
hasCourseStartedEmittedToTracking: () => hasCourseStartedEmittedToTracking,
|
|
55
71
|
hasCourseStartedPipelineDelivered: () => hasCourseStartedPipelineDelivered,
|
|
72
|
+
isChildTypeAllowed: () => isChildTypeAllowed,
|
|
73
|
+
loadCompoundState: () => loadCompoundState,
|
|
56
74
|
markCourseStarted: () => markCourseStarted,
|
|
57
75
|
markCourseStartedEmittedToTracking: () => markCourseStartedEmittedToTracking,
|
|
58
76
|
markCourseStartedPipelineDelivered: () => markCourseStartedPipelineDelivered,
|
|
@@ -60,13 +78,17 @@ __export(index_exports, {
|
|
|
60
78
|
nowIso: () => nowIso,
|
|
61
79
|
parseBlockId: () => parseBlockId,
|
|
62
80
|
parseCheckId: () => parseCheckId,
|
|
81
|
+
parseCompoundResumeState: () => parseCompoundResumeState,
|
|
63
82
|
parseCourseId: () => parseCourseId,
|
|
64
83
|
parseLessonId: () => parseLessonId,
|
|
84
|
+
resetSharedVolatileSessionIdForTests: () => resetSharedVolatileSessionIdForTests,
|
|
65
85
|
resetStoragePortForTests: () => resetStoragePortForTests,
|
|
66
86
|
resetTelemetryBuilderWarningsForTests: () => resetTelemetryBuilderWarningsForTests,
|
|
67
87
|
resolveSessionId: () => resolveSessionId,
|
|
88
|
+
saveCompoundState: () => saveCompoundState,
|
|
68
89
|
slugifyId: () => slugifyId,
|
|
69
90
|
telemetryCatalogV2Version: () => telemetryCatalogV2Version,
|
|
91
|
+
telemetryCatalogV3Version: () => telemetryCatalogV3Version,
|
|
70
92
|
telemetryCatalogVersion: () => telemetryCatalogVersion,
|
|
71
93
|
tryBuildTelemetryEvent: () => tryBuildTelemetryEvent,
|
|
72
94
|
tryEmitCourseStarted: () => tryEmitCourseStarted,
|
|
@@ -136,21 +158,40 @@ function assertValidId(input, path = "id") {
|
|
|
136
158
|
}
|
|
137
159
|
|
|
138
160
|
// src/slugify.ts
|
|
161
|
+
function shortHash(input) {
|
|
162
|
+
let h = 0;
|
|
163
|
+
for (let i = 0; i < input.length; i++) {
|
|
164
|
+
h = Math.imul(31, h) + input.charCodeAt(i) >>> 0;
|
|
165
|
+
}
|
|
166
|
+
return h.toString(36);
|
|
167
|
+
}
|
|
168
|
+
function uniqueFallbackId(input, usedIds) {
|
|
169
|
+
const hash = shortHash(input);
|
|
170
|
+
for (let n = 0; n < 100; n++) {
|
|
171
|
+
const candidate = (n === 0 ? `id-${hash}` : `id-${hash}-${n}`).slice(0, 64);
|
|
172
|
+
const validated2 = validateId(candidate);
|
|
173
|
+
if (validated2.ok && !usedIds.has(validated2.id)) return validated2.id;
|
|
174
|
+
}
|
|
175
|
+
const timed = `id-${hash}-${Date.now().toString(36)}`.slice(0, 64);
|
|
176
|
+
const validated = validateId(timed);
|
|
177
|
+
return validated.ok ? validated.id : `id-${hash}`;
|
|
178
|
+
}
|
|
139
179
|
function slugifyId(input) {
|
|
140
180
|
const slug = input.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").replace(/-+/g, "-").slice(0, 64);
|
|
141
|
-
if (!slug.length) return
|
|
142
|
-
const candidate = /^[a-z]/.test(slug) ? slug : `id-${slug}
|
|
181
|
+
if (!slug.length) return uniqueFallbackId(input, /* @__PURE__ */ new Set());
|
|
182
|
+
const candidate = /^[a-z]/.test(slug) ? slug : `id-${slug}`.slice(0, 64);
|
|
143
183
|
const validated = validateId(candidate);
|
|
144
|
-
return validated.ok ? validated.id :
|
|
184
|
+
return validated.ok ? validated.id : uniqueFallbackId(input, /* @__PURE__ */ new Set());
|
|
145
185
|
}
|
|
146
186
|
function deriveId(title, usedIds = /* @__PURE__ */ new Set()) {
|
|
147
187
|
const base = slugifyId(title);
|
|
148
|
-
if (!usedIds.has(base)) return base;
|
|
188
|
+
if (!usedIds.has(base) && validateId(base).ok) return base;
|
|
149
189
|
for (let n = 2; n < 1e3; n++) {
|
|
150
|
-
const candidate = `${base}-${n}
|
|
151
|
-
|
|
190
|
+
const candidate = `${base}-${n}`.slice(0, 64);
|
|
191
|
+
const validated = validateId(candidate);
|
|
192
|
+
if (validated.ok && !usedIds.has(validated.id)) return validated.id;
|
|
152
193
|
}
|
|
153
|
-
return `${
|
|
194
|
+
return uniqueFallbackId(`${title}-${Date.now()}`, usedIds);
|
|
154
195
|
}
|
|
155
196
|
|
|
156
197
|
// src/urn.ts
|
|
@@ -178,6 +219,187 @@ function buildLessonkitUrn(parts) {
|
|
|
178
219
|
return urn;
|
|
179
220
|
}
|
|
180
221
|
|
|
222
|
+
// src/compound.ts
|
|
223
|
+
var COMPOUND_RESUME_SCHEMA_VERSION = 1;
|
|
224
|
+
function createCompoundResumeState(input = {}) {
|
|
225
|
+
const childStates = {};
|
|
226
|
+
if (input.childStates) {
|
|
227
|
+
for (const [key, value] of Object.entries(input.childStates)) {
|
|
228
|
+
childStates[key] = value;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
return {
|
|
232
|
+
schemaVersion: COMPOUND_RESUME_SCHEMA_VERSION,
|
|
233
|
+
activePageIndex: input.activePageIndex ?? 0,
|
|
234
|
+
...input.activeChapterIndex !== void 0 ? { activeChapterIndex: input.activeChapterIndex } : {},
|
|
235
|
+
childStates
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
function clampCompoundPageIndex(index, pageCount) {
|
|
239
|
+
if (pageCount < 1) return 0;
|
|
240
|
+
return Math.min(Math.max(0, Math.floor(index)), pageCount - 1);
|
|
241
|
+
}
|
|
242
|
+
function isJsonPrimitive(value) {
|
|
243
|
+
return value === null || typeof value === "boolean" || typeof value === "string" || typeof value === "number" && Number.isFinite(value);
|
|
244
|
+
}
|
|
245
|
+
function isPlainStringKeyMap(value) {
|
|
246
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
247
|
+
return Object.entries(value).every(
|
|
248
|
+
([key, entry]) => typeof key === "string" && isJsonPrimitive(entry)
|
|
249
|
+
);
|
|
250
|
+
}
|
|
251
|
+
function isValidChildResumeValue(value) {
|
|
252
|
+
if (isJsonPrimitive(value)) return true;
|
|
253
|
+
if (Array.isArray(value)) return value.every((item) => isJsonPrimitive(item));
|
|
254
|
+
if (isPlainStringKeyMap(value)) return true;
|
|
255
|
+
return false;
|
|
256
|
+
}
|
|
257
|
+
function isPlainSerializableChildState(value) {
|
|
258
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
259
|
+
return Object.values(value).every(
|
|
260
|
+
(entry) => isValidChildResumeValue(entry)
|
|
261
|
+
);
|
|
262
|
+
}
|
|
263
|
+
function parseCompoundResumeState(raw) {
|
|
264
|
+
if (!raw || typeof raw !== "object") return null;
|
|
265
|
+
const obj = raw;
|
|
266
|
+
if (obj.schemaVersion !== COMPOUND_RESUME_SCHEMA_VERSION) return null;
|
|
267
|
+
if (typeof obj.activePageIndex !== "number" || !Number.isFinite(obj.activePageIndex)) return null;
|
|
268
|
+
const childStates = {};
|
|
269
|
+
if (obj.childStates && typeof obj.childStates === "object" && !Array.isArray(obj.childStates)) {
|
|
270
|
+
for (const [key, value] of Object.entries(obj.childStates)) {
|
|
271
|
+
if (isPlainSerializableChildState(value)) {
|
|
272
|
+
childStates[key] = value;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
const activeChapterIndex = typeof obj.activeChapterIndex === "number" && Number.isFinite(obj.activeChapterIndex) ? obj.activeChapterIndex : void 0;
|
|
277
|
+
return {
|
|
278
|
+
schemaVersion: COMPOUND_RESUME_SCHEMA_VERSION,
|
|
279
|
+
activePageIndex: Math.max(0, Math.floor(obj.activePageIndex)),
|
|
280
|
+
...activeChapterIndex !== void 0 ? { activeChapterIndex: Math.max(0, Math.floor(activeChapterIndex)) } : {},
|
|
281
|
+
childStates
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// src/internal/env.ts
|
|
286
|
+
function isDevEnvironment() {
|
|
287
|
+
const g = globalThis;
|
|
288
|
+
return typeof g.process !== "undefined" && g.process.env?.NODE_ENV !== "production";
|
|
289
|
+
}
|
|
290
|
+
function warnDev(message, err) {
|
|
291
|
+
if (!isDevEnvironment()) return;
|
|
292
|
+
console.warn(message, err instanceof Error ? err.message : err);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// src/compoundState.ts
|
|
296
|
+
var COMPOUND_STATE_PREFIX = "lessonkit:compound:";
|
|
297
|
+
function compoundStateStorageKey(courseId, compoundId) {
|
|
298
|
+
return `${COMPOUND_STATE_PREFIX}${courseId}:${compoundId}`;
|
|
299
|
+
}
|
|
300
|
+
function loadCompoundState(storage, courseId, compoundId) {
|
|
301
|
+
const key = compoundStateStorageKey(courseId, compoundId);
|
|
302
|
+
const raw = storage.getItem(key);
|
|
303
|
+
if (!raw) return null;
|
|
304
|
+
try {
|
|
305
|
+
const parsed = parseCompoundResumeState(JSON.parse(raw));
|
|
306
|
+
if (parsed === null && isDevEnvironment()) {
|
|
307
|
+
console.warn(`[lessonkit] Ignoring corrupt compound resume state at ${key}`);
|
|
308
|
+
}
|
|
309
|
+
return parsed;
|
|
310
|
+
} catch {
|
|
311
|
+
if (isDevEnvironment()) {
|
|
312
|
+
console.warn(`[lessonkit] Ignoring corrupt compound resume state at ${key}`);
|
|
313
|
+
}
|
|
314
|
+
return null;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
function saveCompoundState(storage, courseId, compoundId, state) {
|
|
318
|
+
return storage.setItem(compoundStateStorageKey(courseId, compoundId), JSON.stringify(state));
|
|
319
|
+
}
|
|
320
|
+
function clearCompoundState(storage, courseId, compoundId) {
|
|
321
|
+
storage.removeItem?.(compoundStateStorageKey(courseId, compoundId));
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// src/compoundAllowlists.ts
|
|
325
|
+
var PAGE_ALLOWED_CHILD_TYPES = [
|
|
326
|
+
"Text",
|
|
327
|
+
"Heading",
|
|
328
|
+
"Image",
|
|
329
|
+
"Scenario",
|
|
330
|
+
"Reflection",
|
|
331
|
+
"Quiz",
|
|
332
|
+
"KnowledgeCheck",
|
|
333
|
+
"TrueFalse",
|
|
334
|
+
"FillInTheBlanks",
|
|
335
|
+
"DragAndDrop",
|
|
336
|
+
"DragTheWords",
|
|
337
|
+
"MarkTheWords",
|
|
338
|
+
"Accordion",
|
|
339
|
+
"DialogCards",
|
|
340
|
+
"Flashcards",
|
|
341
|
+
"ImageHotspots",
|
|
342
|
+
"FindHotspot",
|
|
343
|
+
"FindMultipleHotspots",
|
|
344
|
+
"ImageSlider",
|
|
345
|
+
"ProgressTracker"
|
|
346
|
+
];
|
|
347
|
+
var INTERACTIVE_BOOK_ALLOWED_CHILD_TYPES = ["Page"];
|
|
348
|
+
var SLIDE_ALLOWED_CHILD_TYPES = [
|
|
349
|
+
"Text",
|
|
350
|
+
"Heading",
|
|
351
|
+
"Image",
|
|
352
|
+
"Scenario",
|
|
353
|
+
"Reflection",
|
|
354
|
+
"Quiz",
|
|
355
|
+
"KnowledgeCheck",
|
|
356
|
+
"TrueFalse",
|
|
357
|
+
"FillInTheBlanks",
|
|
358
|
+
"DragAndDrop",
|
|
359
|
+
"DragTheWords",
|
|
360
|
+
"MarkTheWords",
|
|
361
|
+
"Accordion",
|
|
362
|
+
"DialogCards",
|
|
363
|
+
"Flashcards",
|
|
364
|
+
"ImageHotspots",
|
|
365
|
+
"FindHotspot",
|
|
366
|
+
"FindMultipleHotspots",
|
|
367
|
+
"ImageSlider"
|
|
368
|
+
];
|
|
369
|
+
var SLIDE_DECK_ALLOWED_CHILD_TYPES = ["Slide"];
|
|
370
|
+
var ASSESSMENT_SEQUENCE_ALLOWED_CHILD_TYPES = [
|
|
371
|
+
"TrueFalse",
|
|
372
|
+
"FillInTheBlanks",
|
|
373
|
+
"DragAndDrop",
|
|
374
|
+
"DragTheWords",
|
|
375
|
+
"MarkTheWords",
|
|
376
|
+
"Quiz",
|
|
377
|
+
"KnowledgeCheck",
|
|
378
|
+
"FindHotspot",
|
|
379
|
+
"FindMultipleHotspots"
|
|
380
|
+
];
|
|
381
|
+
var ALLOWLISTS = {
|
|
382
|
+
Page: PAGE_ALLOWED_CHILD_TYPES,
|
|
383
|
+
InteractiveBook: INTERACTIVE_BOOK_ALLOWED_CHILD_TYPES,
|
|
384
|
+
Slide: SLIDE_ALLOWED_CHILD_TYPES,
|
|
385
|
+
SlideDeck: SLIDE_DECK_ALLOWED_CHILD_TYPES,
|
|
386
|
+
AssessmentSequence: ASSESSMENT_SEQUENCE_ALLOWED_CHILD_TYPES
|
|
387
|
+
};
|
|
388
|
+
var COMPOUND_MAX_NESTING_DEPTH = {
|
|
389
|
+
Page: 1,
|
|
390
|
+
InteractiveBook: 2,
|
|
391
|
+
Slide: 1,
|
|
392
|
+
SlideDeck: 2,
|
|
393
|
+
AssessmentSequence: 1
|
|
394
|
+
};
|
|
395
|
+
function getAllowedChildTypes(parent) {
|
|
396
|
+
return ALLOWLISTS[parent];
|
|
397
|
+
}
|
|
398
|
+
function isChildTypeAllowed(parent, childType) {
|
|
399
|
+
return ALLOWLISTS[parent].includes(childType);
|
|
400
|
+
}
|
|
401
|
+
var ACCORDION_FORBIDDEN_CHILD_TYPES = ["Accordion"];
|
|
402
|
+
|
|
181
403
|
// src/telemetryCatalog.ts
|
|
182
404
|
var telemetryCatalogVersion = 1;
|
|
183
405
|
var TELEMETRY_EVENT_CATALOG = [
|
|
@@ -274,35 +496,99 @@ function buildTelemetryCatalogV2() {
|
|
|
274
496
|
return TELEMETRY_EVENT_CATALOG_V2.map((entry) => ({ ...entry }));
|
|
275
497
|
}
|
|
276
498
|
|
|
277
|
-
// src/
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
499
|
+
// src/telemetryCatalogV3.ts
|
|
500
|
+
var telemetryCatalogV3Version = 3;
|
|
501
|
+
var TELEMETRY_EVENT_CATALOG_V3 = [
|
|
502
|
+
{
|
|
503
|
+
name: "book_page_viewed",
|
|
504
|
+
description: "Learner viewed a page/chapter in an Interactive Book",
|
|
505
|
+
requiredFields: ["courseId", "lessonId", "sessionId", "timestamp"],
|
|
506
|
+
dataFields: ["blockId", "pageIndex", "pageTitle"],
|
|
507
|
+
xapiVerb: "http://adlnet.gov/expapi/verbs/experienced",
|
|
508
|
+
urnPattern: "urn:lessonkit:course:{courseId}:lesson:{lessonId}:block:{blockId}"
|
|
509
|
+
},
|
|
510
|
+
{
|
|
511
|
+
name: "slide_viewed",
|
|
512
|
+
description: "Learner viewed a slide in a SlideDeck (Course Presentation)",
|
|
513
|
+
requiredFields: ["courseId", "lessonId", "sessionId", "timestamp"],
|
|
514
|
+
dataFields: ["blockId", "slideIndex", "slideTitle"],
|
|
515
|
+
xapiVerb: "http://adlnet.gov/expapi/verbs/experienced",
|
|
516
|
+
urnPattern: "urn:lessonkit:course:{courseId}:lesson:{lessonId}:block:{blockId}"
|
|
517
|
+
},
|
|
518
|
+
{
|
|
519
|
+
name: "compound_page_viewed",
|
|
520
|
+
description: "Learner activated a page inside a compound container",
|
|
521
|
+
requiredFields: ["courseId", "lessonId", "sessionId", "timestamp"],
|
|
522
|
+
dataFields: ["blockId", "pageIndex", "parentType"],
|
|
523
|
+
xapiVerb: "http://adlnet.gov/expapi/verbs/experienced",
|
|
524
|
+
urnPattern: "urn:lessonkit:course:{courseId}:lesson:{lessonId}:block:{blockId}"
|
|
525
|
+
},
|
|
526
|
+
{
|
|
527
|
+
name: "hotspot_opened",
|
|
528
|
+
description: "Learner opened an image hotspot popover",
|
|
529
|
+
requiredFields: ["courseId", "sessionId", "timestamp"],
|
|
530
|
+
dataFields: ["blockId", "hotspotId"],
|
|
531
|
+
xapiVerb: "http://adlnet.gov/expapi/verbs/experienced",
|
|
532
|
+
urnPattern: "urn:lessonkit:course:{courseId}:lesson:{lessonId}:block:{blockId}"
|
|
533
|
+
},
|
|
534
|
+
{
|
|
535
|
+
name: "accordion_section_toggled",
|
|
536
|
+
description: "Learner expanded or collapsed an accordion section",
|
|
537
|
+
requiredFields: ["courseId", "sessionId", "timestamp"],
|
|
538
|
+
dataFields: ["blockId", "sectionId", "expanded"],
|
|
539
|
+
xapiVerb: "http://adlnet.gov/expapi/verbs/experienced",
|
|
540
|
+
urnPattern: "urn:lessonkit:course:{courseId}:lesson:{lessonId}:block:{blockId}"
|
|
541
|
+
},
|
|
542
|
+
{
|
|
543
|
+
name: "flashcard_flipped",
|
|
544
|
+
description: "Learner flipped a flashcard",
|
|
545
|
+
requiredFields: ["courseId", "sessionId", "timestamp"],
|
|
546
|
+
dataFields: ["blockId", "cardIndex", "face"],
|
|
547
|
+
xapiVerb: "http://adlnet.gov/expapi/verbs/experienced",
|
|
548
|
+
urnPattern: "urn:lessonkit:course:{courseId}:lesson:{lessonId}:block:{blockId}"
|
|
549
|
+
},
|
|
550
|
+
{
|
|
551
|
+
name: "image_slider_changed",
|
|
552
|
+
description: "Learner changed the active slide in an image slider",
|
|
553
|
+
requiredFields: ["courseId", "sessionId", "timestamp"],
|
|
554
|
+
dataFields: ["blockId", "slideIndex"],
|
|
555
|
+
xapiVerb: "http://adlnet.gov/expapi/verbs/experienced",
|
|
556
|
+
urnPattern: "urn:lessonkit:course:{courseId}:lesson:{lessonId}:block:{blockId}"
|
|
557
|
+
}
|
|
558
|
+
];
|
|
559
|
+
function buildTelemetryCatalogV3() {
|
|
560
|
+
return TELEMETRY_EVENT_CATALOG_V3.map((entry) => ({ ...entry }));
|
|
281
561
|
}
|
|
562
|
+
|
|
563
|
+
// src/internal/sinkInvoke.ts
|
|
282
564
|
function invokeTrackingSink(sink, event) {
|
|
283
565
|
let result;
|
|
284
566
|
try {
|
|
285
567
|
result = sink(event);
|
|
286
568
|
} catch (err) {
|
|
287
|
-
|
|
288
|
-
console.warn(
|
|
289
|
-
"[lessonkit] tracking sink failed:",
|
|
290
|
-
err instanceof Error ? err.message : err
|
|
291
|
-
);
|
|
292
|
-
}
|
|
569
|
+
warnDev("[lessonkit] tracking sink failed:", err);
|
|
293
570
|
throw err;
|
|
294
571
|
}
|
|
295
572
|
if (result != null && typeof result.catch === "function") {
|
|
296
|
-
void result.catch((err) =>
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
573
|
+
void result.catch((err) => warnDev("[lessonkit] tracking sink failed:", err));
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
function invokePipelineSink(sinkId, emit) {
|
|
577
|
+
let result;
|
|
578
|
+
try {
|
|
579
|
+
result = emit();
|
|
580
|
+
} catch (err) {
|
|
581
|
+
warnDev(`[lessonkit] telemetry sink "${sinkId}" failed:`, err);
|
|
582
|
+
return;
|
|
583
|
+
}
|
|
584
|
+
if (result != null && typeof result.catch === "function") {
|
|
585
|
+
void result.catch(
|
|
586
|
+
(err) => warnDev(`[lessonkit] telemetry sink "${sinkId}" failed:`, err)
|
|
587
|
+
);
|
|
304
588
|
}
|
|
305
589
|
}
|
|
590
|
+
|
|
591
|
+
// src/trackingClient.ts
|
|
306
592
|
function createTrackingClient(opts) {
|
|
307
593
|
const sink = opts?.sink;
|
|
308
594
|
const batchSink = opts?.batchSink;
|
|
@@ -338,41 +624,58 @@ function createTrackingClient(opts) {
|
|
|
338
624
|
let disposing = false;
|
|
339
625
|
let intervalId;
|
|
340
626
|
const runFlush = () => {
|
|
341
|
-
if (!buffer.length) return Promise.resolve();
|
|
627
|
+
if (!buffer.length) return Promise.resolve(true);
|
|
342
628
|
const events = buffer.splice(0, buffer.length);
|
|
343
|
-
let sent = 0;
|
|
344
629
|
let succeeded = false;
|
|
345
630
|
return Promise.resolve().then(async () => {
|
|
346
631
|
if (batchSink) {
|
|
347
632
|
await batchSink(events);
|
|
348
633
|
} else {
|
|
349
|
-
for (
|
|
350
|
-
|
|
351
|
-
|
|
634
|
+
for (let i = 0; i < events.length; i++) {
|
|
635
|
+
try {
|
|
636
|
+
await sink?.(events[i]);
|
|
637
|
+
} catch {
|
|
638
|
+
buffer.unshift(...events.slice(i));
|
|
639
|
+
return;
|
|
640
|
+
}
|
|
352
641
|
}
|
|
353
642
|
}
|
|
354
643
|
succeeded = true;
|
|
355
644
|
}).catch(() => {
|
|
356
|
-
|
|
357
|
-
|
|
645
|
+
if (batchSink) {
|
|
646
|
+
buffer.unshift(...events);
|
|
647
|
+
}
|
|
648
|
+
}).then(async () => {
|
|
358
649
|
if (succeeded && buffer.length > 0 && !disposed) {
|
|
359
650
|
return runFlush();
|
|
360
651
|
}
|
|
652
|
+
return succeeded;
|
|
361
653
|
});
|
|
362
654
|
};
|
|
363
655
|
const flush = () => {
|
|
364
|
-
if (disposed) return Promise.resolve();
|
|
656
|
+
if (disposed) return Promise.resolve(true);
|
|
365
657
|
if (flushInFlight) return flushInFlight;
|
|
366
|
-
if (!buffer.length) return Promise.resolve();
|
|
658
|
+
if (!buffer.length) return Promise.resolve(true);
|
|
367
659
|
flushInFlight = runFlush().finally(() => {
|
|
368
660
|
flushInFlight = null;
|
|
369
661
|
});
|
|
370
662
|
return flushInFlight;
|
|
371
663
|
};
|
|
664
|
+
const MAX_DISPOSE_FLUSH_ATTEMPTS = 10;
|
|
372
665
|
const drainAll = async () => {
|
|
373
|
-
|
|
374
|
-
while (buffer.length > 0) {
|
|
375
|
-
await flush();
|
|
666
|
+
let attempts = 0;
|
|
667
|
+
while (buffer.length > 0 && attempts < MAX_DISPOSE_FLUSH_ATTEMPTS) {
|
|
668
|
+
const delivered = await flush();
|
|
669
|
+
attempts += 1;
|
|
670
|
+
if (!delivered) break;
|
|
671
|
+
}
|
|
672
|
+
if (buffer.length > 0) {
|
|
673
|
+
if (isDevEnvironment()) {
|
|
674
|
+
console.warn(
|
|
675
|
+
`[lessonkit] dropped ${buffer.length} buffered telemetry event(s) after dispose flush cap`
|
|
676
|
+
);
|
|
677
|
+
}
|
|
678
|
+
buffer.length = 0;
|
|
376
679
|
}
|
|
377
680
|
};
|
|
378
681
|
intervalId = flushIntervalMs > 0 ? globalThis.setInterval(() => void flush(), flushIntervalMs) : void 0;
|
|
@@ -381,13 +684,14 @@ function createTrackingClient(opts) {
|
|
|
381
684
|
track: (event) => {
|
|
382
685
|
if (disposed || disposing) return;
|
|
383
686
|
if (buffer.length >= maxBufferSize) {
|
|
384
|
-
|
|
687
|
+
opts?.onBufferDrop?.();
|
|
385
688
|
if (!warnedBufferCap && isDevEnvironment()) {
|
|
386
689
|
warnedBufferCap = true;
|
|
387
690
|
console.warn(
|
|
388
|
-
`[lessonkit] telemetry batch buffer capped at ${maxBufferSize} events;
|
|
691
|
+
`[lessonkit] telemetry batch buffer capped at ${maxBufferSize} events; new events are dropped until the buffer drains.`
|
|
389
692
|
);
|
|
390
693
|
}
|
|
694
|
+
return;
|
|
391
695
|
}
|
|
392
696
|
buffer.push(event);
|
|
393
697
|
if (buffer.length >= maxBatchSize) void flush();
|
|
@@ -420,96 +724,243 @@ function nowIso() {
|
|
|
420
724
|
return (/* @__PURE__ */ new Date()).toISOString();
|
|
421
725
|
}
|
|
422
726
|
|
|
423
|
-
// src/
|
|
424
|
-
var warnedMissingQuizLesson = false;
|
|
425
|
-
var warnedMissingAssessmentLesson = false;
|
|
426
|
-
function isDevEnvironment2() {
|
|
427
|
-
const g = globalThis;
|
|
428
|
-
return typeof g.process !== "undefined" && g.process.env?.NODE_ENV !== "production";
|
|
429
|
-
}
|
|
430
|
-
function resetTelemetryBuilderWarningsForTests() {
|
|
431
|
-
warnedMissingQuizLesson = false;
|
|
432
|
-
warnedMissingAssessmentLesson = false;
|
|
433
|
-
}
|
|
727
|
+
// src/telemetry/eventRegistry.ts
|
|
434
728
|
function resolveLessonId(opts, eventName) {
|
|
435
729
|
const lessonId = opts.lessonId ?? opts.data?.lessonId;
|
|
436
730
|
if (!lessonId) throw new Error(`${eventName} requires lessonId`);
|
|
437
731
|
return lessonId;
|
|
438
732
|
}
|
|
439
|
-
function
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
733
|
+
function withLessonScopedData(name, base, lessonId, data) {
|
|
734
|
+
return { name, ...base, lessonId, data: { ...data, lessonId } };
|
|
735
|
+
}
|
|
736
|
+
var TELEMETRY_EVENT_REGISTRY = {
|
|
737
|
+
course_started: {
|
|
738
|
+
build: (_opts, base) => ({ name: "course_started", ...base })
|
|
739
|
+
},
|
|
740
|
+
course_completed: {
|
|
741
|
+
build: (_opts, base) => ({ name: "course_completed", ...base })
|
|
742
|
+
},
|
|
743
|
+
lesson_started: {
|
|
744
|
+
requiresLessonId: true,
|
|
745
|
+
build: (opts, base) => {
|
|
746
|
+
if (opts.name !== "lesson_started") throw new Error("unexpected event");
|
|
453
747
|
const lessonId = resolveLessonId(opts, "lesson_started");
|
|
748
|
+
return withLessonScopedData("lesson_started", base, lessonId, opts.data);
|
|
749
|
+
}
|
|
750
|
+
},
|
|
751
|
+
lesson_completed: {
|
|
752
|
+
requiresLessonId: true,
|
|
753
|
+
build: (opts, base) => {
|
|
754
|
+
if (opts.name !== "lesson_completed") throw new Error("unexpected event");
|
|
755
|
+
const lessonId = resolveLessonId(opts, opts.name);
|
|
756
|
+
return withLessonScopedData(opts.name, base, lessonId, opts.data);
|
|
757
|
+
}
|
|
758
|
+
},
|
|
759
|
+
lesson_time_on_task: {
|
|
760
|
+
requiresLessonId: true,
|
|
761
|
+
build: (opts, base) => {
|
|
762
|
+
if (opts.name !== "lesson_time_on_task") throw new Error("unexpected event");
|
|
763
|
+
const lessonId = resolveLessonId(opts, opts.name);
|
|
764
|
+
return withLessonScopedData(opts.name, base, lessonId, opts.data);
|
|
765
|
+
}
|
|
766
|
+
},
|
|
767
|
+
quiz_answered: {
|
|
768
|
+
requiresLessonId: true,
|
|
769
|
+
tryBuildMissingLessonWarning: "quiz",
|
|
770
|
+
build: (opts, base) => {
|
|
771
|
+
if (opts.name !== "quiz_answered") throw new Error("unexpected event");
|
|
772
|
+
const lessonId = opts.lessonId;
|
|
773
|
+
if (!lessonId) throw new Error("quiz_answered requires active lessonId");
|
|
454
774
|
return {
|
|
455
|
-
name: "
|
|
775
|
+
name: "quiz_answered",
|
|
456
776
|
...base,
|
|
457
777
|
lessonId,
|
|
458
|
-
data:
|
|
778
|
+
data: opts.data
|
|
459
779
|
};
|
|
460
780
|
}
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
781
|
+
},
|
|
782
|
+
quiz_completed: {
|
|
783
|
+
requiresLessonId: true,
|
|
784
|
+
tryBuildMissingLessonWarning: "quiz",
|
|
785
|
+
build: (opts, base) => {
|
|
786
|
+
if (opts.name !== "quiz_completed") throw new Error("unexpected event");
|
|
787
|
+
const lessonId = opts.lessonId;
|
|
788
|
+
if (!lessonId) throw new Error("quiz_completed requires active lessonId");
|
|
464
789
|
return {
|
|
465
|
-
name:
|
|
790
|
+
name: "quiz_completed",
|
|
466
791
|
...base,
|
|
467
792
|
lessonId,
|
|
468
|
-
data:
|
|
793
|
+
data: opts.data
|
|
469
794
|
};
|
|
470
795
|
}
|
|
471
|
-
|
|
796
|
+
},
|
|
797
|
+
assessment_answered: {
|
|
798
|
+
requiresLessonId: true,
|
|
799
|
+
tryBuildMissingLessonWarning: "assessment",
|
|
800
|
+
build: (opts, base) => {
|
|
801
|
+
if (opts.name !== "assessment_answered") throw new Error("unexpected event");
|
|
472
802
|
const lessonId = opts.lessonId;
|
|
473
|
-
if (!lessonId) throw new Error("
|
|
474
|
-
return {
|
|
803
|
+
if (!lessonId) throw new Error("assessment_answered requires active lessonId");
|
|
804
|
+
return {
|
|
805
|
+
name: "assessment_answered",
|
|
806
|
+
...base,
|
|
807
|
+
lessonId,
|
|
808
|
+
data: opts.data
|
|
809
|
+
};
|
|
475
810
|
}
|
|
476
|
-
|
|
811
|
+
},
|
|
812
|
+
assessment_completed: {
|
|
813
|
+
requiresLessonId: true,
|
|
814
|
+
tryBuildMissingLessonWarning: "assessment",
|
|
815
|
+
build: (opts, base) => {
|
|
816
|
+
if (opts.name !== "assessment_completed") throw new Error("unexpected event");
|
|
477
817
|
const lessonId = opts.lessonId;
|
|
478
|
-
if (!lessonId) throw new Error("
|
|
479
|
-
return {
|
|
818
|
+
if (!lessonId) throw new Error("assessment_completed requires active lessonId");
|
|
819
|
+
return {
|
|
820
|
+
name: "assessment_completed",
|
|
821
|
+
...base,
|
|
822
|
+
lessonId,
|
|
823
|
+
data: opts.data
|
|
824
|
+
};
|
|
825
|
+
}
|
|
826
|
+
},
|
|
827
|
+
interaction: {
|
|
828
|
+
build: (opts, base) => {
|
|
829
|
+
if (opts.name !== "interaction") throw new Error("unexpected event");
|
|
830
|
+
return {
|
|
831
|
+
name: "interaction",
|
|
832
|
+
...base,
|
|
833
|
+
lessonId: opts.lessonId,
|
|
834
|
+
data: opts.data
|
|
835
|
+
};
|
|
480
836
|
}
|
|
481
|
-
|
|
837
|
+
},
|
|
838
|
+
book_page_viewed: {
|
|
839
|
+
requiresLessonId: true,
|
|
840
|
+
build: (opts, base) => {
|
|
841
|
+
if (opts.name !== "book_page_viewed") throw new Error("unexpected event");
|
|
482
842
|
const lessonId = opts.lessonId;
|
|
483
|
-
if (!lessonId) throw new Error("
|
|
484
|
-
return {
|
|
843
|
+
if (!lessonId) throw new Error("book_page_viewed requires active lessonId");
|
|
844
|
+
return {
|
|
845
|
+
name: "book_page_viewed",
|
|
846
|
+
...base,
|
|
847
|
+
lessonId,
|
|
848
|
+
data: opts.data
|
|
849
|
+
};
|
|
485
850
|
}
|
|
486
|
-
|
|
851
|
+
},
|
|
852
|
+
slide_viewed: {
|
|
853
|
+
requiresLessonId: true,
|
|
854
|
+
build: (opts, base) => {
|
|
855
|
+
if (opts.name !== "slide_viewed") throw new Error("unexpected event");
|
|
487
856
|
const lessonId = opts.lessonId;
|
|
488
|
-
if (!lessonId) throw new Error("
|
|
489
|
-
return {
|
|
857
|
+
if (!lessonId) throw new Error("slide_viewed requires active lessonId");
|
|
858
|
+
return {
|
|
859
|
+
name: "slide_viewed",
|
|
860
|
+
...base,
|
|
861
|
+
lessonId,
|
|
862
|
+
data: opts.data
|
|
863
|
+
};
|
|
490
864
|
}
|
|
491
|
-
|
|
865
|
+
},
|
|
866
|
+
compound_page_viewed: {
|
|
867
|
+
requiresLessonId: true,
|
|
868
|
+
build: (opts, base) => {
|
|
869
|
+
if (opts.name !== "compound_page_viewed") throw new Error("unexpected event");
|
|
870
|
+
const lessonId = opts.lessonId;
|
|
871
|
+
if (!lessonId) throw new Error("compound_page_viewed requires active lessonId");
|
|
492
872
|
return {
|
|
493
|
-
name: "
|
|
873
|
+
name: "compound_page_viewed",
|
|
874
|
+
...base,
|
|
875
|
+
lessonId,
|
|
876
|
+
data: opts.data
|
|
877
|
+
};
|
|
878
|
+
}
|
|
879
|
+
},
|
|
880
|
+
hotspot_opened: {
|
|
881
|
+
build: (opts, base) => {
|
|
882
|
+
if (opts.name !== "hotspot_opened") throw new Error("unexpected event");
|
|
883
|
+
return {
|
|
884
|
+
name: "hotspot_opened",
|
|
885
|
+
...base,
|
|
886
|
+
lessonId: opts.lessonId,
|
|
887
|
+
data: opts.data
|
|
888
|
+
};
|
|
889
|
+
}
|
|
890
|
+
},
|
|
891
|
+
accordion_section_toggled: {
|
|
892
|
+
build: (opts, base) => {
|
|
893
|
+
if (opts.name !== "accordion_section_toggled") throw new Error("unexpected event");
|
|
894
|
+
return {
|
|
895
|
+
name: "accordion_section_toggled",
|
|
896
|
+
...base,
|
|
897
|
+
lessonId: opts.lessonId,
|
|
898
|
+
data: opts.data
|
|
899
|
+
};
|
|
900
|
+
}
|
|
901
|
+
},
|
|
902
|
+
flashcard_flipped: {
|
|
903
|
+
build: (opts, base) => {
|
|
904
|
+
if (opts.name !== "flashcard_flipped") throw new Error("unexpected event");
|
|
905
|
+
return {
|
|
906
|
+
name: "flashcard_flipped",
|
|
907
|
+
...base,
|
|
908
|
+
lessonId: opts.lessonId,
|
|
909
|
+
data: opts.data
|
|
910
|
+
};
|
|
911
|
+
}
|
|
912
|
+
},
|
|
913
|
+
image_slider_changed: {
|
|
914
|
+
build: (opts, base) => {
|
|
915
|
+
if (opts.name !== "image_slider_changed") throw new Error("unexpected event");
|
|
916
|
+
return {
|
|
917
|
+
name: "image_slider_changed",
|
|
494
918
|
...base,
|
|
495
919
|
lessonId: opts.lessonId,
|
|
496
920
|
data: opts.data
|
|
497
921
|
};
|
|
498
|
-
|
|
499
|
-
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
};
|
|
925
|
+
function buildTelemetryEventFromRegistry(opts) {
|
|
926
|
+
const entry = TELEMETRY_EVENT_REGISTRY[opts.name];
|
|
927
|
+
if (!entry) {
|
|
928
|
+
throw new Error("Unexpected value");
|
|
500
929
|
}
|
|
930
|
+
const base = {
|
|
931
|
+
timestamp: opts.timestamp ?? nowIso(),
|
|
932
|
+
courseId: opts.courseId,
|
|
933
|
+
sessionId: opts.sessionId,
|
|
934
|
+
attemptId: opts.attemptId,
|
|
935
|
+
user: opts.user
|
|
936
|
+
};
|
|
937
|
+
return entry.build(opts, base);
|
|
938
|
+
}
|
|
939
|
+
function getTelemetryEventRegistryEntry(name) {
|
|
940
|
+
return TELEMETRY_EVENT_REGISTRY[name];
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
// src/telemetryBuilder.ts
|
|
944
|
+
var warnedMissingQuizLesson = false;
|
|
945
|
+
var warnedMissingAssessmentLesson = false;
|
|
946
|
+
function resetTelemetryBuilderWarningsForTests() {
|
|
947
|
+
warnedMissingQuizLesson = false;
|
|
948
|
+
warnedMissingAssessmentLesson = false;
|
|
949
|
+
}
|
|
950
|
+
function buildTelemetryEvent(opts) {
|
|
951
|
+
return buildTelemetryEventFromRegistry(opts);
|
|
501
952
|
}
|
|
502
953
|
function tryBuildTelemetryEvent(opts) {
|
|
503
|
-
const
|
|
504
|
-
if (
|
|
505
|
-
if (
|
|
506
|
-
if (
|
|
954
|
+
const entry = getTelemetryEventRegistryEntry(opts.name);
|
|
955
|
+
if (entry.requiresLessonId && !opts.lessonId && entry.tryBuildMissingLessonWarning) {
|
|
956
|
+
if (isDevEnvironment()) {
|
|
957
|
+
if (entry.tryBuildMissingLessonWarning === "quiz" && !warnedMissingQuizLesson) {
|
|
507
958
|
warnedMissingQuizLesson = true;
|
|
508
959
|
console.warn(
|
|
509
960
|
`[lessonkit] ${opts.name} skipped: wrap <Quiz> in <Lesson> so an active lessonId is available`
|
|
510
961
|
);
|
|
511
962
|
}
|
|
512
|
-
if (
|
|
963
|
+
if (entry.tryBuildMissingLessonWarning === "assessment" && !warnedMissingAssessmentLesson) {
|
|
513
964
|
warnedMissingAssessmentLesson = true;
|
|
514
965
|
console.warn(
|
|
515
966
|
`[lessonkit] ${opts.name} skipped: wrap assessment blocks in <Lesson> so an active lessonId is available`
|
|
@@ -522,29 +973,8 @@ function tryBuildTelemetryEvent(opts) {
|
|
|
522
973
|
}
|
|
523
974
|
|
|
524
975
|
// src/telemetryPipeline.ts
|
|
525
|
-
function isDevEnvironment3() {
|
|
526
|
-
const g = globalThis;
|
|
527
|
-
return typeof g.process !== "undefined" && g.process.env?.NODE_ENV !== "production";
|
|
528
|
-
}
|
|
529
|
-
function warnSinkFailure(sinkId, err) {
|
|
530
|
-
if (isDevEnvironment3()) {
|
|
531
|
-
console.warn(
|
|
532
|
-
`[lessonkit] telemetry sink "${sinkId}" failed:`,
|
|
533
|
-
err instanceof Error ? err.message : err
|
|
534
|
-
);
|
|
535
|
-
}
|
|
536
|
-
}
|
|
537
976
|
function invokeSink(sink, event, emitCtx) {
|
|
538
|
-
|
|
539
|
-
try {
|
|
540
|
-
result = sink.emit(event, emitCtx);
|
|
541
|
-
} catch (err) {
|
|
542
|
-
warnSinkFailure(sink.id, err);
|
|
543
|
-
return;
|
|
544
|
-
}
|
|
545
|
-
if (result != null && typeof result.catch === "function") {
|
|
546
|
-
void result.catch((err) => warnSinkFailure(sink.id, err));
|
|
547
|
-
}
|
|
977
|
+
invokePipelineSink(sink.id, () => sink.emit(event, emitCtx));
|
|
548
978
|
}
|
|
549
979
|
function createTelemetryPipeline(sinks) {
|
|
550
980
|
const list = [...sinks];
|
|
@@ -581,8 +1011,7 @@ function createDefaultClock() {
|
|
|
581
1011
|
function createNoopStorage() {
|
|
582
1012
|
return {
|
|
583
1013
|
getItem: () => null,
|
|
584
|
-
setItem: () =>
|
|
585
|
-
}
|
|
1014
|
+
setItem: () => true
|
|
586
1015
|
};
|
|
587
1016
|
}
|
|
588
1017
|
function createMemoryBackedSessionStorage(session) {
|
|
@@ -613,8 +1042,10 @@ function createMemoryBackedSessionStorage(session) {
|
|
|
613
1042
|
memory.set(key, value);
|
|
614
1043
|
try {
|
|
615
1044
|
session.setItem(key, value);
|
|
1045
|
+
return true;
|
|
616
1046
|
} catch {
|
|
617
1047
|
warnPersistFailure();
|
|
1048
|
+
return false;
|
|
618
1049
|
}
|
|
619
1050
|
},
|
|
620
1051
|
removeItem: (key) => {
|
|
@@ -639,6 +1070,7 @@ function createInMemorySessionStoragePort() {
|
|
|
639
1070
|
getItem: (key) => memory.get(key) ?? null,
|
|
640
1071
|
setItem: (key, value) => {
|
|
641
1072
|
memory.set(key, value);
|
|
1073
|
+
return true;
|
|
642
1074
|
},
|
|
643
1075
|
removeItem: (key) => {
|
|
644
1076
|
memory.delete(key);
|
|
@@ -691,7 +1123,12 @@ function createProgressController() {
|
|
|
691
1123
|
return { previousLessonId };
|
|
692
1124
|
},
|
|
693
1125
|
completeLesson: (lessonId, completedAtMs) => {
|
|
694
|
-
if (completedLessonIds.has(lessonId))
|
|
1126
|
+
if (completedLessonIds.has(lessonId)) {
|
|
1127
|
+
if (activeLessonId === lessonId) {
|
|
1128
|
+
activeLessonId = void 0;
|
|
1129
|
+
}
|
|
1130
|
+
return { didComplete: false };
|
|
1131
|
+
}
|
|
695
1132
|
completedLessonIds = new Set(completedLessonIds).add(lessonId);
|
|
696
1133
|
if (activeLessonId === lessonId) {
|
|
697
1134
|
activeLessonId = void 0;
|
|
@@ -711,6 +1148,12 @@ function createProgressController() {
|
|
|
711
1148
|
|
|
712
1149
|
// src/session.ts
|
|
713
1150
|
var SESSION_STORAGE_KEY = "lessonkit:sessionId";
|
|
1151
|
+
var volatileSessionIds = /* @__PURE__ */ new WeakMap();
|
|
1152
|
+
var sharedVolatileSessionId = null;
|
|
1153
|
+
function isDevEnvironment2() {
|
|
1154
|
+
const g = globalThis;
|
|
1155
|
+
return typeof g.process !== "undefined" && g.process.env?.NODE_ENV !== "production";
|
|
1156
|
+
}
|
|
714
1157
|
function getTabSessionId(storage) {
|
|
715
1158
|
return storage.getItem(SESSION_STORAGE_KEY);
|
|
716
1159
|
}
|
|
@@ -718,11 +1161,28 @@ var COURSE_STARTED_PREFIX = "lessonkit:course_started:";
|
|
|
718
1161
|
var COURSE_STARTED_TRACKING_PREFIX = "lessonkit:course_started_tracking:";
|
|
719
1162
|
var COURSE_STARTED_PIPELINE_PREFIX = "lessonkit:course_started_pipeline:";
|
|
720
1163
|
function resolveSessionId(storage, provided) {
|
|
721
|
-
if (provided)
|
|
1164
|
+
if (provided !== void 0) {
|
|
1165
|
+
const trimmed = provided.trim();
|
|
1166
|
+
if (trimmed.length > 0) return trimmed;
|
|
1167
|
+
}
|
|
722
1168
|
const existing = storage.getItem(SESSION_STORAGE_KEY);
|
|
723
1169
|
if (existing) return existing;
|
|
1170
|
+
const volatile = volatileSessionIds.get(storage);
|
|
1171
|
+
if (volatile) return volatile;
|
|
724
1172
|
const id = createSessionId();
|
|
725
|
-
storage.setItem(SESSION_STORAGE_KEY, id);
|
|
1173
|
+
const persisted = storage.setItem(SESSION_STORAGE_KEY, id);
|
|
1174
|
+
if (!persisted) {
|
|
1175
|
+
if (!sharedVolatileSessionId) {
|
|
1176
|
+
sharedVolatileSessionId = id;
|
|
1177
|
+
}
|
|
1178
|
+
volatileSessionIds.set(storage, sharedVolatileSessionId);
|
|
1179
|
+
if (isDevEnvironment2()) {
|
|
1180
|
+
console.warn(
|
|
1181
|
+
"[lessonkit] session id could not be persisted; reusing in-memory id for this tab."
|
|
1182
|
+
);
|
|
1183
|
+
}
|
|
1184
|
+
return sharedVolatileSessionId;
|
|
1185
|
+
}
|
|
726
1186
|
return id;
|
|
727
1187
|
}
|
|
728
1188
|
function courseStartedStorageKey(sessionId, courseId) {
|
|
@@ -739,8 +1199,8 @@ function hasCourseStarted(storage, sessionId, courseId) {
|
|
|
739
1199
|
return storage.getItem(courseStartedStorageKey(sessionId, courseId)) === "1";
|
|
740
1200
|
}
|
|
741
1201
|
function markCourseStarted(storage, sessionId, courseId) {
|
|
742
|
-
if (!courseId) return;
|
|
743
|
-
storage.setItem(courseStartedStorageKey(sessionId, courseId), "1");
|
|
1202
|
+
if (!courseId) return false;
|
|
1203
|
+
return storage.setItem(courseStartedStorageKey(sessionId, courseId), "1");
|
|
744
1204
|
}
|
|
745
1205
|
function hasCourseStartedEmittedToTracking(storage, sessionId, courseId) {
|
|
746
1206
|
if (!courseId) return false;
|
|
@@ -758,6 +1218,9 @@ function markCourseStartedPipelineDelivered(storage, sessionId, courseId) {
|
|
|
758
1218
|
if (!courseId) return;
|
|
759
1219
|
storage.setItem(courseStartedPipelineStorageKey(sessionId, courseId), "1");
|
|
760
1220
|
}
|
|
1221
|
+
function resetSharedVolatileSessionIdForTests() {
|
|
1222
|
+
sharedVolatileSessionId = null;
|
|
1223
|
+
}
|
|
761
1224
|
function migrateCourseStartedMark(storage, fromSessionId, toSessionId, courseId) {
|
|
762
1225
|
if (!courseId || fromSessionId === toSessionId) return;
|
|
763
1226
|
if (hasCourseStarted(storage, fromSessionId, courseId)) {
|
|
@@ -780,14 +1243,14 @@ function tryEmitCourseStarted(ctx, deps, alreadyEmittedToSink) {
|
|
|
780
1243
|
if (alreadyEmittedToSink) {
|
|
781
1244
|
return { emitted: true, marked };
|
|
782
1245
|
}
|
|
783
|
-
if (marked) {
|
|
784
|
-
return { emitted: false, marked: true };
|
|
785
|
-
}
|
|
786
1246
|
const emitted = deps.emitCourseStartedEvent(ctx);
|
|
787
|
-
if (emitted) {
|
|
1247
|
+
if (emitted && !marked) {
|
|
788
1248
|
markCourseStarted(ctx.storage, ctx.sessionId, ctx.courseId);
|
|
789
1249
|
}
|
|
790
|
-
return {
|
|
1250
|
+
return {
|
|
1251
|
+
emitted,
|
|
1252
|
+
marked: hasCourseStarted(ctx.storage, ctx.sessionId, ctx.courseId)
|
|
1253
|
+
};
|
|
791
1254
|
}
|
|
792
1255
|
function buildCourseStartedTelemetryEvent(ctx) {
|
|
793
1256
|
return buildTelemetryEvent({
|
|
@@ -820,101 +1283,13 @@ function completeCourseWithTelemetry(opts) {
|
|
|
820
1283
|
return true;
|
|
821
1284
|
}
|
|
822
1285
|
|
|
823
|
-
// src/
|
|
824
|
-
function
|
|
825
|
-
const storage = ports.storage ?? createSessionStoragePort();
|
|
826
|
-
const clock = ports.clock ?? createDefaultClock();
|
|
827
|
-
const configSnapshot = { ...config };
|
|
828
|
-
let sessionId = resolveSessionId(storage, configSnapshot.session?.sessionId);
|
|
829
|
-
let attemptId = configSnapshot.session?.attemptId;
|
|
830
|
-
let user = configSnapshot.session?.user;
|
|
831
|
-
let courseId = configSnapshot.courseId;
|
|
832
|
-
let progress = createProgressController();
|
|
833
|
-
const getSession = () => ({ sessionId, attemptId, user });
|
|
834
|
-
const syncSessionFromConfig = (next) => {
|
|
835
|
-
sessionId = resolveSessionId(storage, next.session?.sessionId);
|
|
836
|
-
attemptId = next.session?.attemptId;
|
|
837
|
-
user = next.session?.user;
|
|
838
|
-
courseId = next.courseId;
|
|
839
|
-
};
|
|
840
|
-
syncSessionFromConfig(configSnapshot);
|
|
841
|
-
const track = (name, data, emit, lessonId) => {
|
|
842
|
-
const event = tryBuildTelemetryEvent({
|
|
843
|
-
name,
|
|
844
|
-
courseId,
|
|
845
|
-
lessonId: lessonId ?? progress.getState().activeLessonId,
|
|
846
|
-
sessionId,
|
|
847
|
-
attemptId,
|
|
848
|
-
user,
|
|
849
|
-
data
|
|
850
|
-
});
|
|
851
|
-
if (!event) return;
|
|
852
|
-
emit(event);
|
|
853
|
-
};
|
|
854
|
-
const emitLessonCompleted = (lessonId, durationMs, emitFn) => {
|
|
855
|
-
emitFn("lesson_completed", { lessonId, durationMs }, lessonId);
|
|
856
|
-
if (durationMs !== void 0) {
|
|
857
|
-
emitFn("lesson_time_on_task", { lessonId, durationMs }, lessonId);
|
|
858
|
-
}
|
|
859
|
-
};
|
|
1286
|
+
// src/plugins/context.ts
|
|
1287
|
+
function buildPluginContext(opts) {
|
|
860
1288
|
return {
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
return progress;
|
|
866
|
-
},
|
|
867
|
-
getProgressState: () => progress.getState(),
|
|
868
|
-
getSession,
|
|
869
|
-
updateConfig(next) {
|
|
870
|
-
if (next.courseId !== void 0) configSnapshot.courseId = next.courseId;
|
|
871
|
-
if (next.runtimeVersion !== void 0) configSnapshot.runtimeVersion = next.runtimeVersion;
|
|
872
|
-
if (next.plugins !== void 0) configSnapshot.plugins = next.plugins;
|
|
873
|
-
if (next.session !== void 0) {
|
|
874
|
-
configSnapshot.session = { ...configSnapshot.session, ...next.session };
|
|
875
|
-
}
|
|
876
|
-
syncSessionFromConfig(configSnapshot);
|
|
877
|
-
},
|
|
878
|
-
setActiveLesson(lessonId, emitFn) {
|
|
879
|
-
const current = progress.getState();
|
|
880
|
-
if (current.activeLessonId === lessonId) return;
|
|
881
|
-
if (current.completedLessonIds.has(lessonId)) {
|
|
882
|
-
progress.setActiveLesson(lessonId, clock.nowMs());
|
|
883
|
-
return;
|
|
884
|
-
}
|
|
885
|
-
const previous = current.activeLessonId;
|
|
886
|
-
if (previous && previous !== lessonId) {
|
|
887
|
-
const completed = progress.completeLesson(previous, clock.nowMs());
|
|
888
|
-
if (completed.didComplete) {
|
|
889
|
-
emitLessonCompleted(previous, completed.durationMs, emitFn);
|
|
890
|
-
}
|
|
891
|
-
}
|
|
892
|
-
progress.setActiveLesson(lessonId, clock.nowMs());
|
|
893
|
-
emitFn("lesson_started", { lessonId }, lessonId);
|
|
894
|
-
},
|
|
895
|
-
completeLesson(lessonId, emitFn) {
|
|
896
|
-
const result = progress.completeLesson(lessonId, clock.nowMs());
|
|
897
|
-
if (!result.didComplete) return;
|
|
898
|
-
emitLessonCompleted(lessonId, result.durationMs, emitFn);
|
|
899
|
-
},
|
|
900
|
-
completeCourse(emitFn) {
|
|
901
|
-
const current = progress.getState();
|
|
902
|
-
if (current.activeLessonId) {
|
|
903
|
-
const lessonResult = progress.completeLesson(current.activeLessonId, clock.nowMs());
|
|
904
|
-
if (lessonResult.didComplete) {
|
|
905
|
-
emitLessonCompleted(current.activeLessonId, lessonResult.durationMs, emitFn);
|
|
906
|
-
}
|
|
907
|
-
}
|
|
908
|
-
const result = progress.completeCourse();
|
|
909
|
-
if (!result.didComplete) return;
|
|
910
|
-
emitFn("course_completed");
|
|
911
|
-
},
|
|
912
|
-
track,
|
|
913
|
-
resetForCourseChange(nextCourseId) {
|
|
914
|
-
configSnapshot.courseId = nextCourseId;
|
|
915
|
-
courseId = nextCourseId;
|
|
916
|
-
progress = createProgressController();
|
|
917
|
-
}
|
|
1289
|
+
courseId: opts.courseId,
|
|
1290
|
+
sessionId: opts.sessionId,
|
|
1291
|
+
attemptId: opts.attemptId,
|
|
1292
|
+
user: opts.user
|
|
918
1293
|
};
|
|
919
1294
|
}
|
|
920
1295
|
|
|
@@ -965,7 +1340,7 @@ function createPluginRegistry(plugins = []) {
|
|
|
965
1340
|
const composeTrackingSink = (sink, ctxSource) => {
|
|
966
1341
|
if (!sink) return void 0;
|
|
967
1342
|
const resolveCtx = () => typeof ctxSource === "function" ? ctxSource() : ctxSource;
|
|
968
|
-
const ctxKey = (ctx) => `${ctx.courseId}\0${ctx.sessionId ?? ""}\0${ctx.attemptId ?? ""}`;
|
|
1343
|
+
const ctxKey = (ctx) => `${ctx.courseId}\0${ctx.sessionId ?? ""}\0${ctx.attemptId ?? ""}\0${ctx.user?.id ?? ""}`;
|
|
969
1344
|
const layers = [];
|
|
970
1345
|
let composed = sink;
|
|
971
1346
|
for (const plugin of list) {
|
|
@@ -1005,6 +1380,174 @@ function createPluginRegistry(plugins = []) {
|
|
|
1005
1380
|
};
|
|
1006
1381
|
}
|
|
1007
1382
|
|
|
1383
|
+
// src/runtime/createLessonkitRuntime.ts
|
|
1384
|
+
function resolvePluginHost(plugins) {
|
|
1385
|
+
if (!plugins) return null;
|
|
1386
|
+
if (typeof plugins === "object" && "runTelemetry" in plugins) return plugins;
|
|
1387
|
+
if (Array.isArray(plugins) && plugins.length > 0) return createPluginRegistry(plugins);
|
|
1388
|
+
return null;
|
|
1389
|
+
}
|
|
1390
|
+
function warnRuntimeV1Deprecated() {
|
|
1391
|
+
const g = globalThis;
|
|
1392
|
+
if (typeof g.process !== "undefined" && g.process.env?.NODE_ENV === "production") return;
|
|
1393
|
+
console.warn(
|
|
1394
|
+
'[lessonkit] runtimeVersion "v1" is deprecated; use "v2" (default). v1 will be removed in LessonKit 2.0.'
|
|
1395
|
+
);
|
|
1396
|
+
}
|
|
1397
|
+
function createLessonkitRuntime(config, ports = {}) {
|
|
1398
|
+
if (config.runtimeVersion === "v1") warnRuntimeV1Deprecated();
|
|
1399
|
+
const storage = ports.storage ?? createSessionStoragePort();
|
|
1400
|
+
const clock = ports.clock ?? createDefaultClock();
|
|
1401
|
+
const configSnapshot = { ...config };
|
|
1402
|
+
let sessionId = resolveSessionId(storage, configSnapshot.session?.sessionId);
|
|
1403
|
+
let attemptId = configSnapshot.session?.attemptId;
|
|
1404
|
+
let user = configSnapshot.session?.user;
|
|
1405
|
+
let courseId = configSnapshot.courseId;
|
|
1406
|
+
let progress = createProgressController();
|
|
1407
|
+
let pluginHost = resolvePluginHost(configSnapshot.plugins);
|
|
1408
|
+
const getPluginCtx = () => buildPluginContext({
|
|
1409
|
+
courseId,
|
|
1410
|
+
sessionId,
|
|
1411
|
+
attemptId,
|
|
1412
|
+
user
|
|
1413
|
+
});
|
|
1414
|
+
if (!configSnapshot.deferPluginSetup) {
|
|
1415
|
+
pluginHost?.setupAll(getPluginCtx());
|
|
1416
|
+
}
|
|
1417
|
+
const getSession = () => ({ sessionId, attemptId, user });
|
|
1418
|
+
const syncSessionFromConfig = (next) => {
|
|
1419
|
+
sessionId = resolveSessionId(storage, next.session?.sessionId);
|
|
1420
|
+
attemptId = next.session?.attemptId;
|
|
1421
|
+
user = next.session?.user;
|
|
1422
|
+
courseId = next.courseId;
|
|
1423
|
+
};
|
|
1424
|
+
const applyPluginsToEvent = (event) => {
|
|
1425
|
+
if (!pluginHost) return event;
|
|
1426
|
+
return pluginHost.runTelemetry(event, getPluginCtx());
|
|
1427
|
+
};
|
|
1428
|
+
const buildAndApply = (name, data, lessonId) => {
|
|
1429
|
+
const event = tryBuildTelemetryEvent({
|
|
1430
|
+
name,
|
|
1431
|
+
courseId,
|
|
1432
|
+
lessonId: lessonId ?? progress.getState().activeLessonId,
|
|
1433
|
+
sessionId,
|
|
1434
|
+
attemptId,
|
|
1435
|
+
user,
|
|
1436
|
+
data
|
|
1437
|
+
});
|
|
1438
|
+
if (!event) return null;
|
|
1439
|
+
return applyPluginsToEvent(event);
|
|
1440
|
+
};
|
|
1441
|
+
const wrapEmitFn = (emitFn) => {
|
|
1442
|
+
return (name, data, lessonId) => {
|
|
1443
|
+
const event = buildAndApply(name, data, lessonId);
|
|
1444
|
+
if (event === null) return;
|
|
1445
|
+
const eventLessonId = "lessonId" in event ? event.lessonId : lessonId;
|
|
1446
|
+
const eventData = "data" in event ? event.data : data;
|
|
1447
|
+
emitFn(event.name, eventData, eventLessonId);
|
|
1448
|
+
};
|
|
1449
|
+
};
|
|
1450
|
+
syncSessionFromConfig(configSnapshot);
|
|
1451
|
+
const track = (name, data, emit, lessonId) => {
|
|
1452
|
+
const event = buildAndApply(name, data, lessonId);
|
|
1453
|
+
if (!event) return;
|
|
1454
|
+
emit(event);
|
|
1455
|
+
};
|
|
1456
|
+
const emitLessonCompletedEvents = (lessonId, durationMs, emitFn) => {
|
|
1457
|
+
const wrapped = wrapEmitFn(emitFn);
|
|
1458
|
+
wrapped("lesson_completed", { lessonId, durationMs }, lessonId);
|
|
1459
|
+
if (durationMs !== void 0) {
|
|
1460
|
+
wrapped("lesson_time_on_task", { lessonId, durationMs }, lessonId);
|
|
1461
|
+
}
|
|
1462
|
+
};
|
|
1463
|
+
return {
|
|
1464
|
+
get config() {
|
|
1465
|
+
return configSnapshot;
|
|
1466
|
+
},
|
|
1467
|
+
get progress() {
|
|
1468
|
+
return progress;
|
|
1469
|
+
},
|
|
1470
|
+
get pluginHost() {
|
|
1471
|
+
return pluginHost;
|
|
1472
|
+
},
|
|
1473
|
+
getProgressState: () => progress.getState(),
|
|
1474
|
+
getSession,
|
|
1475
|
+
updateConfig(next) {
|
|
1476
|
+
const previousCourseId = courseId;
|
|
1477
|
+
const sessionKeyBefore = JSON.stringify({ sessionId, attemptId, user });
|
|
1478
|
+
if (next.courseId !== void 0) configSnapshot.courseId = next.courseId;
|
|
1479
|
+
if (next.runtimeVersion !== void 0) {
|
|
1480
|
+
if (next.runtimeVersion === "v1") warnRuntimeV1Deprecated();
|
|
1481
|
+
configSnapshot.runtimeVersion = next.runtimeVersion;
|
|
1482
|
+
}
|
|
1483
|
+
if (next.session !== void 0) {
|
|
1484
|
+
configSnapshot.session = { ...configSnapshot.session, ...next.session };
|
|
1485
|
+
}
|
|
1486
|
+
syncSessionFromConfig(configSnapshot);
|
|
1487
|
+
const sessionKeyAfter = JSON.stringify({ sessionId, attemptId, user });
|
|
1488
|
+
if (next.courseId !== void 0 && next.courseId !== previousCourseId) {
|
|
1489
|
+
progress = createProgressController();
|
|
1490
|
+
}
|
|
1491
|
+
if (next.plugins !== void 0 && next.plugins !== pluginHost) {
|
|
1492
|
+
pluginHost?.disposeAll();
|
|
1493
|
+
configSnapshot.plugins = next.plugins;
|
|
1494
|
+
pluginHost = resolvePluginHost(configSnapshot.plugins);
|
|
1495
|
+
pluginHost?.setupAll(getPluginCtx());
|
|
1496
|
+
} else if (next.session !== void 0 && sessionKeyBefore !== sessionKeyAfter && pluginHost && !configSnapshot.deferPluginSetup) {
|
|
1497
|
+
pluginHost.disposeAll();
|
|
1498
|
+
pluginHost.setupAll(getPluginCtx());
|
|
1499
|
+
}
|
|
1500
|
+
},
|
|
1501
|
+
setActiveLesson(lessonId, emitFn) {
|
|
1502
|
+
const wrapped = wrapEmitFn(emitFn);
|
|
1503
|
+
const current = progress.getState();
|
|
1504
|
+
if (current.activeLessonId === lessonId) return;
|
|
1505
|
+
if (current.completedLessonIds.has(lessonId)) {
|
|
1506
|
+
progress.setActiveLesson(lessonId, clock.nowMs());
|
|
1507
|
+
return;
|
|
1508
|
+
}
|
|
1509
|
+
const previous = current.activeLessonId;
|
|
1510
|
+
if (previous && previous !== lessonId) {
|
|
1511
|
+
const completed = progress.completeLesson(previous, clock.nowMs());
|
|
1512
|
+
if (completed.didComplete) {
|
|
1513
|
+
emitLessonCompletedEvents(previous, completed.durationMs, wrapped);
|
|
1514
|
+
}
|
|
1515
|
+
}
|
|
1516
|
+
progress.setActiveLesson(lessonId, clock.nowMs());
|
|
1517
|
+
wrapped("lesson_started", { lessonId }, lessonId);
|
|
1518
|
+
},
|
|
1519
|
+
completeLesson(lessonId, emitFn) {
|
|
1520
|
+
completeLessonWithTelemetry({
|
|
1521
|
+
progress,
|
|
1522
|
+
lessonId,
|
|
1523
|
+
nowMs: clock.nowMs(),
|
|
1524
|
+
emitLessonCompleted: (id, durationMs) => emitLessonCompletedEvents(id, durationMs, wrapEmitFn(emitFn))
|
|
1525
|
+
});
|
|
1526
|
+
},
|
|
1527
|
+
completeCourse(emitFn) {
|
|
1528
|
+
completeCourseWithTelemetry({
|
|
1529
|
+
progress,
|
|
1530
|
+
nowMs: clock.nowMs(),
|
|
1531
|
+
emitLessonCompleted: (id, durationMs) => emitLessonCompletedEvents(id, durationMs, wrapEmitFn(emitFn)),
|
|
1532
|
+
emitCourseCompleted: () => wrapEmitFn(emitFn)("course_completed")
|
|
1533
|
+
});
|
|
1534
|
+
},
|
|
1535
|
+
track,
|
|
1536
|
+
scoreAssessment(input, _lessonId) {
|
|
1537
|
+
if (!pluginHost) return null;
|
|
1538
|
+
return pluginHost.scoreAssessment(input, getPluginCtx());
|
|
1539
|
+
},
|
|
1540
|
+
resetForCourseChange(nextCourseId) {
|
|
1541
|
+
configSnapshot.courseId = nextCourseId;
|
|
1542
|
+
courseId = nextCourseId;
|
|
1543
|
+
progress = createProgressController();
|
|
1544
|
+
},
|
|
1545
|
+
dispose() {
|
|
1546
|
+
pluginHost?.disposeAll();
|
|
1547
|
+
}
|
|
1548
|
+
};
|
|
1549
|
+
}
|
|
1550
|
+
|
|
1008
1551
|
// src/plugins/define.ts
|
|
1009
1552
|
function defineTelemetryPlugin(plugin) {
|
|
1010
1553
|
return plugin;
|
|
@@ -1017,20 +1560,35 @@ function defineLifecyclePlugin(plugin) {
|
|
|
1017
1560
|
}
|
|
1018
1561
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1019
1562
|
0 && (module.exports = {
|
|
1563
|
+
ACCORDION_FORBIDDEN_CHILD_TYPES,
|
|
1564
|
+
ASSESSMENT_SEQUENCE_ALLOWED_CHILD_TYPES,
|
|
1565
|
+
COMPOUND_MAX_NESTING_DEPTH,
|
|
1566
|
+
COMPOUND_RESUME_SCHEMA_VERSION,
|
|
1020
1567
|
ID_MAX_LENGTH,
|
|
1021
1568
|
ID_PATTERN,
|
|
1569
|
+
INTERACTIVE_BOOK_ALLOWED_CHILD_TYPES,
|
|
1570
|
+
PAGE_ALLOWED_CHILD_TYPES,
|
|
1022
1571
|
SESSION_STORAGE_KEY,
|
|
1572
|
+
SLIDE_ALLOWED_CHILD_TYPES,
|
|
1573
|
+
SLIDE_DECK_ALLOWED_CHILD_TYPES,
|
|
1023
1574
|
TELEMETRY_EVENT_CATALOG,
|
|
1024
1575
|
TELEMETRY_EVENT_CATALOG_V2,
|
|
1576
|
+
TELEMETRY_EVENT_CATALOG_V3,
|
|
1025
1577
|
assertNever,
|
|
1026
1578
|
assertValidId,
|
|
1027
1579
|
buildCourseStartedTelemetryEvent,
|
|
1028
1580
|
buildLessonkitUrn,
|
|
1581
|
+
buildPluginContext,
|
|
1029
1582
|
buildTelemetryCatalog,
|
|
1030
1583
|
buildTelemetryCatalogV2,
|
|
1584
|
+
buildTelemetryCatalogV3,
|
|
1031
1585
|
buildTelemetryEvent,
|
|
1586
|
+
clampCompoundPageIndex,
|
|
1587
|
+
clearCompoundState,
|
|
1032
1588
|
completeCourseWithTelemetry,
|
|
1033
1589
|
completeLessonWithTelemetry,
|
|
1590
|
+
compoundStateStorageKey,
|
|
1591
|
+
createCompoundResumeState,
|
|
1034
1592
|
createDefaultClock,
|
|
1035
1593
|
createGlobalTimer,
|
|
1036
1594
|
createLessonkitRuntime,
|
|
@@ -1046,10 +1604,13 @@ function defineLifecyclePlugin(plugin) {
|
|
|
1046
1604
|
defineLifecyclePlugin,
|
|
1047
1605
|
defineTelemetryPlugin,
|
|
1048
1606
|
deriveId,
|
|
1607
|
+
getAllowedChildTypes,
|
|
1049
1608
|
getTabSessionId,
|
|
1050
1609
|
hasCourseStarted,
|
|
1051
1610
|
hasCourseStartedEmittedToTracking,
|
|
1052
1611
|
hasCourseStartedPipelineDelivered,
|
|
1612
|
+
isChildTypeAllowed,
|
|
1613
|
+
loadCompoundState,
|
|
1053
1614
|
markCourseStarted,
|
|
1054
1615
|
markCourseStartedEmittedToTracking,
|
|
1055
1616
|
markCourseStartedPipelineDelivered,
|
|
@@ -1057,13 +1618,17 @@ function defineLifecyclePlugin(plugin) {
|
|
|
1057
1618
|
nowIso,
|
|
1058
1619
|
parseBlockId,
|
|
1059
1620
|
parseCheckId,
|
|
1621
|
+
parseCompoundResumeState,
|
|
1060
1622
|
parseCourseId,
|
|
1061
1623
|
parseLessonId,
|
|
1624
|
+
resetSharedVolatileSessionIdForTests,
|
|
1062
1625
|
resetStoragePortForTests,
|
|
1063
1626
|
resetTelemetryBuilderWarningsForTests,
|
|
1064
1627
|
resolveSessionId,
|
|
1628
|
+
saveCompoundState,
|
|
1065
1629
|
slugifyId,
|
|
1066
1630
|
telemetryCatalogV2Version,
|
|
1631
|
+
telemetryCatalogV3Version,
|
|
1067
1632
|
telemetryCatalogVersion,
|
|
1068
1633
|
tryBuildTelemetryEvent,
|
|
1069
1634
|
tryEmitCourseStarted,
|