@lessonkit/lxpack 1.4.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 +49 -7
- package/block-tree.v1.json +40 -0
- package/dist/bridge.cjs +229 -55
- package/dist/bridge.d.cts +59 -11
- package/dist/bridge.d.ts +59 -11
- package/dist/bridge.js +162 -42
- package/dist/chunk-HTZR4CF3.js +94 -0
- package/dist/index.cjs +1527 -209
- package/dist/index.d.cts +184 -5
- package/dist/index.d.ts +184 -5
- package/dist/index.js +1472 -183
- package/dist/telemetry-0fIWoomS.d.cts +17 -0
- package/dist/telemetry-0fIWoomS.d.ts +17 -0
- package/lessonkit-manifest.v1.json +6 -3
- package/lkcourse-format.v1.json +21 -0
- package/package.json +14 -8
- package/dist/chunk-DYQI222N.js +0 -41
- package/dist/telemetry-gCxlwc7I.d.cts +0 -9
- package/dist/telemetry-gCxlwc7I.d.ts +0 -9
package/README.md
CHANGED
|
@@ -4,9 +4,15 @@
|
|
|
4
4
|
[](https://lessonkit.readthedocs.io/en/latest/reference/packaging.html)
|
|
5
5
|
[](https://github.com/eddiethedean/lessonkit/blob/main/LICENSE)
|
|
6
6
|
|
|
7
|
-
Package Vite SPAs for LMS delivery — SCORM 1.2/2004, standalone, xAPI, and cmi5.
|
|
7
|
+
Package Vite SPAs for LMS delivery — SCORM 1.2/2004, standalone, xAPI, and cmi5. Bundles [`@lxpack/*`](https://www.npmjs.com/org/lxpack) as direct dependencies.
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
## When to install
|
|
10
|
+
|
|
11
|
+
- Custom packaging pipelines without the CLI
|
|
12
|
+
- Validating `lessonkit.json` / course descriptors in CI
|
|
13
|
+
- Programmatic LMS export from your own build tools
|
|
14
|
+
|
|
15
|
+
Most authors use `lessonkit package` (CLI) which calls this package internally.
|
|
10
16
|
|
|
11
17
|
## Install
|
|
12
18
|
|
|
@@ -14,13 +20,18 @@ Requires Node.js **18+**.
|
|
|
14
20
|
npm install @lessonkit/lxpack
|
|
15
21
|
```
|
|
16
22
|
|
|
23
|
+
Requires Node.js **18+** minimum; **20.19+** recommended for CLI scaffold workflows.
|
|
24
|
+
|
|
17
25
|
## Usage
|
|
18
26
|
|
|
19
27
|
```typescript
|
|
20
|
-
import { packageLessonkitCourse } from "@lessonkit/lxpack";
|
|
28
|
+
import { packageLessonkitCourse, parseLessonkitManifest } from "@lessonkit/lxpack";
|
|
29
|
+
|
|
30
|
+
const manifest = parseLessonkitManifest(await readFile("lessonkit.json", "utf8"));
|
|
31
|
+
if (!manifest.ok) throw manifest.error;
|
|
21
32
|
|
|
22
33
|
const result = await packageLessonkitCourse({
|
|
23
|
-
descriptor:
|
|
34
|
+
descriptor: manifest.value,
|
|
24
35
|
outDir: ".lxpack/course",
|
|
25
36
|
spaDistDir: "dist",
|
|
26
37
|
target: "scorm12",
|
|
@@ -30,19 +41,50 @@ const result = await packageLessonkitCourse({
|
|
|
30
41
|
if (!result.ok) throw new Error("packaging failed");
|
|
31
42
|
```
|
|
32
43
|
|
|
33
|
-
Prefer the CLI: `lessonkit package --target scorm12` reads `lessonkit.json` and runs the same pipeline.
|
|
44
|
+
Prefer the CLI: `lessonkit package --target scorm12` reads `lessonkit.json` and runs the same staged pipeline.
|
|
45
|
+
|
|
46
|
+
## Layouts
|
|
47
|
+
|
|
48
|
+
| Layout | Use case |
|
|
49
|
+
| --- | --- |
|
|
50
|
+
| `single-spa` | One Vite SPA for the whole course (CLI default) |
|
|
51
|
+
| `per-lesson-spa` | One dist per lesson (advanced; see packaging reference) |
|
|
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).
|
|
34
66
|
|
|
35
67
|
## Browser bridge
|
|
36
68
|
|
|
37
|
-
When embedded in an LXPack iframe, `@lessonkit/react` forwards completion events to `window.parent.lxpackBridge.v1
|
|
69
|
+
When embedded in an LXPack iframe, `@lessonkit/react` forwards completion events to `window.parent.lxpackBridge.v1`:
|
|
38
70
|
|
|
39
71
|
```typescript
|
|
40
72
|
import { forwardTelemetryToBridge } from "@lessonkit/lxpack/bridge";
|
|
41
73
|
```
|
|
42
74
|
|
|
75
|
+
Production builds require `allowedParentOrigins` when `bridge: "auto"`.
|
|
76
|
+
|
|
77
|
+
## Common issues
|
|
78
|
+
|
|
79
|
+
| Symptom | Fix |
|
|
80
|
+
| --- | --- |
|
|
81
|
+
| React/manifest ID mismatch | Run strict parity validation; align IDs in `App.tsx` and `lessonkit.json` |
|
|
82
|
+
| xAPI/cmi5 validation failure | Set HTTPS `activityIri` in manifest |
|
|
83
|
+
| Empty `dist/` | Run `lessonkit build` before `package` (or omit `--no-build`) |
|
|
84
|
+
|
|
43
85
|
## Docs
|
|
44
86
|
|
|
45
|
-
[Packaging reference](https://lessonkit.readthedocs.io/en/latest/reference/packaging.html) · [LXPack bridge](https://lessonkit.readthedocs.io/en/latest/reference/lxpack-bridge.html) · [Golden example](https://github.com/eddiethedean/lessonkit/tree/main/examples/lxpack-golden)
|
|
87
|
+
[Packaging reference](https://lessonkit.readthedocs.io/en/latest/reference/packaging.html) · [LXPack bridge](https://lessonkit.readthedocs.io/en/latest/reference/lxpack-bridge.html) · [Manifest](https://lessonkit.readthedocs.io/en/latest/reference/manifest.html) · [Golden example](https://github.com/eddiethedean/lessonkit/tree/main/examples/lxpack-golden) · [TypeDoc API index](https://lessonkit.readthedocs.io/en/latest/reference/api.html)
|
|
46
88
|
|
|
47
89
|
## License
|
|
48
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
|
@@ -20,23 +20,27 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
20
20
|
// src/bridge.ts
|
|
21
21
|
var bridge_exports = {};
|
|
22
22
|
__export(bridge_exports, {
|
|
23
|
+
BRANCH_TELEMETRY_EVENTS: () => BRANCH_TELEMETRY_EVENTS,
|
|
23
24
|
DEFAULT_BRIDGE_PASSING_SCORE: () => import_spa_bridge2.DEFAULT_BRIDGE_PASSING_SCORE,
|
|
24
25
|
LESSONKIT_TELEMETRY_EVENTS: () => import_tracking_schema2.LESSONKIT_TELEMETRY_EVENTS,
|
|
25
26
|
LXPACK_BRIDGE_VERSIONS: () => import_spa_bridge2.LXPACK_BRIDGE_VERSIONS,
|
|
27
|
+
branchTelemetryToBridgeTrackEvent: () => branchTelemetryToBridgeTrackEvent,
|
|
26
28
|
createLxpackBridge: () => createLxpackBridge,
|
|
27
29
|
createLxpackBridgeHost: () => import_spa_bridge2.createLxpackBridgeHost,
|
|
28
30
|
dispatchBridgeAction: () => dispatchBridgeAction,
|
|
29
31
|
forwardTelemetryToBridge: () => forwardTelemetryToBridge,
|
|
30
|
-
getLxpackBridge: () =>
|
|
32
|
+
getLxpackBridge: () => getLxpackBridge,
|
|
33
|
+
isParentOriginAllowed: () => isParentOriginAllowed,
|
|
31
34
|
mapLessonkitTelemetryToBridgeAction: () => import_tracking_schema2.mapLessonkitTelemetryToBridgeAction,
|
|
32
35
|
mapLessonkitTelemetryToLxpack: () => import_tracking_schema2.mapLessonkitTelemetryToLxpack,
|
|
33
36
|
normalizeAssessmentPassingScore: () => normalizeAssessmentPassingScore,
|
|
34
37
|
normalizeAssessmentScore: () => normalizeAssessmentScore,
|
|
35
|
-
normalizePassingThreshold: () =>
|
|
36
|
-
normalizeScore: () =>
|
|
38
|
+
normalizePassingThreshold: () => normalizePassingThreshold,
|
|
39
|
+
normalizeScore: () => normalizeScore,
|
|
37
40
|
notifyLxpackAssessment: () => notifyLxpackAssessment,
|
|
38
41
|
notifyLxpackCourseComplete: () => notifyLxpackCourseComplete,
|
|
39
42
|
notifyLxpackLessonComplete: () => notifyLxpackLessonComplete,
|
|
43
|
+
resolveParentOrigin: () => resolveParentOrigin,
|
|
40
44
|
supportedBridgeVersions: () => import_spa_bridge2.supportedBridgeVersions,
|
|
41
45
|
telemetryEventToLessonkit: () => telemetryEventToLessonkit
|
|
42
46
|
});
|
|
@@ -48,56 +52,163 @@ var import_tracking_schema3 = require("@lxpack/tracking-schema");
|
|
|
48
52
|
|
|
49
53
|
// src/telemetry.ts
|
|
50
54
|
var import_tracking_schema = require("@lxpack/tracking-schema");
|
|
51
|
-
var
|
|
55
|
+
var BRANCH_TELEMETRY_EVENTS = ["branch_node_viewed", "branch_selected"];
|
|
56
|
+
var ASSESSMENT_TELEMETRY_EVENTS = ["assessment_answered"];
|
|
57
|
+
var SUPPORTED = /* @__PURE__ */ new Set([
|
|
58
|
+
...import_tracking_schema.LESSONKIT_TELEMETRY_EVENTS,
|
|
59
|
+
...BRANCH_TELEMETRY_EVENTS,
|
|
60
|
+
...ASSESSMENT_TELEMETRY_EVENTS
|
|
61
|
+
]);
|
|
52
62
|
function isQuizAnsweredData(data) {
|
|
53
|
-
return typeof data === "object" && data !== null && typeof data.checkId === "string";
|
|
63
|
+
return typeof data === "object" && data !== null && typeof data.checkId === "string" && data.checkId.length > 0;
|
|
54
64
|
}
|
|
55
65
|
function isQuizCompletedData(data) {
|
|
56
|
-
return typeof data === "object" && data !== null && typeof data.checkId === "string";
|
|
66
|
+
return typeof data === "object" && data !== null && typeof data.checkId === "string" && data.checkId.length > 0;
|
|
67
|
+
}
|
|
68
|
+
function isAssessmentAnsweredData(data) {
|
|
69
|
+
return typeof data === "object" && data !== null && typeof data.checkId === "string" && data.checkId.length > 0;
|
|
57
70
|
}
|
|
58
71
|
function isInteractionData(data) {
|
|
59
72
|
return typeof data === "object" && data !== null;
|
|
60
73
|
}
|
|
74
|
+
function isBranchNodeViewedData(data) {
|
|
75
|
+
return typeof data === "object" && data !== null && typeof data.blockId === "string" && typeof data.nodeId === "string";
|
|
76
|
+
}
|
|
77
|
+
function isBranchSelectedData(data) {
|
|
78
|
+
return typeof data === "object" && data !== null && typeof data.blockId === "string" && typeof data.fromNodeId === "string" && typeof data.toNodeId === "string";
|
|
79
|
+
}
|
|
61
80
|
function telemetryEventToLessonkit(event) {
|
|
62
81
|
if (!SUPPORTED.has(event.name)) {
|
|
63
82
|
return null;
|
|
64
83
|
}
|
|
65
|
-
const name = event.name;
|
|
66
84
|
const mapped = {
|
|
67
|
-
name,
|
|
85
|
+
name: event.name,
|
|
68
86
|
lessonId: event.lessonId
|
|
69
87
|
};
|
|
70
|
-
if (name === "quiz_completed" || name === "quiz_answered") {
|
|
88
|
+
if (event.name === "quiz_completed" || event.name === "quiz_answered" || event.name === "assessment_answered") {
|
|
71
89
|
const data = event.data;
|
|
72
|
-
if (isQuizAnsweredData(data)
|
|
73
|
-
|
|
74
|
-
if ("score" in data) {
|
|
75
|
-
mapped.score = data.score;
|
|
76
|
-
mapped.maxScore = data.maxScore;
|
|
77
|
-
mapped.passingScore = data.passingScore;
|
|
78
|
-
}
|
|
79
|
-
mapped.data = data;
|
|
90
|
+
if (!isQuizAnsweredData(data) && !isQuizCompletedData(data) && !isAssessmentAnsweredData(data)) {
|
|
91
|
+
return null;
|
|
80
92
|
}
|
|
81
|
-
|
|
93
|
+
mapped.assessmentId = data.checkId;
|
|
94
|
+
if ("score" in data) {
|
|
95
|
+
mapped.score = data.score;
|
|
96
|
+
mapped.maxScore = data.maxScore;
|
|
97
|
+
mapped.passingScore = data.passingScore;
|
|
98
|
+
}
|
|
99
|
+
mapped.data = data;
|
|
100
|
+
} else if (mapped.name === "interaction" && event.data && isInteractionData(event.data)) {
|
|
101
|
+
mapped.data = event.data;
|
|
102
|
+
} else if (event.name === "branch_node_viewed" && isBranchNodeViewedData(event.data)) {
|
|
103
|
+
mapped.data = event.data;
|
|
104
|
+
} else if (event.name === "branch_selected" && isBranchSelectedData(event.data)) {
|
|
82
105
|
mapped.data = event.data;
|
|
83
106
|
}
|
|
84
107
|
return mapped;
|
|
85
108
|
}
|
|
109
|
+
function answeredTelemetryToBridgeTrackEvent(event) {
|
|
110
|
+
if (event.name !== "quiz_answered" && event.name !== "assessment_answered") {
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
const lessonkitEvent = telemetryEventToLessonkit(event);
|
|
114
|
+
if (!lessonkitEvent?.assessmentId) return null;
|
|
115
|
+
return (0, import_tracking_schema.mapLessonkitTelemetryToLxpack)({
|
|
116
|
+
...lessonkitEvent,
|
|
117
|
+
name: "quiz_answered"
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
function branchTelemetryToBridgeTrackEvent(event) {
|
|
121
|
+
if (event.name === "branch_node_viewed" && isBranchNodeViewedData(event.data)) {
|
|
122
|
+
return {
|
|
123
|
+
type: "interaction",
|
|
124
|
+
id: "branch_node_viewed",
|
|
125
|
+
data: { ...event.data, lessonkitEvent: event.name }
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
if (event.name === "branch_selected" && isBranchSelectedData(event.data)) {
|
|
129
|
+
return {
|
|
130
|
+
type: "interaction",
|
|
131
|
+
id: "branch_selected",
|
|
132
|
+
data: { ...event.data, lessonkitEvent: event.name }
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
return null;
|
|
136
|
+
}
|
|
86
137
|
|
|
87
138
|
// src/bridge.ts
|
|
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
|
+
}
|
|
88
171
|
function normalizeAssessmentScore(opts) {
|
|
89
172
|
if (typeof opts.score !== "number" || !Number.isFinite(opts.score)) {
|
|
90
173
|
return null;
|
|
91
174
|
}
|
|
92
|
-
return
|
|
175
|
+
return normalizeScore({ score: opts.score, maxScore: opts.maxScore });
|
|
93
176
|
}
|
|
94
177
|
function normalizeAssessmentPassingScore(opts) {
|
|
95
|
-
return
|
|
178
|
+
return normalizePassingThreshold({
|
|
96
179
|
passingScore: opts?.passingScore,
|
|
97
180
|
maxScore: opts?.maxScore
|
|
98
181
|
});
|
|
99
182
|
}
|
|
100
|
-
function
|
|
183
|
+
function resolveParentOrigin(parentWindow) {
|
|
184
|
+
if (typeof window === "undefined") return null;
|
|
185
|
+
const parent = parentWindow ?? window.parent;
|
|
186
|
+
if (!parent || parent === window) return null;
|
|
187
|
+
try {
|
|
188
|
+
return parent.location.origin;
|
|
189
|
+
} catch {
|
|
190
|
+
return null;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
function isProductionRuntime() {
|
|
194
|
+
try {
|
|
195
|
+
if (import_meta.env?.PROD === true) return true;
|
|
196
|
+
} catch {
|
|
197
|
+
}
|
|
198
|
+
const g = globalThis;
|
|
199
|
+
return typeof g.process !== "undefined" && g.process.env?.NODE_ENV === "production";
|
|
200
|
+
}
|
|
201
|
+
function isParentOriginAllowed(allowedParentOrigins, parentWindow, mode) {
|
|
202
|
+
if (mode === "off") return false;
|
|
203
|
+
if (isProductionRuntime() && !allowedParentOrigins?.length) return false;
|
|
204
|
+
if (!allowedParentOrigins?.length) return true;
|
|
205
|
+
const origin = resolveParentOrigin(parentWindow);
|
|
206
|
+
if (!origin) return false;
|
|
207
|
+
return allowedParentOrigins.includes(origin);
|
|
208
|
+
}
|
|
209
|
+
function getBridge(parentWindow, opts) {
|
|
210
|
+
const mode = opts?.mode ?? "auto";
|
|
211
|
+
if (!isParentOriginAllowed(opts?.allowedParentOrigins, parentWindow, mode)) return null;
|
|
101
212
|
const fromSdk = (0, import_spa_bridge.getLxpackBridge)(parentWindow);
|
|
102
213
|
if (fromSdk) return fromSdk;
|
|
103
214
|
if (typeof window === "undefined") return null;
|
|
@@ -105,21 +216,33 @@ function getBridge(parentWindow) {
|
|
|
105
216
|
if (!parent || parent === window) return null;
|
|
106
217
|
return parent.lxpackBridge?.v1 ?? parent.lxpack ?? null;
|
|
107
218
|
}
|
|
219
|
+
function getLxpackBridge(parentWindow, opts) {
|
|
220
|
+
return getBridge(parentWindow, opts);
|
|
221
|
+
}
|
|
108
222
|
function isDevEnvironment() {
|
|
223
|
+
try {
|
|
224
|
+
if (import_meta.env?.DEV === true) return true;
|
|
225
|
+
if (import_meta.env?.PROD === true) return false;
|
|
226
|
+
} catch {
|
|
227
|
+
}
|
|
109
228
|
const g = globalThis;
|
|
110
229
|
return typeof g.process !== "undefined" && g.process.env?.NODE_ENV !== "production";
|
|
111
230
|
}
|
|
112
|
-
function
|
|
231
|
+
function handleBridgeError(err, onBridgeError) {
|
|
232
|
+
onBridgeError?.(err);
|
|
233
|
+
if (isDevEnvironment()) {
|
|
234
|
+
console.warn(
|
|
235
|
+
"[lessonkit/lxpack] lxpack bridge action failed:",
|
|
236
|
+
err instanceof Error ? err.message : err
|
|
237
|
+
);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
function dispatchBridgeAction(bridge, action, opts) {
|
|
113
241
|
if (!action) return;
|
|
114
242
|
try {
|
|
115
243
|
dispatchBridgeActionInner(bridge, action);
|
|
116
244
|
} catch (err) {
|
|
117
|
-
|
|
118
|
-
console.warn(
|
|
119
|
-
"[lessonkit/lxpack] lxpack bridge action failed:",
|
|
120
|
-
err instanceof Error ? err.message : err
|
|
121
|
-
);
|
|
122
|
-
}
|
|
245
|
+
handleBridgeError(err, opts?.onBridgeError);
|
|
123
246
|
}
|
|
124
247
|
}
|
|
125
248
|
function dispatchBridgeActionInner(bridge, action) {
|
|
@@ -132,7 +255,7 @@ function dispatchBridgeActionInner(bridge, action) {
|
|
|
132
255
|
bridge.completeCourse?.();
|
|
133
256
|
return;
|
|
134
257
|
case "submitAssessment": {
|
|
135
|
-
const scaled =
|
|
258
|
+
const scaled = normalizeScore({
|
|
136
259
|
score: action.score,
|
|
137
260
|
maxScore: action.maxScore
|
|
138
261
|
});
|
|
@@ -140,7 +263,7 @@ function dispatchBridgeActionInner(bridge, action) {
|
|
|
140
263
|
bridge.submitAssessment?.({
|
|
141
264
|
id: action.id,
|
|
142
265
|
score: scaled,
|
|
143
|
-
passingScore:
|
|
266
|
+
passingScore: normalizePassingThreshold({
|
|
144
267
|
passingScore: action.passingScore,
|
|
145
268
|
maxScore: action.maxScore
|
|
146
269
|
}),
|
|
@@ -155,13 +278,16 @@ function dispatchBridgeActionInner(bridge, action) {
|
|
|
155
278
|
return;
|
|
156
279
|
}
|
|
157
280
|
}
|
|
158
|
-
function forwardAssessmentCompletedToBridge(bridge, event) {
|
|
281
|
+
function forwardAssessmentCompletedToBridge(bridge, event, onBridgeMiss) {
|
|
159
282
|
const data = event.data;
|
|
160
283
|
const scaled = normalizeAssessmentScore({
|
|
161
284
|
score: data.score,
|
|
162
285
|
maxScore: data.maxScore
|
|
163
286
|
});
|
|
164
|
-
if (scaled === null)
|
|
287
|
+
if (scaled === null) {
|
|
288
|
+
onBridgeMiss?.(event);
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
165
291
|
bridge.submitAssessment?.({
|
|
166
292
|
id: data.checkId,
|
|
167
293
|
score: scaled,
|
|
@@ -172,50 +298,97 @@ function forwardAssessmentCompletedToBridge(bridge, event) {
|
|
|
172
298
|
maxScore: data.maxScore
|
|
173
299
|
});
|
|
174
300
|
}
|
|
175
|
-
function forwardTelemetryToBridge(event, mode = "auto", parentWindow) {
|
|
301
|
+
function forwardTelemetryToBridge(event, mode = "auto", parentWindow, opts) {
|
|
176
302
|
if (mode === "off") return;
|
|
177
|
-
const bridge = getBridge(parentWindow
|
|
303
|
+
const bridge = getBridge(parentWindow, {
|
|
304
|
+
allowedParentOrigins: opts?.allowedParentOrigins,
|
|
305
|
+
mode
|
|
306
|
+
});
|
|
178
307
|
if (!bridge) return;
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
308
|
+
try {
|
|
309
|
+
if (event.name === "assessment_completed") {
|
|
310
|
+
forwardAssessmentCompletedToBridge(bridge, event, opts?.onBridgeMiss);
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
const answeredTrack = answeredTelemetryToBridgeTrackEvent(event);
|
|
314
|
+
if (answeredTrack) {
|
|
315
|
+
bridge.track?.(answeredTrack);
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
const branchTrack = branchTelemetryToBridgeTrackEvent(event);
|
|
319
|
+
if (branchTrack) {
|
|
320
|
+
bridge.track?.(branchTrack);
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
const lessonkitEvent = telemetryEventToLessonkit(event);
|
|
324
|
+
if (!lessonkitEvent) return;
|
|
325
|
+
const action = (0, import_tracking_schema3.mapLessonkitTelemetryToBridgeAction)(lessonkitEvent);
|
|
326
|
+
dispatchBridgeActionInner(bridge, action);
|
|
327
|
+
} catch (err) {
|
|
328
|
+
handleBridgeError(err, opts?.onBridgeError);
|
|
182
329
|
}
|
|
183
|
-
const lessonkitEvent = telemetryEventToLessonkit(event);
|
|
184
|
-
if (!lessonkitEvent) return;
|
|
185
|
-
const action = (0, import_tracking_schema3.mapLessonkitTelemetryToBridgeAction)(lessonkitEvent);
|
|
186
|
-
dispatchBridgeAction(bridge, action);
|
|
187
330
|
}
|
|
188
|
-
function createLxpackBridge() {
|
|
189
|
-
return getBridge();
|
|
331
|
+
function createLxpackBridge(opts) {
|
|
332
|
+
return getBridge(void 0, opts);
|
|
190
333
|
}
|
|
191
|
-
function notifyLxpackLessonComplete(lessonId) {
|
|
192
|
-
const bridge = getBridge();
|
|
334
|
+
function notifyLxpackLessonComplete(lessonId, opts) {
|
|
335
|
+
const bridge = getBridge(void 0, opts);
|
|
193
336
|
if (!bridge?.completeLesson) return false;
|
|
194
|
-
|
|
195
|
-
|
|
337
|
+
try {
|
|
338
|
+
bridge.completeLesson(lessonId);
|
|
339
|
+
return true;
|
|
340
|
+
} catch (err) {
|
|
341
|
+
handleBridgeError(err, opts?.onBridgeError);
|
|
342
|
+
return false;
|
|
343
|
+
}
|
|
196
344
|
}
|
|
197
|
-
function notifyLxpackCourseComplete() {
|
|
198
|
-
const bridge = getBridge();
|
|
345
|
+
function notifyLxpackCourseComplete(opts) {
|
|
346
|
+
const bridge = getBridge(void 0, opts);
|
|
199
347
|
if (!bridge?.completeCourse) return false;
|
|
200
|
-
|
|
201
|
-
|
|
348
|
+
try {
|
|
349
|
+
bridge.completeCourse();
|
|
350
|
+
return true;
|
|
351
|
+
} catch (err) {
|
|
352
|
+
handleBridgeError(err, opts?.onBridgeError);
|
|
353
|
+
return false;
|
|
354
|
+
}
|
|
202
355
|
}
|
|
203
|
-
function notifyLxpackAssessment(payload) {
|
|
204
|
-
const bridge = getBridge();
|
|
356
|
+
function notifyLxpackAssessment(payload, opts) {
|
|
357
|
+
const bridge = getBridge(void 0, opts);
|
|
205
358
|
if (!bridge?.submitAssessment) return false;
|
|
206
|
-
|
|
207
|
-
|
|
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
|
+
}
|
|
208
378
|
}
|
|
209
379
|
// Annotate the CommonJS export names for ESM import in node:
|
|
210
380
|
0 && (module.exports = {
|
|
381
|
+
BRANCH_TELEMETRY_EVENTS,
|
|
211
382
|
DEFAULT_BRIDGE_PASSING_SCORE,
|
|
212
383
|
LESSONKIT_TELEMETRY_EVENTS,
|
|
213
384
|
LXPACK_BRIDGE_VERSIONS,
|
|
385
|
+
branchTelemetryToBridgeTrackEvent,
|
|
214
386
|
createLxpackBridge,
|
|
215
387
|
createLxpackBridgeHost,
|
|
216
388
|
dispatchBridgeAction,
|
|
217
389
|
forwardTelemetryToBridge,
|
|
218
390
|
getLxpackBridge,
|
|
391
|
+
isParentOriginAllowed,
|
|
219
392
|
mapLessonkitTelemetryToBridgeAction,
|
|
220
393
|
mapLessonkitTelemetryToLxpack,
|
|
221
394
|
normalizeAssessmentPassingScore,
|
|
@@ -225,6 +398,7 @@ function notifyLxpackAssessment(payload) {
|
|
|
225
398
|
notifyLxpackAssessment,
|
|
226
399
|
notifyLxpackCourseComplete,
|
|
227
400
|
notifyLxpackLessonComplete,
|
|
401
|
+
resolveParentOrigin,
|
|
228
402
|
supportedBridgeVersions,
|
|
229
403
|
telemetryEventToLessonkit
|
|
230
404
|
});
|
package/dist/bridge.d.cts
CHANGED
|
@@ -1,9 +1,23 @@
|
|
|
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
|
-
export { t as telemetryEventToLessonkit } from './telemetry-
|
|
6
|
+
export { B as BRANCH_TELEMETRY_EVENTS, b as branchTelemetryToBridgeTrackEvent, t as telemetryEventToLessonkit } from './telemetry-0fIWoomS.cjs';
|
|
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;
|
|
7
21
|
|
|
8
22
|
/**
|
|
9
23
|
* Scale a raw quiz score to 0–1 for the LXPack parent bridge.
|
|
@@ -15,26 +29,60 @@ 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;
|
|
22
36
|
maxScore?: number;
|
|
23
37
|
}): number;
|
|
38
|
+
type BridgeAccessOptions = {
|
|
39
|
+
/** Allowed parent-frame origins (scheme + host + port). When set, bridge calls require a matching origin. */
|
|
40
|
+
allowedParentOrigins?: string[];
|
|
41
|
+
/** LMS bridge mode; `"auto"` in production requires `allowedParentOrigins`. */
|
|
42
|
+
mode?: LxpackBridgeMode;
|
|
43
|
+
onBridgeError?: (err: unknown) => void;
|
|
44
|
+
};
|
|
45
|
+
/** Resolve the parent frame origin when embedded (same-origin parent or document.referrer fallback). */
|
|
46
|
+
declare function resolveParentOrigin(parentWindow?: Window): string | null;
|
|
47
|
+
/** Returns true when no allowlist is configured or the resolved parent origin is listed. */
|
|
48
|
+
declare function isParentOriginAllowed(allowedParentOrigins: string[] | undefined, parentWindow?: Window, mode?: LxpackBridgeMode): boolean;
|
|
49
|
+
/** Resolve the LXPack parent bridge when the parent origin passes validation. */
|
|
50
|
+
declare function getLxpackBridge(parentWindow?: Window, opts?: BridgeAccessOptions): LxpackBridgeV1 | null;
|
|
24
51
|
/** @deprecated Use `LmsBridgeMode` from `@lessonkit/core`. */
|
|
25
52
|
type LxpackBridgeMode = LmsBridgeMode;
|
|
26
53
|
/** Apply a mapped bridge action to an LXPack bridge instance. */
|
|
27
|
-
declare function dispatchBridgeAction(bridge: LxpackBridgeV1, action: ReturnType<typeof mapLessonkitTelemetryToBridgeAction
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
54
|
+
declare function dispatchBridgeAction(bridge: LxpackBridgeV1, action: ReturnType<typeof mapLessonkitTelemetryToBridgeAction>, opts?: {
|
|
55
|
+
onBridgeError?: (err: unknown) => void;
|
|
56
|
+
}): void;
|
|
57
|
+
type ForwardTelemetryToBridgeOptions = {
|
|
58
|
+
onBridgeError?: (err: unknown) => void;
|
|
59
|
+
/** Called when assessment_completed cannot be forwarded (e.g. missing/invalid score). */
|
|
60
|
+
onBridgeMiss?: (event: TelemetryEvent) => void;
|
|
61
|
+
allowedParentOrigins?: string[];
|
|
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
|
+
*/
|
|
76
|
+
declare function forwardTelemetryToBridge(event: TelemetryEvent, mode?: LxpackBridgeMode, parentWindow?: Window, opts?: ForwardTelemetryToBridgeOptions): void;
|
|
77
|
+
declare function createLxpackBridge(opts?: BridgeAccessOptions): LxpackBridgeV1 | null;
|
|
78
|
+
declare function notifyLxpackLessonComplete(lessonId: LessonId, opts?: BridgeAccessOptions): boolean;
|
|
79
|
+
declare function notifyLxpackCourseComplete(opts?: BridgeAccessOptions): boolean;
|
|
32
80
|
/**
|
|
33
81
|
* Submit assessment results to the parent LXPack bridge.
|
|
34
|
-
*
|
|
82
|
+
* Raw point scores are normalized to 0–1 before submission.
|
|
35
83
|
*/
|
|
36
84
|
declare function notifyLxpackAssessment(payload: LxpackBridgeSubmitAssessmentPayload & {
|
|
37
85
|
id: CheckId;
|
|
38
|
-
}): boolean;
|
|
86
|
+
}, opts?: BridgeAccessOptions): boolean;
|
|
39
87
|
|
|
40
|
-
export { type LxpackBridgeMode, createLxpackBridge, dispatchBridgeAction, forwardTelemetryToBridge, normalizeAssessmentPassingScore, normalizeAssessmentScore, notifyLxpackAssessment, notifyLxpackCourseComplete, notifyLxpackLessonComplete };
|
|
88
|
+
export { type BridgeAccessOptions, type ForwardTelemetryToBridgeOptions, type LxpackBridgeMode, createLxpackBridge, dispatchBridgeAction, forwardTelemetryToBridge, getLxpackBridge, isParentOriginAllowed, normalizeAssessmentPassingScore, normalizeAssessmentScore, normalizePassingThreshold, normalizeScore, notifyLxpackAssessment, notifyLxpackCourseComplete, notifyLxpackLessonComplete, resolveParentOrigin };
|