@lessonkit/lxpack 1.5.0 → 1.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -0
- package/block-tree.v1.json +40 -0
- package/dist/bridge.cjs +77 -22
- package/dist/bridge.d.cts +34 -4
- package/dist/bridge.d.ts +34 -4
- package/dist/bridge.js +74 -23
- package/dist/index.cjs +1115 -120
- package/dist/index.d.cts +162 -1
- package/dist/index.d.ts +162 -1
- package/dist/index.js +1090 -104
- package/lessonkit-manifest.v1.json +6 -3
- package/lkcourse-format.v1.json +21 -0
- package/package.json +12 -6
package/README.md
CHANGED
|
@@ -50,6 +50,20 @@ Prefer the CLI: `lessonkit package --target scorm12` reads `lessonkit.json` and
|
|
|
50
50
|
| `single-spa` | One Vite SPA for the whole course (CLI default) |
|
|
51
51
|
| `per-lesson-spa` | One dist per lesson (advanced; see packaging reference) |
|
|
52
52
|
|
|
53
|
+
## Portable interchange (1.6.0)
|
|
54
|
+
|
|
55
|
+
Export a `.lkcourse` archive for team handoff (not LMS upload):
|
|
56
|
+
|
|
57
|
+
```typescript
|
|
58
|
+
import { exportLkcourse, validateLkcourse, importLkcourse } from "@lessonkit/lxpack";
|
|
59
|
+
|
|
60
|
+
await exportLkcourse({ projectRoot, manifest, includeBlockTree: true });
|
|
61
|
+
validateLkcourse("course.lkcourse");
|
|
62
|
+
await importLkcourse({ archivePath: "course.lkcourse", targetDir: "./restored" });
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Schemas: `@lessonkit/lxpack/lkcourse-format.v1.json`, `@lessonkit/lxpack/block-tree.v1.json`. See [Portable interchange](https://lessonkit.readthedocs.io/en/latest/reference/interchange.html).
|
|
66
|
+
|
|
53
67
|
## Browser bridge
|
|
54
68
|
|
|
55
69
|
When embedded in an LXPack iframe, `@lessonkit/react` forwards completion events to `window.parent.lxpackBridge.v1`:
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://lessonkit.dev/schemas/block-tree.v1.json",
|
|
4
|
+
"title": "BlockTreeV1",
|
|
5
|
+
"description": "Best-effort static JSX block inventory for a LessonKit course",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"additionalProperties": false,
|
|
8
|
+
"required": ["schemaVersion", "blocks", "sources"],
|
|
9
|
+
"properties": {
|
|
10
|
+
"schemaVersion": { "const": 1 },
|
|
11
|
+
"sources": {
|
|
12
|
+
"type": "array",
|
|
13
|
+
"items": { "type": "string", "minLength": 1 }
|
|
14
|
+
},
|
|
15
|
+
"blocks": {
|
|
16
|
+
"type": "array",
|
|
17
|
+
"items": { "$ref": "#/$defs/BlockTreeNode" }
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"$defs": {
|
|
21
|
+
"BlockTreeNode": {
|
|
22
|
+
"type": "object",
|
|
23
|
+
"additionalProperties": false,
|
|
24
|
+
"required": ["type"],
|
|
25
|
+
"properties": {
|
|
26
|
+
"type": { "type": "string", "minLength": 1 },
|
|
27
|
+
"rawTag": { "type": "string" },
|
|
28
|
+
"courseId": { "type": "string" },
|
|
29
|
+
"lessonId": { "type": "string" },
|
|
30
|
+
"checkId": { "type": "string" },
|
|
31
|
+
"blockId": { "type": "string" },
|
|
32
|
+
"nodeId": { "type": "string" },
|
|
33
|
+
"children": {
|
|
34
|
+
"type": "array",
|
|
35
|
+
"items": { "$ref": "#/$defs/BlockTreeNode" }
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
package/dist/bridge.cjs
CHANGED
|
@@ -35,8 +35,8 @@ __export(bridge_exports, {
|
|
|
35
35
|
mapLessonkitTelemetryToLxpack: () => import_tracking_schema2.mapLessonkitTelemetryToLxpack,
|
|
36
36
|
normalizeAssessmentPassingScore: () => normalizeAssessmentPassingScore,
|
|
37
37
|
normalizeAssessmentScore: () => normalizeAssessmentScore,
|
|
38
|
-
normalizePassingThreshold: () =>
|
|
39
|
-
normalizeScore: () =>
|
|
38
|
+
normalizePassingThreshold: () => normalizePassingThreshold,
|
|
39
|
+
normalizeScore: () => normalizeScore,
|
|
40
40
|
notifyLxpackAssessment: () => notifyLxpackAssessment,
|
|
41
41
|
notifyLxpackCourseComplete: () => notifyLxpackCourseComplete,
|
|
42
42
|
notifyLxpackLessonComplete: () => notifyLxpackLessonComplete,
|
|
@@ -137,14 +137,45 @@ function branchTelemetryToBridgeTrackEvent(event) {
|
|
|
137
137
|
|
|
138
138
|
// src/bridge.ts
|
|
139
139
|
var import_meta = {};
|
|
140
|
+
var DEFAULT_BRIDGE_PASSING_SCORE2 = 1;
|
|
141
|
+
function clamp01(value) {
|
|
142
|
+
return Math.min(1, Math.max(0, value));
|
|
143
|
+
}
|
|
144
|
+
function normalizeScore(raw) {
|
|
145
|
+
const { score, maxScore } = raw;
|
|
146
|
+
if (typeof score !== "number" || !Number.isFinite(score)) return null;
|
|
147
|
+
if (typeof maxScore === "number" && maxScore > 0) {
|
|
148
|
+
return clamp01(score / maxScore);
|
|
149
|
+
}
|
|
150
|
+
if (score > 1 && score <= 100) {
|
|
151
|
+
return clamp01(score / 100);
|
|
152
|
+
}
|
|
153
|
+
return clamp01(score);
|
|
154
|
+
}
|
|
155
|
+
function normalizePassingThreshold(raw) {
|
|
156
|
+
const { passingScore, maxScore } = raw ?? {};
|
|
157
|
+
if (typeof passingScore !== "number" || !Number.isFinite(passingScore)) {
|
|
158
|
+
return DEFAULT_BRIDGE_PASSING_SCORE2;
|
|
159
|
+
}
|
|
160
|
+
if (typeof maxScore === "number" && maxScore > 1) {
|
|
161
|
+
return clamp01(passingScore / maxScore);
|
|
162
|
+
}
|
|
163
|
+
if (typeof maxScore === "number" && maxScore <= 1) {
|
|
164
|
+
return clamp01(passingScore);
|
|
165
|
+
}
|
|
166
|
+
if (passingScore > 1 && passingScore <= 100) {
|
|
167
|
+
return clamp01(passingScore / 100);
|
|
168
|
+
}
|
|
169
|
+
return clamp01(passingScore);
|
|
170
|
+
}
|
|
140
171
|
function normalizeAssessmentScore(opts) {
|
|
141
172
|
if (typeof opts.score !== "number" || !Number.isFinite(opts.score)) {
|
|
142
173
|
return null;
|
|
143
174
|
}
|
|
144
|
-
return
|
|
175
|
+
return normalizeScore({ score: opts.score, maxScore: opts.maxScore });
|
|
145
176
|
}
|
|
146
177
|
function normalizeAssessmentPassingScore(opts) {
|
|
147
|
-
return
|
|
178
|
+
return normalizePassingThreshold({
|
|
148
179
|
passingScore: opts?.passingScore,
|
|
149
180
|
maxScore: opts?.maxScore
|
|
150
181
|
});
|
|
@@ -156,13 +187,7 @@ function resolveParentOrigin(parentWindow) {
|
|
|
156
187
|
try {
|
|
157
188
|
return parent.location.origin;
|
|
158
189
|
} catch {
|
|
159
|
-
|
|
160
|
-
if (!referrer) return null;
|
|
161
|
-
try {
|
|
162
|
-
return new URL(referrer).origin;
|
|
163
|
-
} catch {
|
|
164
|
-
return null;
|
|
165
|
-
}
|
|
190
|
+
return null;
|
|
166
191
|
}
|
|
167
192
|
}
|
|
168
193
|
function isProductionRuntime() {
|
|
@@ -230,7 +255,7 @@ function dispatchBridgeActionInner(bridge, action) {
|
|
|
230
255
|
bridge.completeCourse?.();
|
|
231
256
|
return;
|
|
232
257
|
case "submitAssessment": {
|
|
233
|
-
const scaled =
|
|
258
|
+
const scaled = normalizeScore({
|
|
234
259
|
score: action.score,
|
|
235
260
|
maxScore: action.maxScore
|
|
236
261
|
});
|
|
@@ -238,7 +263,7 @@ function dispatchBridgeActionInner(bridge, action) {
|
|
|
238
263
|
bridge.submitAssessment?.({
|
|
239
264
|
id: action.id,
|
|
240
265
|
score: scaled,
|
|
241
|
-
passingScore:
|
|
266
|
+
passingScore: normalizePassingThreshold({
|
|
242
267
|
passingScore: action.passingScore,
|
|
243
268
|
maxScore: action.maxScore
|
|
244
269
|
}),
|
|
@@ -253,13 +278,16 @@ function dispatchBridgeActionInner(bridge, action) {
|
|
|
253
278
|
return;
|
|
254
279
|
}
|
|
255
280
|
}
|
|
256
|
-
function forwardAssessmentCompletedToBridge(bridge, event) {
|
|
281
|
+
function forwardAssessmentCompletedToBridge(bridge, event, onBridgeMiss) {
|
|
257
282
|
const data = event.data;
|
|
258
283
|
const scaled = normalizeAssessmentScore({
|
|
259
284
|
score: data.score,
|
|
260
285
|
maxScore: data.maxScore
|
|
261
286
|
});
|
|
262
|
-
if (scaled === null)
|
|
287
|
+
if (scaled === null) {
|
|
288
|
+
onBridgeMiss?.(event);
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
263
291
|
bridge.submitAssessment?.({
|
|
264
292
|
id: data.checkId,
|
|
265
293
|
score: scaled,
|
|
@@ -279,7 +307,7 @@ function forwardTelemetryToBridge(event, mode = "auto", parentWindow, opts) {
|
|
|
279
307
|
if (!bridge) return;
|
|
280
308
|
try {
|
|
281
309
|
if (event.name === "assessment_completed") {
|
|
282
|
-
forwardAssessmentCompletedToBridge(bridge, event);
|
|
310
|
+
forwardAssessmentCompletedToBridge(bridge, event, opts?.onBridgeMiss);
|
|
283
311
|
return;
|
|
284
312
|
}
|
|
285
313
|
const answeredTrack = answeredTelemetryToBridgeTrackEvent(event);
|
|
@@ -306,20 +334,47 @@ function createLxpackBridge(opts) {
|
|
|
306
334
|
function notifyLxpackLessonComplete(lessonId, opts) {
|
|
307
335
|
const bridge = getBridge(void 0, opts);
|
|
308
336
|
if (!bridge?.completeLesson) return false;
|
|
309
|
-
|
|
310
|
-
|
|
337
|
+
try {
|
|
338
|
+
bridge.completeLesson(lessonId);
|
|
339
|
+
return true;
|
|
340
|
+
} catch (err) {
|
|
341
|
+
handleBridgeError(err, opts?.onBridgeError);
|
|
342
|
+
return false;
|
|
343
|
+
}
|
|
311
344
|
}
|
|
312
345
|
function notifyLxpackCourseComplete(opts) {
|
|
313
346
|
const bridge = getBridge(void 0, opts);
|
|
314
347
|
if (!bridge?.completeCourse) return false;
|
|
315
|
-
|
|
316
|
-
|
|
348
|
+
try {
|
|
349
|
+
bridge.completeCourse();
|
|
350
|
+
return true;
|
|
351
|
+
} catch (err) {
|
|
352
|
+
handleBridgeError(err, opts?.onBridgeError);
|
|
353
|
+
return false;
|
|
354
|
+
}
|
|
317
355
|
}
|
|
318
356
|
function notifyLxpackAssessment(payload, opts) {
|
|
319
357
|
const bridge = getBridge(void 0, opts);
|
|
320
358
|
if (!bridge?.submitAssessment) return false;
|
|
321
|
-
|
|
322
|
-
|
|
359
|
+
const scaled = normalizeAssessmentScore({
|
|
360
|
+
score: payload.score,
|
|
361
|
+
maxScore: payload.maxScore
|
|
362
|
+
});
|
|
363
|
+
if (scaled === null) return false;
|
|
364
|
+
try {
|
|
365
|
+
bridge.submitAssessment({
|
|
366
|
+
...payload,
|
|
367
|
+
score: scaled,
|
|
368
|
+
passingScore: normalizeAssessmentPassingScore({
|
|
369
|
+
passingScore: payload.passingScore,
|
|
370
|
+
maxScore: payload.maxScore
|
|
371
|
+
})
|
|
372
|
+
});
|
|
373
|
+
return true;
|
|
374
|
+
} catch (err) {
|
|
375
|
+
handleBridgeError(err, opts?.onBridgeError);
|
|
376
|
+
return false;
|
|
377
|
+
}
|
|
323
378
|
}
|
|
324
379
|
// Annotate the CommonJS export names for ESM import in node:
|
|
325
380
|
0 && (module.exports = {
|
package/dist/bridge.d.cts
CHANGED
|
@@ -1,10 +1,24 @@
|
|
|
1
1
|
import { LmsBridgeMode, TelemetryEvent, CheckId, LessonId } from '@lessonkit/core';
|
|
2
2
|
import { LxpackBridgeV1, LxpackBridgeSubmitAssessmentPayload } from '@lxpack/spa-bridge';
|
|
3
|
-
export { DEFAULT_BRIDGE_PASSING_SCORE, LXPACK_BRIDGE_VERSIONS, LxpackBridgeSubmitAssessmentPayload, LxpackBridgeV1, createLxpackBridgeHost,
|
|
3
|
+
export { DEFAULT_BRIDGE_PASSING_SCORE, LXPACK_BRIDGE_VERSIONS, LxpackBridgeSubmitAssessmentPayload, LxpackBridgeV1, createLxpackBridgeHost, supportedBridgeVersions } from '@lxpack/spa-bridge';
|
|
4
4
|
import { mapLessonkitTelemetryToBridgeAction } from '@lxpack/tracking-schema';
|
|
5
5
|
export { LESSONKIT_TELEMETRY_EVENTS, LessonkitBridgeAction, LessonkitTelemetryEvent, LessonkitTelemetryEventName, TrackingSchemaEvent, mapLessonkitTelemetryToBridgeAction, mapLessonkitTelemetryToLxpack } from '@lxpack/tracking-schema';
|
|
6
6
|
export { B as BRANCH_TELEMETRY_EVENTS, b as branchTelemetryToBridgeTrackEvent, t as telemetryEventToLessonkit } from './telemetry-0fIWoomS.cjs';
|
|
7
7
|
|
|
8
|
+
/**
|
|
9
|
+
* Scale a raw quiz score to 0–1 for the LXPack parent bridge.
|
|
10
|
+
* When `maxScore > 1`, always treats `score` as raw points (fixes partial-credit 1/N cases).
|
|
11
|
+
*/
|
|
12
|
+
declare function normalizeScore(raw: {
|
|
13
|
+
score?: number;
|
|
14
|
+
maxScore?: number;
|
|
15
|
+
}): number | null;
|
|
16
|
+
/** Scale a raw passing threshold to 0–1 for the LXPack parent bridge. */
|
|
17
|
+
declare function normalizePassingThreshold(raw?: {
|
|
18
|
+
passingScore?: number;
|
|
19
|
+
maxScore?: number;
|
|
20
|
+
}): number;
|
|
21
|
+
|
|
8
22
|
/**
|
|
9
23
|
* Scale a raw quiz score to 0–1 for the LXPack parent bridge.
|
|
10
24
|
* Returns null when `score` is missing or not finite (caller should skip submit).
|
|
@@ -15,7 +29,7 @@ declare function normalizeAssessmentScore(opts: {
|
|
|
15
29
|
}): number | null;
|
|
16
30
|
/**
|
|
17
31
|
* Scale a raw passing threshold to 0–1 for the LXPack parent bridge.
|
|
18
|
-
*
|
|
32
|
+
* Default 1.0 (100%) when omitted — matches React SPA default.
|
|
19
33
|
*/
|
|
20
34
|
declare function normalizeAssessmentPassingScore(opts?: {
|
|
21
35
|
passingScore?: number;
|
|
@@ -26,6 +40,7 @@ type BridgeAccessOptions = {
|
|
|
26
40
|
allowedParentOrigins?: string[];
|
|
27
41
|
/** LMS bridge mode; `"auto"` in production requires `allowedParentOrigins`. */
|
|
28
42
|
mode?: LxpackBridgeMode;
|
|
43
|
+
onBridgeError?: (err: unknown) => void;
|
|
29
44
|
};
|
|
30
45
|
/** Resolve the parent frame origin when embedded (same-origin parent or document.referrer fallback). */
|
|
31
46
|
declare function resolveParentOrigin(parentWindow?: Window): string | null;
|
|
@@ -41,18 +56,33 @@ declare function dispatchBridgeAction(bridge: LxpackBridgeV1, action: ReturnType
|
|
|
41
56
|
}): void;
|
|
42
57
|
type ForwardTelemetryToBridgeOptions = {
|
|
43
58
|
onBridgeError?: (err: unknown) => void;
|
|
59
|
+
/** Called when assessment_completed cannot be forwarded (e.g. missing/invalid score). */
|
|
60
|
+
onBridgeMiss?: (event: TelemetryEvent) => void;
|
|
44
61
|
allowedParentOrigins?: string[];
|
|
45
62
|
};
|
|
63
|
+
/**
|
|
64
|
+
* Map a LessonKit telemetry event to LMS bridge actions (`track`, `complete`, assessment score).
|
|
65
|
+
*
|
|
66
|
+
* @example
|
|
67
|
+
* ```ts
|
|
68
|
+
* import { forwardTelemetryToBridge } from "@lessonkit/lxpack/bridge";
|
|
69
|
+
*
|
|
70
|
+
* forwardTelemetryToBridge(event, "auto", window.parent, {
|
|
71
|
+
* allowedParentOrigins: ["https://lms.example.com"],
|
|
72
|
+
* onBridgeMiss: (e) => console.warn("bridge miss", e.name),
|
|
73
|
+
* });
|
|
74
|
+
* ```
|
|
75
|
+
*/
|
|
46
76
|
declare function forwardTelemetryToBridge(event: TelemetryEvent, mode?: LxpackBridgeMode, parentWindow?: Window, opts?: ForwardTelemetryToBridgeOptions): void;
|
|
47
77
|
declare function createLxpackBridge(opts?: BridgeAccessOptions): LxpackBridgeV1 | null;
|
|
48
78
|
declare function notifyLxpackLessonComplete(lessonId: LessonId, opts?: BridgeAccessOptions): boolean;
|
|
49
79
|
declare function notifyLxpackCourseComplete(opts?: BridgeAccessOptions): boolean;
|
|
50
80
|
/**
|
|
51
81
|
* Submit assessment results to the parent LXPack bridge.
|
|
52
|
-
*
|
|
82
|
+
* Raw point scores are normalized to 0–1 before submission.
|
|
53
83
|
*/
|
|
54
84
|
declare function notifyLxpackAssessment(payload: LxpackBridgeSubmitAssessmentPayload & {
|
|
55
85
|
id: CheckId;
|
|
56
86
|
}, opts?: BridgeAccessOptions): boolean;
|
|
57
87
|
|
|
58
|
-
export { type BridgeAccessOptions, type ForwardTelemetryToBridgeOptions, type LxpackBridgeMode, createLxpackBridge, dispatchBridgeAction, forwardTelemetryToBridge, getLxpackBridge, isParentOriginAllowed, normalizeAssessmentPassingScore, normalizeAssessmentScore, notifyLxpackAssessment, notifyLxpackCourseComplete, notifyLxpackLessonComplete, resolveParentOrigin };
|
|
88
|
+
export { type BridgeAccessOptions, type ForwardTelemetryToBridgeOptions, type LxpackBridgeMode, createLxpackBridge, dispatchBridgeAction, forwardTelemetryToBridge, getLxpackBridge, isParentOriginAllowed, normalizeAssessmentPassingScore, normalizeAssessmentScore, normalizePassingThreshold, normalizeScore, notifyLxpackAssessment, notifyLxpackCourseComplete, notifyLxpackLessonComplete, resolveParentOrigin };
|
package/dist/bridge.d.ts
CHANGED
|
@@ -1,10 +1,24 @@
|
|
|
1
1
|
import { LmsBridgeMode, TelemetryEvent, CheckId, LessonId } from '@lessonkit/core';
|
|
2
2
|
import { LxpackBridgeV1, LxpackBridgeSubmitAssessmentPayload } from '@lxpack/spa-bridge';
|
|
3
|
-
export { DEFAULT_BRIDGE_PASSING_SCORE, LXPACK_BRIDGE_VERSIONS, LxpackBridgeSubmitAssessmentPayload, LxpackBridgeV1, createLxpackBridgeHost,
|
|
3
|
+
export { DEFAULT_BRIDGE_PASSING_SCORE, LXPACK_BRIDGE_VERSIONS, LxpackBridgeSubmitAssessmentPayload, LxpackBridgeV1, createLxpackBridgeHost, supportedBridgeVersions } from '@lxpack/spa-bridge';
|
|
4
4
|
import { mapLessonkitTelemetryToBridgeAction } from '@lxpack/tracking-schema';
|
|
5
5
|
export { LESSONKIT_TELEMETRY_EVENTS, LessonkitBridgeAction, LessonkitTelemetryEvent, LessonkitTelemetryEventName, TrackingSchemaEvent, mapLessonkitTelemetryToBridgeAction, mapLessonkitTelemetryToLxpack } from '@lxpack/tracking-schema';
|
|
6
6
|
export { B as BRANCH_TELEMETRY_EVENTS, b as branchTelemetryToBridgeTrackEvent, t as telemetryEventToLessonkit } from './telemetry-0fIWoomS.js';
|
|
7
7
|
|
|
8
|
+
/**
|
|
9
|
+
* Scale a raw quiz score to 0–1 for the LXPack parent bridge.
|
|
10
|
+
* When `maxScore > 1`, always treats `score` as raw points (fixes partial-credit 1/N cases).
|
|
11
|
+
*/
|
|
12
|
+
declare function normalizeScore(raw: {
|
|
13
|
+
score?: number;
|
|
14
|
+
maxScore?: number;
|
|
15
|
+
}): number | null;
|
|
16
|
+
/** Scale a raw passing threshold to 0–1 for the LXPack parent bridge. */
|
|
17
|
+
declare function normalizePassingThreshold(raw?: {
|
|
18
|
+
passingScore?: number;
|
|
19
|
+
maxScore?: number;
|
|
20
|
+
}): number;
|
|
21
|
+
|
|
8
22
|
/**
|
|
9
23
|
* Scale a raw quiz score to 0–1 for the LXPack parent bridge.
|
|
10
24
|
* Returns null when `score` is missing or not finite (caller should skip submit).
|
|
@@ -15,7 +29,7 @@ declare function normalizeAssessmentScore(opts: {
|
|
|
15
29
|
}): number | null;
|
|
16
30
|
/**
|
|
17
31
|
* Scale a raw passing threshold to 0–1 for the LXPack parent bridge.
|
|
18
|
-
*
|
|
32
|
+
* Default 1.0 (100%) when omitted — matches React SPA default.
|
|
19
33
|
*/
|
|
20
34
|
declare function normalizeAssessmentPassingScore(opts?: {
|
|
21
35
|
passingScore?: number;
|
|
@@ -26,6 +40,7 @@ type BridgeAccessOptions = {
|
|
|
26
40
|
allowedParentOrigins?: string[];
|
|
27
41
|
/** LMS bridge mode; `"auto"` in production requires `allowedParentOrigins`. */
|
|
28
42
|
mode?: LxpackBridgeMode;
|
|
43
|
+
onBridgeError?: (err: unknown) => void;
|
|
29
44
|
};
|
|
30
45
|
/** Resolve the parent frame origin when embedded (same-origin parent or document.referrer fallback). */
|
|
31
46
|
declare function resolveParentOrigin(parentWindow?: Window): string | null;
|
|
@@ -41,18 +56,33 @@ declare function dispatchBridgeAction(bridge: LxpackBridgeV1, action: ReturnType
|
|
|
41
56
|
}): void;
|
|
42
57
|
type ForwardTelemetryToBridgeOptions = {
|
|
43
58
|
onBridgeError?: (err: unknown) => void;
|
|
59
|
+
/** Called when assessment_completed cannot be forwarded (e.g. missing/invalid score). */
|
|
60
|
+
onBridgeMiss?: (event: TelemetryEvent) => void;
|
|
44
61
|
allowedParentOrigins?: string[];
|
|
45
62
|
};
|
|
63
|
+
/**
|
|
64
|
+
* Map a LessonKit telemetry event to LMS bridge actions (`track`, `complete`, assessment score).
|
|
65
|
+
*
|
|
66
|
+
* @example
|
|
67
|
+
* ```ts
|
|
68
|
+
* import { forwardTelemetryToBridge } from "@lessonkit/lxpack/bridge";
|
|
69
|
+
*
|
|
70
|
+
* forwardTelemetryToBridge(event, "auto", window.parent, {
|
|
71
|
+
* allowedParentOrigins: ["https://lms.example.com"],
|
|
72
|
+
* onBridgeMiss: (e) => console.warn("bridge miss", e.name),
|
|
73
|
+
* });
|
|
74
|
+
* ```
|
|
75
|
+
*/
|
|
46
76
|
declare function forwardTelemetryToBridge(event: TelemetryEvent, mode?: LxpackBridgeMode, parentWindow?: Window, opts?: ForwardTelemetryToBridgeOptions): void;
|
|
47
77
|
declare function createLxpackBridge(opts?: BridgeAccessOptions): LxpackBridgeV1 | null;
|
|
48
78
|
declare function notifyLxpackLessonComplete(lessonId: LessonId, opts?: BridgeAccessOptions): boolean;
|
|
49
79
|
declare function notifyLxpackCourseComplete(opts?: BridgeAccessOptions): boolean;
|
|
50
80
|
/**
|
|
51
81
|
* Submit assessment results to the parent LXPack bridge.
|
|
52
|
-
*
|
|
82
|
+
* Raw point scores are normalized to 0–1 before submission.
|
|
53
83
|
*/
|
|
54
84
|
declare function notifyLxpackAssessment(payload: LxpackBridgeSubmitAssessmentPayload & {
|
|
55
85
|
id: CheckId;
|
|
56
86
|
}, opts?: BridgeAccessOptions): boolean;
|
|
57
87
|
|
|
58
|
-
export { type BridgeAccessOptions, type ForwardTelemetryToBridgeOptions, type LxpackBridgeMode, createLxpackBridge, dispatchBridgeAction, forwardTelemetryToBridge, getLxpackBridge, isParentOriginAllowed, normalizeAssessmentPassingScore, normalizeAssessmentScore, notifyLxpackAssessment, notifyLxpackCourseComplete, notifyLxpackLessonComplete, resolveParentOrigin };
|
|
88
|
+
export { type BridgeAccessOptions, type ForwardTelemetryToBridgeOptions, type LxpackBridgeMode, createLxpackBridge, dispatchBridgeAction, forwardTelemetryToBridge, getLxpackBridge, isParentOriginAllowed, normalizeAssessmentPassingScore, normalizeAssessmentScore, normalizePassingThreshold, normalizeScore, notifyLxpackAssessment, notifyLxpackCourseComplete, notifyLxpackLessonComplete, resolveParentOrigin };
|
package/dist/bridge.js
CHANGED
|
@@ -7,16 +7,12 @@ import {
|
|
|
7
7
|
|
|
8
8
|
// src/bridge.ts
|
|
9
9
|
import {
|
|
10
|
-
getLxpackBridge as getLxpackBridgeFromParent
|
|
11
|
-
normalizePassingThreshold,
|
|
12
|
-
normalizeScore
|
|
10
|
+
getLxpackBridge as getLxpackBridgeFromParent
|
|
13
11
|
} from "@lxpack/spa-bridge";
|
|
14
12
|
import {
|
|
15
13
|
createLxpackBridgeHost,
|
|
16
14
|
DEFAULT_BRIDGE_PASSING_SCORE,
|
|
17
15
|
LXPACK_BRIDGE_VERSIONS,
|
|
18
|
-
normalizePassingThreshold as normalizePassingThreshold2,
|
|
19
|
-
normalizeScore as normalizeScore2,
|
|
20
16
|
supportedBridgeVersions
|
|
21
17
|
} from "@lxpack/spa-bridge";
|
|
22
18
|
import {
|
|
@@ -25,6 +21,37 @@ import {
|
|
|
25
21
|
mapLessonkitTelemetryToLxpack
|
|
26
22
|
} from "@lxpack/tracking-schema";
|
|
27
23
|
import { mapLessonkitTelemetryToBridgeAction as mapLessonkitTelemetryToBridgeAction2 } from "@lxpack/tracking-schema";
|
|
24
|
+
var DEFAULT_BRIDGE_PASSING_SCORE2 = 1;
|
|
25
|
+
function clamp01(value) {
|
|
26
|
+
return Math.min(1, Math.max(0, value));
|
|
27
|
+
}
|
|
28
|
+
function normalizeScore(raw) {
|
|
29
|
+
const { score, maxScore } = raw;
|
|
30
|
+
if (typeof score !== "number" || !Number.isFinite(score)) return null;
|
|
31
|
+
if (typeof maxScore === "number" && maxScore > 0) {
|
|
32
|
+
return clamp01(score / maxScore);
|
|
33
|
+
}
|
|
34
|
+
if (score > 1 && score <= 100) {
|
|
35
|
+
return clamp01(score / 100);
|
|
36
|
+
}
|
|
37
|
+
return clamp01(score);
|
|
38
|
+
}
|
|
39
|
+
function normalizePassingThreshold(raw) {
|
|
40
|
+
const { passingScore, maxScore } = raw ?? {};
|
|
41
|
+
if (typeof passingScore !== "number" || !Number.isFinite(passingScore)) {
|
|
42
|
+
return DEFAULT_BRIDGE_PASSING_SCORE2;
|
|
43
|
+
}
|
|
44
|
+
if (typeof maxScore === "number" && maxScore > 1) {
|
|
45
|
+
return clamp01(passingScore / maxScore);
|
|
46
|
+
}
|
|
47
|
+
if (typeof maxScore === "number" && maxScore <= 1) {
|
|
48
|
+
return clamp01(passingScore);
|
|
49
|
+
}
|
|
50
|
+
if (passingScore > 1 && passingScore <= 100) {
|
|
51
|
+
return clamp01(passingScore / 100);
|
|
52
|
+
}
|
|
53
|
+
return clamp01(passingScore);
|
|
54
|
+
}
|
|
28
55
|
function normalizeAssessmentScore(opts) {
|
|
29
56
|
if (typeof opts.score !== "number" || !Number.isFinite(opts.score)) {
|
|
30
57
|
return null;
|
|
@@ -44,13 +71,7 @@ function resolveParentOrigin(parentWindow) {
|
|
|
44
71
|
try {
|
|
45
72
|
return parent.location.origin;
|
|
46
73
|
} catch {
|
|
47
|
-
|
|
48
|
-
if (!referrer) return null;
|
|
49
|
-
try {
|
|
50
|
-
return new URL(referrer).origin;
|
|
51
|
-
} catch {
|
|
52
|
-
return null;
|
|
53
|
-
}
|
|
74
|
+
return null;
|
|
54
75
|
}
|
|
55
76
|
}
|
|
56
77
|
function isProductionRuntime() {
|
|
@@ -141,13 +162,16 @@ function dispatchBridgeActionInner(bridge, action) {
|
|
|
141
162
|
return;
|
|
142
163
|
}
|
|
143
164
|
}
|
|
144
|
-
function forwardAssessmentCompletedToBridge(bridge, event) {
|
|
165
|
+
function forwardAssessmentCompletedToBridge(bridge, event, onBridgeMiss) {
|
|
145
166
|
const data = event.data;
|
|
146
167
|
const scaled = normalizeAssessmentScore({
|
|
147
168
|
score: data.score,
|
|
148
169
|
maxScore: data.maxScore
|
|
149
170
|
});
|
|
150
|
-
if (scaled === null)
|
|
171
|
+
if (scaled === null) {
|
|
172
|
+
onBridgeMiss?.(event);
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
151
175
|
bridge.submitAssessment?.({
|
|
152
176
|
id: data.checkId,
|
|
153
177
|
score: scaled,
|
|
@@ -167,7 +191,7 @@ function forwardTelemetryToBridge(event, mode = "auto", parentWindow, opts) {
|
|
|
167
191
|
if (!bridge) return;
|
|
168
192
|
try {
|
|
169
193
|
if (event.name === "assessment_completed") {
|
|
170
|
-
forwardAssessmentCompletedToBridge(bridge, event);
|
|
194
|
+
forwardAssessmentCompletedToBridge(bridge, event, opts?.onBridgeMiss);
|
|
171
195
|
return;
|
|
172
196
|
}
|
|
173
197
|
const answeredTrack = answeredTelemetryToBridgeTrackEvent(event);
|
|
@@ -194,20 +218,47 @@ function createLxpackBridge(opts) {
|
|
|
194
218
|
function notifyLxpackLessonComplete(lessonId, opts) {
|
|
195
219
|
const bridge = getBridge(void 0, opts);
|
|
196
220
|
if (!bridge?.completeLesson) return false;
|
|
197
|
-
|
|
198
|
-
|
|
221
|
+
try {
|
|
222
|
+
bridge.completeLesson(lessonId);
|
|
223
|
+
return true;
|
|
224
|
+
} catch (err) {
|
|
225
|
+
handleBridgeError(err, opts?.onBridgeError);
|
|
226
|
+
return false;
|
|
227
|
+
}
|
|
199
228
|
}
|
|
200
229
|
function notifyLxpackCourseComplete(opts) {
|
|
201
230
|
const bridge = getBridge(void 0, opts);
|
|
202
231
|
if (!bridge?.completeCourse) return false;
|
|
203
|
-
|
|
204
|
-
|
|
232
|
+
try {
|
|
233
|
+
bridge.completeCourse();
|
|
234
|
+
return true;
|
|
235
|
+
} catch (err) {
|
|
236
|
+
handleBridgeError(err, opts?.onBridgeError);
|
|
237
|
+
return false;
|
|
238
|
+
}
|
|
205
239
|
}
|
|
206
240
|
function notifyLxpackAssessment(payload, opts) {
|
|
207
241
|
const bridge = getBridge(void 0, opts);
|
|
208
242
|
if (!bridge?.submitAssessment) return false;
|
|
209
|
-
|
|
210
|
-
|
|
243
|
+
const scaled = normalizeAssessmentScore({
|
|
244
|
+
score: payload.score,
|
|
245
|
+
maxScore: payload.maxScore
|
|
246
|
+
});
|
|
247
|
+
if (scaled === null) return false;
|
|
248
|
+
try {
|
|
249
|
+
bridge.submitAssessment({
|
|
250
|
+
...payload,
|
|
251
|
+
score: scaled,
|
|
252
|
+
passingScore: normalizeAssessmentPassingScore({
|
|
253
|
+
passingScore: payload.passingScore,
|
|
254
|
+
maxScore: payload.maxScore
|
|
255
|
+
})
|
|
256
|
+
});
|
|
257
|
+
return true;
|
|
258
|
+
} catch (err) {
|
|
259
|
+
handleBridgeError(err, opts?.onBridgeError);
|
|
260
|
+
return false;
|
|
261
|
+
}
|
|
211
262
|
}
|
|
212
263
|
export {
|
|
213
264
|
BRANCH_TELEMETRY_EVENTS,
|
|
@@ -225,8 +276,8 @@ export {
|
|
|
225
276
|
mapLessonkitTelemetryToLxpack,
|
|
226
277
|
normalizeAssessmentPassingScore,
|
|
227
278
|
normalizeAssessmentScore,
|
|
228
|
-
|
|
229
|
-
|
|
279
|
+
normalizePassingThreshold,
|
|
280
|
+
normalizeScore,
|
|
230
281
|
notifyLxpackAssessment,
|
|
231
282
|
notifyLxpackCourseComplete,
|
|
232
283
|
notifyLxpackLessonComplete,
|