@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 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
- [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)
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: () => import_spa_bridge2.normalizePassingThreshold,
39
- normalizeScore: () => import_spa_bridge2.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 (0, import_spa_bridge.normalizeScore)({ score: opts.score, maxScore: opts.maxScore });
179
+ return normalizeScore({ score: opts.score, maxScore: opts.maxScore });
145
180
  }
146
181
  function normalizeAssessmentPassingScore(opts) {
147
- return (0, import_spa_bridge.normalizePassingThreshold)({
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
- const referrer = typeof document !== "undefined" ? document.referrer : "";
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 = (0, import_spa_bridge.normalizeScore)({
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: (0, import_spa_bridge.normalizePassingThreshold)({
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) return;
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
- bridge.completeLesson(lessonId);
310
- return true;
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
- bridge.completeCourse();
316
- return true;
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
- bridge.submitAssessment(payload);
322
- return true;
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, normalizePassingThreshold, normalizeScore, supportedBridgeVersions } from '@lxpack/spa-bridge';
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
- * Delegates to `@lxpack/spa-bridge` (default 0.7 when omitted).
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
- * `score` must already be on a 0–1 scale (use `normalizeAssessmentScore` for raw points).
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, normalizePassingThreshold, normalizeScore, supportedBridgeVersions } from '@lxpack/spa-bridge';
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
- * Delegates to `@lxpack/spa-bridge` (default 0.7 when omitted).
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
- * `score` must already be on a 0–1 scale (use `normalizeAssessmentScore` for raw points).
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
- const referrer = typeof document !== "undefined" ? document.referrer : "";
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) return;
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
- bridge.completeLesson(lessonId);
198
- return true;
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
- bridge.completeCourse();
204
- return true;
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
- bridge.submitAssessment(payload);
210
- return true;
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
- normalizePassingThreshold2 as normalizePassingThreshold,
229
- normalizeScore2 as normalizeScore,
283
+ normalizePassingThreshold,
284
+ normalizeScore,
230
285
  notifyLxpackAssessment,
231
286
  notifyLxpackCourseComplete,
232
287
  notifyLxpackLessonComplete,