@lessonkit/lxpack 0.9.3 → 1.0.1

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 CHANGED
@@ -1,14 +1,12 @@
1
1
  # @lessonkit/lxpack
2
2
 
3
- [![Documentation](https://readthedocs.org/projects/lessonkit/badge/?version=latest)](https://lessonkit.readthedocs.io/en/latest/)
4
3
  [![npm](https://img.shields.io/npm/v/@lessonkit/lxpack.svg)](https://www.npmjs.com/package/@lessonkit/lxpack)
4
+ [![Documentation](https://readthedocs.org/projects/lessonkit/badge/?version=latest)](https://lessonkit.readthedocs.io/en/latest/reference/packaging.html)
5
5
  [![License](https://img.shields.io/github/license/eddiethedean/lessonkit)](https://github.com/eddiethedean/lessonkit/blob/main/LICENSE)
6
6
 
7
- LXPack export adapter for LessonKitwrite `lessonkit.json` + `course.yaml`, copy SPA builds, and package to SCORM / standalone / xAPI / cmi5 via [`@lxpack/api`](https://www.npmjs.com/package/@lxpack/api).
7
+ Package Vite SPAs for LMS delivery SCORM 1.2/2004, standalone, xAPI, and cmi5 via [`@lxpack/api`](https://www.npmjs.com/package/@lxpack/api).
8
8
 
9
- Requires **Node.js 20+**.
10
-
11
- **Docs:** [Packaging reference](https://lessonkit.readthedocs.io/en/latest/reference/packaging.html) · [Packaging & CLI guide](https://lessonkit.readthedocs.io/en/latest/guides/react-developers/packaging-and-cli.html) · [Live examples](https://lessonkit.readthedocs.io/en/latest/examples/index.html)
9
+ Requires Node.js **18+**.
12
10
 
13
11
  ## Install
14
12
 
@@ -16,14 +14,13 @@ Requires **Node.js 20+**.
16
14
  npm install @lessonkit/lxpack @lxpack/api
17
15
  ```
18
16
 
19
- ## Quick start
17
+ ## Usage
20
18
 
21
- ```ts
19
+ ```typescript
22
20
  import { packageLessonkitCourse } from "@lessonkit/lxpack";
23
- import { goldenCourseDescriptor } from "./course.descriptor";
24
21
 
25
22
  const result = await packageLessonkitCourse({
26
- descriptor: goldenCourseDescriptor,
23
+ descriptor: courseDescriptor,
27
24
  outDir: ".lxpack/course",
28
25
  spaDistDir: "dist",
29
26
  target: "scorm12",
@@ -33,16 +30,20 @@ const result = await packageLessonkitCourse({
33
30
  if (!result.ok) throw new Error("packaging failed");
34
31
  ```
35
32
 
36
- See the [packaging reference](https://lessonkit.readthedocs.io/en/latest/reference/packaging.html) and the [`examples/lxpack-golden`](https://github.com/eddiethedean/lessonkit/tree/main/examples/lxpack-golden) course.
33
+ Prefer the CLI: `lessonkit package --target scorm12` reads `lessonkit.json` and runs the same pipeline.
37
34
 
38
35
  ## Browser bridge
39
36
 
40
- When your React app runs inside an LXPack iframe:
37
+ When embedded in an LXPack iframe, `@lessonkit/react` forwards completion events to `window.parent.lxpackBridge.v1`. Direct API:
41
38
 
42
- ```ts
43
- import { notifyLxpackLessonComplete } from "@lessonkit/lxpack/bridge";
39
+ ```typescript
40
+ import { forwardTelemetryToBridge } from "@lessonkit/lxpack/bridge";
44
41
  ```
45
42
 
46
- `@lessonkit/react` forwards `lesson_completed`, `course_completed`, and `quiz_completed` automatically when `window.parent.lxpackBridge.v1` is present (`config.lxpack.bridge: "off"` to disable).
43
+ ## Docs
44
+
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)
46
+
47
+ ## License
47
48
 
48
- For interoperability notes, see [LXPack upgrades](https://lessonkit.readthedocs.io/en/latest/reference/lxpack-upgrades.html). LXPack maintainers: [upgrade plan for maintainers](https://github.com/eddiethedean/lessonkit/blob/main/docs/LXPACK_UPGRADE_PLAN_FOR_MAINTAINERS.md).
49
+ Apache-2.0
package/dist/bridge.cjs CHANGED
@@ -25,6 +25,8 @@ __export(bridge_exports, {
25
25
  LXPACK_BRIDGE_VERSIONS: () => import_spa_bridge2.LXPACK_BRIDGE_VERSIONS,
26
26
  createLxpackBridge: () => createLxpackBridge,
27
27
  createLxpackBridgeHost: () => import_spa_bridge2.createLxpackBridgeHost,
28
+ dispatchBridgeAction: () => dispatchBridgeAction,
29
+ forwardTelemetryToBridge: () => forwardTelemetryToBridge,
28
30
  getLxpackBridge: () => import_spa_bridge2.getLxpackBridge,
29
31
  mapLessonkitTelemetryToBridgeAction: () => import_tracking_schema2.mapLessonkitTelemetryToBridgeAction,
30
32
  mapLessonkitTelemetryToLxpack: () => import_tracking_schema2.mapLessonkitTelemetryToLxpack,
@@ -42,10 +44,20 @@ module.exports = __toCommonJS(bridge_exports);
42
44
  var import_spa_bridge = require("@lxpack/spa-bridge");
43
45
  var import_spa_bridge2 = require("@lxpack/spa-bridge");
44
46
  var import_tracking_schema2 = require("@lxpack/tracking-schema");
47
+ var import_tracking_schema3 = require("@lxpack/tracking-schema");
45
48
 
46
49
  // src/telemetry.ts
47
50
  var import_tracking_schema = require("@lxpack/tracking-schema");
48
51
  var SUPPORTED = new Set(import_tracking_schema.LESSONKIT_TELEMETRY_EVENTS);
52
+ function isQuizAnsweredData(data) {
53
+ return typeof data === "object" && data !== null && typeof data.checkId === "string";
54
+ }
55
+ function isQuizCompletedData(data) {
56
+ return typeof data === "object" && data !== null && typeof data.checkId === "string";
57
+ }
58
+ function isInteractionData(data) {
59
+ return typeof data === "object" && data !== null;
60
+ }
49
61
  function telemetryEventToLessonkit(event) {
50
62
  if (!SUPPORTED.has(event.name)) {
51
63
  return null;
@@ -57,16 +69,16 @@ function telemetryEventToLessonkit(event) {
57
69
  };
58
70
  if (name === "quiz_completed" || name === "quiz_answered") {
59
71
  const data = event.data;
60
- mapped.assessmentId = data?.checkId;
61
- if (data && "score" in data) {
62
- mapped.score = data.score;
63
- mapped.maxScore = data.maxScore;
64
- mapped.passingScore = data.passingScore;
65
- }
66
- if (data) {
72
+ if (isQuizAnsweredData(data) || isQuizCompletedData(data)) {
73
+ mapped.assessmentId = data.checkId;
74
+ if ("score" in data) {
75
+ mapped.score = data.score;
76
+ mapped.maxScore = data.maxScore;
77
+ mapped.passingScore = data.passingScore;
78
+ }
67
79
  mapped.data = data;
68
80
  }
69
- } else if (name === "interaction" && event.data) {
81
+ } else if (name === "interaction" && event.data && isInteractionData(event.data)) {
70
82
  mapped.data = event.data;
71
83
  }
72
84
  return mapped;
@@ -93,6 +105,65 @@ function getBridge(parentWindow) {
93
105
  if (!parent || parent === window) return null;
94
106
  return parent.lxpack ?? null;
95
107
  }
108
+ function isDevEnvironment() {
109
+ const g = globalThis;
110
+ return typeof g.process !== "undefined" && g.process.env?.NODE_ENV !== "production";
111
+ }
112
+ function dispatchBridgeAction(bridge, action) {
113
+ if (!action) return;
114
+ try {
115
+ dispatchBridgeActionInner(bridge, action);
116
+ } catch (err) {
117
+ if (isDevEnvironment()) {
118
+ console.warn(
119
+ "[lessonkit/lxpack] lxpack bridge action failed:",
120
+ err instanceof Error ? err.message : err
121
+ );
122
+ }
123
+ }
124
+ }
125
+ function dispatchBridgeActionInner(bridge, action) {
126
+ if (!action) return;
127
+ switch (action.kind) {
128
+ case "completeLesson":
129
+ bridge.completeLesson?.(action.lessonId);
130
+ return;
131
+ case "completeCourse":
132
+ bridge.completeCourse?.();
133
+ return;
134
+ case "submitAssessment": {
135
+ const scaled = (0, import_spa_bridge.normalizeScore)({
136
+ score: action.score,
137
+ maxScore: action.maxScore
138
+ });
139
+ if (scaled === null) return;
140
+ bridge.submitAssessment?.({
141
+ id: action.id,
142
+ score: scaled,
143
+ passingScore: (0, import_spa_bridge.normalizePassingThreshold)({
144
+ passingScore: action.passingScore,
145
+ maxScore: action.maxScore
146
+ }),
147
+ maxScore: action.maxScore
148
+ });
149
+ return;
150
+ }
151
+ case "track":
152
+ bridge.track?.(action.event);
153
+ return;
154
+ default:
155
+ return;
156
+ }
157
+ }
158
+ function forwardTelemetryToBridge(event, mode = "auto", parentWindow) {
159
+ if (mode === "off") return;
160
+ const bridge = getBridge(parentWindow);
161
+ if (!bridge) return;
162
+ const lessonkitEvent = telemetryEventToLessonkit(event);
163
+ if (!lessonkitEvent) return;
164
+ const action = (0, import_tracking_schema3.mapLessonkitTelemetryToBridgeAction)(lessonkitEvent);
165
+ dispatchBridgeAction(bridge, action);
166
+ }
96
167
  function createLxpackBridge() {
97
168
  return getBridge();
98
169
  }
@@ -121,6 +192,8 @@ function notifyLxpackAssessment(payload) {
121
192
  LXPACK_BRIDGE_VERSIONS,
122
193
  createLxpackBridge,
123
194
  createLxpackBridgeHost,
195
+ dispatchBridgeAction,
196
+ forwardTelemetryToBridge,
124
197
  getLxpackBridge,
125
198
  mapLessonkitTelemetryToBridgeAction,
126
199
  mapLessonkitTelemetryToLxpack,
package/dist/bridge.d.cts CHANGED
@@ -1,6 +1,7 @@
1
- import { CheckId, LessonId } from '@lessonkit/core';
1
+ import { TelemetryEvent, CheckId, LessonId } from '@lessonkit/core';
2
2
  import { LxpackBridgeV1, LxpackBridgeSubmitAssessmentPayload } from '@lxpack/spa-bridge';
3
3
  export { DEFAULT_BRIDGE_PASSING_SCORE, LXPACK_BRIDGE_VERSIONS, LxpackBridgeSubmitAssessmentPayload, LxpackBridgeV1, createLxpackBridgeHost, getLxpackBridge, normalizePassingThreshold, normalizeScore, supportedBridgeVersions } from '@lxpack/spa-bridge';
4
+ import { mapLessonkitTelemetryToBridgeAction } from '@lxpack/tracking-schema';
4
5
  export { LESSONKIT_TELEMETRY_EVENTS, LessonkitBridgeAction, LessonkitTelemetryEvent, LessonkitTelemetryEventName, TrackingSchemaEvent, mapLessonkitTelemetryToBridgeAction, mapLessonkitTelemetryToLxpack } from '@lxpack/tracking-schema';
5
6
  export { t as telemetryEventToLessonkit } from './telemetry-gCxlwc7I.cjs';
6
7
 
@@ -20,6 +21,11 @@ declare function normalizeAssessmentPassingScore(opts?: {
20
21
  passingScore?: number;
21
22
  maxScore?: number;
22
23
  }): number;
24
+ type LxpackBridgeMode = "auto" | "off";
25
+ /** Apply a mapped bridge action to an LXPack bridge instance. */
26
+ declare function dispatchBridgeAction(bridge: LxpackBridgeV1, action: ReturnType<typeof mapLessonkitTelemetryToBridgeAction>): void;
27
+ /** Resolve bridge and dispatch a telemetry-derived action. */
28
+ declare function forwardTelemetryToBridge(event: TelemetryEvent, mode?: LxpackBridgeMode, parentWindow?: Window): void;
23
29
  declare function createLxpackBridge(): LxpackBridgeV1 | null;
24
30
  declare function notifyLxpackLessonComplete(lessonId: LessonId): boolean;
25
31
  declare function notifyLxpackCourseComplete(): boolean;
@@ -31,4 +37,4 @@ declare function notifyLxpackAssessment(payload: LxpackBridgeSubmitAssessmentPay
31
37
  id: CheckId;
32
38
  }): boolean;
33
39
 
34
- export { createLxpackBridge, normalizeAssessmentPassingScore, normalizeAssessmentScore, notifyLxpackAssessment, notifyLxpackCourseComplete, notifyLxpackLessonComplete };
40
+ export { type LxpackBridgeMode, createLxpackBridge, dispatchBridgeAction, forwardTelemetryToBridge, normalizeAssessmentPassingScore, normalizeAssessmentScore, notifyLxpackAssessment, notifyLxpackCourseComplete, notifyLxpackLessonComplete };
package/dist/bridge.d.ts CHANGED
@@ -1,6 +1,7 @@
1
- import { CheckId, LessonId } from '@lessonkit/core';
1
+ import { TelemetryEvent, CheckId, LessonId } from '@lessonkit/core';
2
2
  import { LxpackBridgeV1, LxpackBridgeSubmitAssessmentPayload } from '@lxpack/spa-bridge';
3
3
  export { DEFAULT_BRIDGE_PASSING_SCORE, LXPACK_BRIDGE_VERSIONS, LxpackBridgeSubmitAssessmentPayload, LxpackBridgeV1, createLxpackBridgeHost, getLxpackBridge, normalizePassingThreshold, normalizeScore, supportedBridgeVersions } from '@lxpack/spa-bridge';
4
+ import { mapLessonkitTelemetryToBridgeAction } from '@lxpack/tracking-schema';
4
5
  export { LESSONKIT_TELEMETRY_EVENTS, LessonkitBridgeAction, LessonkitTelemetryEvent, LessonkitTelemetryEventName, TrackingSchemaEvent, mapLessonkitTelemetryToBridgeAction, mapLessonkitTelemetryToLxpack } from '@lxpack/tracking-schema';
5
6
  export { t as telemetryEventToLessonkit } from './telemetry-gCxlwc7I.js';
6
7
 
@@ -20,6 +21,11 @@ declare function normalizeAssessmentPassingScore(opts?: {
20
21
  passingScore?: number;
21
22
  maxScore?: number;
22
23
  }): number;
24
+ type LxpackBridgeMode = "auto" | "off";
25
+ /** Apply a mapped bridge action to an LXPack bridge instance. */
26
+ declare function dispatchBridgeAction(bridge: LxpackBridgeV1, action: ReturnType<typeof mapLessonkitTelemetryToBridgeAction>): void;
27
+ /** Resolve bridge and dispatch a telemetry-derived action. */
28
+ declare function forwardTelemetryToBridge(event: TelemetryEvent, mode?: LxpackBridgeMode, parentWindow?: Window): void;
23
29
  declare function createLxpackBridge(): LxpackBridgeV1 | null;
24
30
  declare function notifyLxpackLessonComplete(lessonId: LessonId): boolean;
25
31
  declare function notifyLxpackCourseComplete(): boolean;
@@ -31,4 +37,4 @@ declare function notifyLxpackAssessment(payload: LxpackBridgeSubmitAssessmentPay
31
37
  id: CheckId;
32
38
  }): boolean;
33
39
 
34
- export { createLxpackBridge, normalizeAssessmentPassingScore, normalizeAssessmentScore, notifyLxpackAssessment, notifyLxpackCourseComplete, notifyLxpackLessonComplete };
40
+ export { type LxpackBridgeMode, createLxpackBridge, dispatchBridgeAction, forwardTelemetryToBridge, normalizeAssessmentPassingScore, normalizeAssessmentScore, notifyLxpackAssessment, notifyLxpackCourseComplete, notifyLxpackLessonComplete };
package/dist/bridge.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  telemetryEventToLessonkit
3
- } from "./chunk-PSUSESH3.js";
3
+ } from "./chunk-DYQI222N.js";
4
4
 
5
5
  // src/bridge.ts
6
6
  import {
@@ -9,7 +9,7 @@ import {
9
9
  normalizeScore
10
10
  } from "@lxpack/spa-bridge";
11
11
  import {
12
- createLxpackBridgeHost as createLxpackBridgeHost2,
12
+ createLxpackBridgeHost,
13
13
  DEFAULT_BRIDGE_PASSING_SCORE,
14
14
  getLxpackBridge,
15
15
  LXPACK_BRIDGE_VERSIONS,
@@ -22,6 +22,7 @@ import {
22
22
  mapLessonkitTelemetryToBridgeAction,
23
23
  mapLessonkitTelemetryToLxpack
24
24
  } from "@lxpack/tracking-schema";
25
+ import { mapLessonkitTelemetryToBridgeAction as mapLessonkitTelemetryToBridgeAction2 } from "@lxpack/tracking-schema";
25
26
  function normalizeAssessmentScore(opts) {
26
27
  if (typeof opts.score !== "number" || !Number.isFinite(opts.score)) {
27
28
  return null;
@@ -42,6 +43,65 @@ function getBridge(parentWindow) {
42
43
  if (!parent || parent === window) return null;
43
44
  return parent.lxpack ?? null;
44
45
  }
46
+ function isDevEnvironment() {
47
+ const g = globalThis;
48
+ return typeof g.process !== "undefined" && g.process.env?.NODE_ENV !== "production";
49
+ }
50
+ function dispatchBridgeAction(bridge, action) {
51
+ if (!action) return;
52
+ try {
53
+ dispatchBridgeActionInner(bridge, action);
54
+ } catch (err) {
55
+ if (isDevEnvironment()) {
56
+ console.warn(
57
+ "[lessonkit/lxpack] lxpack bridge action failed:",
58
+ err instanceof Error ? err.message : err
59
+ );
60
+ }
61
+ }
62
+ }
63
+ function dispatchBridgeActionInner(bridge, action) {
64
+ if (!action) return;
65
+ switch (action.kind) {
66
+ case "completeLesson":
67
+ bridge.completeLesson?.(action.lessonId);
68
+ return;
69
+ case "completeCourse":
70
+ bridge.completeCourse?.();
71
+ return;
72
+ case "submitAssessment": {
73
+ const scaled = normalizeScore({
74
+ score: action.score,
75
+ maxScore: action.maxScore
76
+ });
77
+ if (scaled === null) return;
78
+ bridge.submitAssessment?.({
79
+ id: action.id,
80
+ score: scaled,
81
+ passingScore: normalizePassingThreshold({
82
+ passingScore: action.passingScore,
83
+ maxScore: action.maxScore
84
+ }),
85
+ maxScore: action.maxScore
86
+ });
87
+ return;
88
+ }
89
+ case "track":
90
+ bridge.track?.(action.event);
91
+ return;
92
+ default:
93
+ return;
94
+ }
95
+ }
96
+ function forwardTelemetryToBridge(event, mode = "auto", parentWindow) {
97
+ if (mode === "off") return;
98
+ const bridge = getBridge(parentWindow);
99
+ if (!bridge) return;
100
+ const lessonkitEvent = telemetryEventToLessonkit(event);
101
+ if (!lessonkitEvent) return;
102
+ const action = mapLessonkitTelemetryToBridgeAction2(lessonkitEvent);
103
+ dispatchBridgeAction(bridge, action);
104
+ }
45
105
  function createLxpackBridge() {
46
106
  return getBridge();
47
107
  }
@@ -68,7 +128,9 @@ export {
68
128
  LESSONKIT_TELEMETRY_EVENTS,
69
129
  LXPACK_BRIDGE_VERSIONS,
70
130
  createLxpackBridge,
71
- createLxpackBridgeHost2 as createLxpackBridgeHost,
131
+ createLxpackBridgeHost,
132
+ dispatchBridgeAction,
133
+ forwardTelemetryToBridge,
72
134
  getLxpackBridge,
73
135
  mapLessonkitTelemetryToBridgeAction,
74
136
  mapLessonkitTelemetryToLxpack,
@@ -0,0 +1,41 @@
1
+ // src/telemetry.ts
2
+ import { LESSONKIT_TELEMETRY_EVENTS } from "@lxpack/tracking-schema";
3
+ var SUPPORTED = new Set(LESSONKIT_TELEMETRY_EVENTS);
4
+ function isQuizAnsweredData(data) {
5
+ return typeof data === "object" && data !== null && typeof data.checkId === "string";
6
+ }
7
+ function isQuizCompletedData(data) {
8
+ return typeof data === "object" && data !== null && typeof data.checkId === "string";
9
+ }
10
+ function isInteractionData(data) {
11
+ return typeof data === "object" && data !== null;
12
+ }
13
+ function telemetryEventToLessonkit(event) {
14
+ if (!SUPPORTED.has(event.name)) {
15
+ return null;
16
+ }
17
+ const name = event.name;
18
+ const mapped = {
19
+ name,
20
+ lessonId: event.lessonId
21
+ };
22
+ if (name === "quiz_completed" || name === "quiz_answered") {
23
+ const data = event.data;
24
+ if (isQuizAnsweredData(data) || isQuizCompletedData(data)) {
25
+ mapped.assessmentId = data.checkId;
26
+ if ("score" in data) {
27
+ mapped.score = data.score;
28
+ mapped.maxScore = data.maxScore;
29
+ mapped.passingScore = data.passingScore;
30
+ }
31
+ mapped.data = data;
32
+ }
33
+ } else if (name === "interaction" && event.data && isInteractionData(event.data)) {
34
+ mapped.data = event.data;
35
+ }
36
+ return mapped;
37
+ }
38
+
39
+ export {
40
+ telemetryEventToLessonkit
41
+ };