@lessonkit/lxpack 1.5.0 → 1.6.0

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