@lessonkit/lxpack 1.5.0 → 1.7.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 +15 -1
- package/block-tree.v1.json +40 -0
- package/dist/bridge.cjs +81 -22
- package/dist/bridge.d.cts +34 -4
- package/dist/bridge.d.ts +34 -4
- package/dist/bridge.js +78 -23
- package/dist/index.cjs +1479 -187
- package/dist/index.d.cts +212 -2
- package/dist/index.d.ts +212 -2
- package/dist/index.js +1481 -199
- package/lessonkit-manifest.v1.json +75 -5
- package/lkcourse-format.v1.json +21 -0
- package/package.json +17 -11
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`:
|
|
@@ -70,7 +84,7 @@ Production builds require `allowedParentOrigins` when `bridge: "auto"`.
|
|
|
70
84
|
|
|
71
85
|
## Docs
|
|
72
86
|
|
|
73
|
-
[
|
|
87
|
+
[5-minute guide](https://lessonkit.readthedocs.io/en/latest/guides/react-developers/getting-started-in-5-minutes.html) · [LMS Go-Live](https://lessonkit.readthedocs.io/en/latest/guides/react-developers/lms-go-live.html) · [Packaging reference](https://lessonkit.readthedocs.io/en/latest/reference/packaging.html) · [TypeDoc API index](https://lessonkit.readthedocs.io/en/latest/reference/api.html)
|
|
74
88
|
|
|
75
89
|
## License
|
|
76
90
|
|
|
@@ -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,49 @@ 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) return null;
|
|
148
|
+
if (typeof maxScore === "number" && maxScore > 0) {
|
|
149
|
+
return clamp01(score / maxScore);
|
|
150
|
+
}
|
|
151
|
+
if (score > 1 && score <= 100) {
|
|
152
|
+
return clamp01(score / 100);
|
|
153
|
+
}
|
|
154
|
+
return clamp01(score);
|
|
155
|
+
}
|
|
156
|
+
function normalizePassingThreshold(raw) {
|
|
157
|
+
const { passingScore, maxScore } = raw ?? {};
|
|
158
|
+
if (typeof passingScore !== "number" || !Number.isFinite(passingScore)) {
|
|
159
|
+
return DEFAULT_BRIDGE_PASSING_SCORE2;
|
|
160
|
+
}
|
|
161
|
+
if (typeof maxScore === "number" && maxScore <= 0) {
|
|
162
|
+
return DEFAULT_BRIDGE_PASSING_SCORE2;
|
|
163
|
+
}
|
|
164
|
+
if (typeof maxScore === "number" && maxScore > 1) {
|
|
165
|
+
return clamp01(passingScore / maxScore);
|
|
166
|
+
}
|
|
167
|
+
if (typeof maxScore === "number" && maxScore <= 1) {
|
|
168
|
+
return clamp01(passingScore);
|
|
169
|
+
}
|
|
170
|
+
if (passingScore > 1 && passingScore <= 100) {
|
|
171
|
+
return clamp01(passingScore / 100);
|
|
172
|
+
}
|
|
173
|
+
return clamp01(passingScore);
|
|
174
|
+
}
|
|
140
175
|
function normalizeAssessmentScore(opts) {
|
|
141
176
|
if (typeof opts.score !== "number" || !Number.isFinite(opts.score)) {
|
|
142
177
|
return null;
|
|
143
178
|
}
|
|
144
|
-
return
|
|
179
|
+
return normalizeScore({ score: opts.score, maxScore: opts.maxScore });
|
|
145
180
|
}
|
|
146
181
|
function normalizeAssessmentPassingScore(opts) {
|
|
147
|
-
return
|
|
182
|
+
return normalizePassingThreshold({
|
|
148
183
|
passingScore: opts?.passingScore,
|
|
149
184
|
maxScore: opts?.maxScore
|
|
150
185
|
});
|
|
@@ -156,13 +191,7 @@ function resolveParentOrigin(parentWindow) {
|
|
|
156
191
|
try {
|
|
157
192
|
return parent.location.origin;
|
|
158
193
|
} catch {
|
|
159
|
-
|
|
160
|
-
if (!referrer) return null;
|
|
161
|
-
try {
|
|
162
|
-
return new URL(referrer).origin;
|
|
163
|
-
} catch {
|
|
164
|
-
return null;
|
|
165
|
-
}
|
|
194
|
+
return null;
|
|
166
195
|
}
|
|
167
196
|
}
|
|
168
197
|
function isProductionRuntime() {
|
|
@@ -230,7 +259,7 @@ function dispatchBridgeActionInner(bridge, action) {
|
|
|
230
259
|
bridge.completeCourse?.();
|
|
231
260
|
return;
|
|
232
261
|
case "submitAssessment": {
|
|
233
|
-
const scaled =
|
|
262
|
+
const scaled = normalizeScore({
|
|
234
263
|
score: action.score,
|
|
235
264
|
maxScore: action.maxScore
|
|
236
265
|
});
|
|
@@ -238,7 +267,7 @@ function dispatchBridgeActionInner(bridge, action) {
|
|
|
238
267
|
bridge.submitAssessment?.({
|
|
239
268
|
id: action.id,
|
|
240
269
|
score: scaled,
|
|
241
|
-
passingScore:
|
|
270
|
+
passingScore: normalizePassingThreshold({
|
|
242
271
|
passingScore: action.passingScore,
|
|
243
272
|
maxScore: action.maxScore
|
|
244
273
|
}),
|
|
@@ -253,13 +282,16 @@ function dispatchBridgeActionInner(bridge, action) {
|
|
|
253
282
|
return;
|
|
254
283
|
}
|
|
255
284
|
}
|
|
256
|
-
function forwardAssessmentCompletedToBridge(bridge, event) {
|
|
285
|
+
function forwardAssessmentCompletedToBridge(bridge, event, onBridgeMiss) {
|
|
257
286
|
const data = event.data;
|
|
258
287
|
const scaled = normalizeAssessmentScore({
|
|
259
288
|
score: data.score,
|
|
260
289
|
maxScore: data.maxScore
|
|
261
290
|
});
|
|
262
|
-
if (scaled === null)
|
|
291
|
+
if (scaled === null) {
|
|
292
|
+
onBridgeMiss?.(event);
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
263
295
|
bridge.submitAssessment?.({
|
|
264
296
|
id: data.checkId,
|
|
265
297
|
score: scaled,
|
|
@@ -279,7 +311,7 @@ function forwardTelemetryToBridge(event, mode = "auto", parentWindow, opts) {
|
|
|
279
311
|
if (!bridge) return;
|
|
280
312
|
try {
|
|
281
313
|
if (event.name === "assessment_completed") {
|
|
282
|
-
forwardAssessmentCompletedToBridge(bridge, event);
|
|
314
|
+
forwardAssessmentCompletedToBridge(bridge, event, opts?.onBridgeMiss);
|
|
283
315
|
return;
|
|
284
316
|
}
|
|
285
317
|
const answeredTrack = answeredTelemetryToBridgeTrackEvent(event);
|
|
@@ -306,20 +338,47 @@ function createLxpackBridge(opts) {
|
|
|
306
338
|
function notifyLxpackLessonComplete(lessonId, opts) {
|
|
307
339
|
const bridge = getBridge(void 0, opts);
|
|
308
340
|
if (!bridge?.completeLesson) return false;
|
|
309
|
-
|
|
310
|
-
|
|
341
|
+
try {
|
|
342
|
+
bridge.completeLesson(lessonId);
|
|
343
|
+
return true;
|
|
344
|
+
} catch (err) {
|
|
345
|
+
handleBridgeError(err, opts?.onBridgeError);
|
|
346
|
+
return false;
|
|
347
|
+
}
|
|
311
348
|
}
|
|
312
349
|
function notifyLxpackCourseComplete(opts) {
|
|
313
350
|
const bridge = getBridge(void 0, opts);
|
|
314
351
|
if (!bridge?.completeCourse) return false;
|
|
315
|
-
|
|
316
|
-
|
|
352
|
+
try {
|
|
353
|
+
bridge.completeCourse();
|
|
354
|
+
return true;
|
|
355
|
+
} catch (err) {
|
|
356
|
+
handleBridgeError(err, opts?.onBridgeError);
|
|
357
|
+
return false;
|
|
358
|
+
}
|
|
317
359
|
}
|
|
318
360
|
function notifyLxpackAssessment(payload, opts) {
|
|
319
361
|
const bridge = getBridge(void 0, opts);
|
|
320
362
|
if (!bridge?.submitAssessment) return false;
|
|
321
|
-
|
|
322
|
-
|
|
363
|
+
const scaled = normalizeAssessmentScore({
|
|
364
|
+
score: payload.score,
|
|
365
|
+
maxScore: payload.maxScore
|
|
366
|
+
});
|
|
367
|
+
if (scaled === null) return false;
|
|
368
|
+
try {
|
|
369
|
+
bridge.submitAssessment({
|
|
370
|
+
...payload,
|
|
371
|
+
score: scaled,
|
|
372
|
+
passingScore: normalizeAssessmentPassingScore({
|
|
373
|
+
passingScore: payload.passingScore,
|
|
374
|
+
maxScore: payload.maxScore
|
|
375
|
+
})
|
|
376
|
+
});
|
|
377
|
+
return true;
|
|
378
|
+
} catch (err) {
|
|
379
|
+
handleBridgeError(err, opts?.onBridgeError);
|
|
380
|
+
return false;
|
|
381
|
+
}
|
|
323
382
|
}
|
|
324
383
|
// Annotate the CommonJS export names for ESM import in node:
|
|
325
384
|
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,41 @@ 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) return null;
|
|
32
|
+
if (typeof maxScore === "number" && maxScore > 0) {
|
|
33
|
+
return clamp01(score / maxScore);
|
|
34
|
+
}
|
|
35
|
+
if (score > 1 && score <= 100) {
|
|
36
|
+
return clamp01(score / 100);
|
|
37
|
+
}
|
|
38
|
+
return clamp01(score);
|
|
39
|
+
}
|
|
40
|
+
function normalizePassingThreshold(raw) {
|
|
41
|
+
const { passingScore, maxScore } = raw ?? {};
|
|
42
|
+
if (typeof passingScore !== "number" || !Number.isFinite(passingScore)) {
|
|
43
|
+
return DEFAULT_BRIDGE_PASSING_SCORE2;
|
|
44
|
+
}
|
|
45
|
+
if (typeof maxScore === "number" && maxScore <= 0) {
|
|
46
|
+
return DEFAULT_BRIDGE_PASSING_SCORE2;
|
|
47
|
+
}
|
|
48
|
+
if (typeof maxScore === "number" && maxScore > 1) {
|
|
49
|
+
return clamp01(passingScore / maxScore);
|
|
50
|
+
}
|
|
51
|
+
if (typeof maxScore === "number" && maxScore <= 1) {
|
|
52
|
+
return clamp01(passingScore);
|
|
53
|
+
}
|
|
54
|
+
if (passingScore > 1 && passingScore <= 100) {
|
|
55
|
+
return clamp01(passingScore / 100);
|
|
56
|
+
}
|
|
57
|
+
return clamp01(passingScore);
|
|
58
|
+
}
|
|
28
59
|
function normalizeAssessmentScore(opts) {
|
|
29
60
|
if (typeof opts.score !== "number" || !Number.isFinite(opts.score)) {
|
|
30
61
|
return null;
|
|
@@ -44,13 +75,7 @@ function resolveParentOrigin(parentWindow) {
|
|
|
44
75
|
try {
|
|
45
76
|
return parent.location.origin;
|
|
46
77
|
} catch {
|
|
47
|
-
|
|
48
|
-
if (!referrer) return null;
|
|
49
|
-
try {
|
|
50
|
-
return new URL(referrer).origin;
|
|
51
|
-
} catch {
|
|
52
|
-
return null;
|
|
53
|
-
}
|
|
78
|
+
return null;
|
|
54
79
|
}
|
|
55
80
|
}
|
|
56
81
|
function isProductionRuntime() {
|
|
@@ -141,13 +166,16 @@ function dispatchBridgeActionInner(bridge, action) {
|
|
|
141
166
|
return;
|
|
142
167
|
}
|
|
143
168
|
}
|
|
144
|
-
function forwardAssessmentCompletedToBridge(bridge, event) {
|
|
169
|
+
function forwardAssessmentCompletedToBridge(bridge, event, onBridgeMiss) {
|
|
145
170
|
const data = event.data;
|
|
146
171
|
const scaled = normalizeAssessmentScore({
|
|
147
172
|
score: data.score,
|
|
148
173
|
maxScore: data.maxScore
|
|
149
174
|
});
|
|
150
|
-
if (scaled === null)
|
|
175
|
+
if (scaled === null) {
|
|
176
|
+
onBridgeMiss?.(event);
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
151
179
|
bridge.submitAssessment?.({
|
|
152
180
|
id: data.checkId,
|
|
153
181
|
score: scaled,
|
|
@@ -167,7 +195,7 @@ function forwardTelemetryToBridge(event, mode = "auto", parentWindow, opts) {
|
|
|
167
195
|
if (!bridge) return;
|
|
168
196
|
try {
|
|
169
197
|
if (event.name === "assessment_completed") {
|
|
170
|
-
forwardAssessmentCompletedToBridge(bridge, event);
|
|
198
|
+
forwardAssessmentCompletedToBridge(bridge, event, opts?.onBridgeMiss);
|
|
171
199
|
return;
|
|
172
200
|
}
|
|
173
201
|
const answeredTrack = answeredTelemetryToBridgeTrackEvent(event);
|
|
@@ -194,20 +222,47 @@ function createLxpackBridge(opts) {
|
|
|
194
222
|
function notifyLxpackLessonComplete(lessonId, opts) {
|
|
195
223
|
const bridge = getBridge(void 0, opts);
|
|
196
224
|
if (!bridge?.completeLesson) return false;
|
|
197
|
-
|
|
198
|
-
|
|
225
|
+
try {
|
|
226
|
+
bridge.completeLesson(lessonId);
|
|
227
|
+
return true;
|
|
228
|
+
} catch (err) {
|
|
229
|
+
handleBridgeError(err, opts?.onBridgeError);
|
|
230
|
+
return false;
|
|
231
|
+
}
|
|
199
232
|
}
|
|
200
233
|
function notifyLxpackCourseComplete(opts) {
|
|
201
234
|
const bridge = getBridge(void 0, opts);
|
|
202
235
|
if (!bridge?.completeCourse) return false;
|
|
203
|
-
|
|
204
|
-
|
|
236
|
+
try {
|
|
237
|
+
bridge.completeCourse();
|
|
238
|
+
return true;
|
|
239
|
+
} catch (err) {
|
|
240
|
+
handleBridgeError(err, opts?.onBridgeError);
|
|
241
|
+
return false;
|
|
242
|
+
}
|
|
205
243
|
}
|
|
206
244
|
function notifyLxpackAssessment(payload, opts) {
|
|
207
245
|
const bridge = getBridge(void 0, opts);
|
|
208
246
|
if (!bridge?.submitAssessment) return false;
|
|
209
|
-
|
|
210
|
-
|
|
247
|
+
const scaled = normalizeAssessmentScore({
|
|
248
|
+
score: payload.score,
|
|
249
|
+
maxScore: payload.maxScore
|
|
250
|
+
});
|
|
251
|
+
if (scaled === null) return false;
|
|
252
|
+
try {
|
|
253
|
+
bridge.submitAssessment({
|
|
254
|
+
...payload,
|
|
255
|
+
score: scaled,
|
|
256
|
+
passingScore: normalizeAssessmentPassingScore({
|
|
257
|
+
passingScore: payload.passingScore,
|
|
258
|
+
maxScore: payload.maxScore
|
|
259
|
+
})
|
|
260
|
+
});
|
|
261
|
+
return true;
|
|
262
|
+
} catch (err) {
|
|
263
|
+
handleBridgeError(err, opts?.onBridgeError);
|
|
264
|
+
return false;
|
|
265
|
+
}
|
|
211
266
|
}
|
|
212
267
|
export {
|
|
213
268
|
BRANCH_TELEMETRY_EVENTS,
|
|
@@ -225,8 +280,8 @@ export {
|
|
|
225
280
|
mapLessonkitTelemetryToLxpack,
|
|
226
281
|
normalizeAssessmentPassingScore,
|
|
227
282
|
normalizeAssessmentScore,
|
|
228
|
-
|
|
229
|
-
|
|
283
|
+
normalizePassingThreshold,
|
|
284
|
+
normalizeScore,
|
|
230
285
|
notifyLxpackAssessment,
|
|
231
286
|
notifyLxpackCourseComplete,
|
|
232
287
|
notifyLxpackLessonComplete,
|