@lessonkit/react 0.2.1 → 0.3.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
@@ -53,7 +53,7 @@ export default function App() {
53
53
  }
54
54
  ```
55
55
 
56
- ## API (0.2.1)
56
+ ## API (0.3.0)
57
57
 
58
58
  ### Components
59
59
 
@@ -83,4 +83,5 @@ export default function App() {
83
83
  for that lesson. Use stable `lessonId` values so completion and time-on-task telemetry stay consistent.
84
84
  - If you omit `session.sessionId`, the provider reuses a tab-scoped id via `sessionStorage` so React
85
85
  Strict Mode remounts do not split analytics sessions in development.
86
+ - Accessibility guidance lives in [`docs/ACCESSIBILITY.md`](../../docs/ACCESSIBILITY.md).
86
87
 
package/dist/index.cjs CHANGED
@@ -52,27 +52,39 @@ function disposeTrackingClient(client) {
52
52
  client?.flush?.();
53
53
  client?.dispose?.();
54
54
  }
55
+ function safeSessionStorageGetItem(key) {
56
+ if (typeof sessionStorage === "undefined") return null;
57
+ try {
58
+ return sessionStorage.getItem(key);
59
+ } catch {
60
+ return null;
61
+ }
62
+ }
63
+ function safeSessionStorageSetItem(key, value) {
64
+ if (typeof sessionStorage === "undefined") return;
65
+ try {
66
+ sessionStorage.setItem(key, value);
67
+ } catch {
68
+ }
69
+ }
55
70
  function resolveSessionId(provided) {
56
71
  if (provided) return provided;
57
- if (typeof sessionStorage !== "undefined") {
58
- const existing = sessionStorage.getItem(SESSION_STORAGE_KEY);
59
- if (existing) return existing;
60
- const id = (0, import_core.createSessionId)();
61
- sessionStorage.setItem(SESSION_STORAGE_KEY, id);
62
- return id;
63
- }
64
- return (0, import_core.createSessionId)();
72
+ const existing = safeSessionStorageGetItem(SESSION_STORAGE_KEY);
73
+ if (existing) return existing;
74
+ const id = (0, import_core.createSessionId)();
75
+ safeSessionStorageSetItem(SESSION_STORAGE_KEY, id);
76
+ return id;
65
77
  }
66
78
  function courseStartedStorageKey(sessionId, courseId) {
67
79
  return `${COURSE_STARTED_PREFIX}${sessionId}:${courseId ?? ""}`;
68
80
  }
69
81
  function hasCourseStarted(sessionId, courseId) {
70
- if (typeof sessionStorage === "undefined") return false;
71
- return sessionStorage.getItem(courseStartedStorageKey(sessionId, courseId)) === "1";
82
+ if (!courseId) return false;
83
+ return safeSessionStorageGetItem(courseStartedStorageKey(sessionId, courseId)) === "1";
72
84
  }
73
85
  function markCourseStarted(sessionId, courseId) {
74
- if (typeof sessionStorage === "undefined") return;
75
- sessionStorage.setItem(courseStartedStorageKey(sessionId, courseId), "1");
86
+ if (!courseId) return;
87
+ safeSessionStorageSetItem(courseStartedStorageKey(sessionId, courseId), "1");
76
88
  }
77
89
  function createTrackingClientFromConfig(config) {
78
90
  if (config.tracking?.enabled === false) {
@@ -102,6 +114,7 @@ function LessonkitProvider(props) {
102
114
  courseIdRef.current = config.courseId;
103
115
  const trackingRef = (0, import_react.useRef)((0, import_core.createTrackingClient)());
104
116
  const [tracking, setTracking] = (0, import_react.useState)(() => trackingRef.current);
117
+ const courseStartedInProviderRef = (0, import_react.useRef)(false);
105
118
  const trackingEnabled = config.tracking?.enabled;
106
119
  const trackingSink = config.tracking?.sink;
107
120
  const trackingBatchSink = config.tracking?.batchSink;
@@ -115,8 +128,13 @@ function LessonkitProvider(props) {
115
128
  setTracking(next);
116
129
  const sessionId = sessionIdRef.current;
117
130
  const cid = courseIdRef.current;
118
- if (!hasCourseStarted(sessionId, cid)) {
119
- markCourseStarted(sessionId, cid);
131
+ const shouldEmitCourseStarted = cid ? !hasCourseStarted(sessionId, cid) : !courseStartedInProviderRef.current;
132
+ if (shouldEmitCourseStarted) {
133
+ if (cid) {
134
+ markCourseStarted(sessionId, cid);
135
+ } else {
136
+ courseStartedInProviderRef.current = true;
137
+ }
120
138
  next.track({
121
139
  name: "course_started",
122
140
  timestamp: (0, import_core.nowIso)(),
@@ -150,8 +168,16 @@ function LessonkitProvider(props) {
150
168
  xapiRef.current = next;
151
169
  setXapi(next);
152
170
  void (async () => {
153
- if (prev) await prev.flush();
154
- await next?.flush();
171
+ if (prev) {
172
+ try {
173
+ await prev.flush();
174
+ } catch {
175
+ }
176
+ }
177
+ try {
178
+ await next?.flush();
179
+ } catch {
180
+ }
155
181
  })();
156
182
  return () => {
157
183
  void prev?.flush();
package/dist/index.js CHANGED
@@ -22,27 +22,39 @@ function disposeTrackingClient(client) {
22
22
  client?.flush?.();
23
23
  client?.dispose?.();
24
24
  }
25
+ function safeSessionStorageGetItem(key) {
26
+ if (typeof sessionStorage === "undefined") return null;
27
+ try {
28
+ return sessionStorage.getItem(key);
29
+ } catch {
30
+ return null;
31
+ }
32
+ }
33
+ function safeSessionStorageSetItem(key, value) {
34
+ if (typeof sessionStorage === "undefined") return;
35
+ try {
36
+ sessionStorage.setItem(key, value);
37
+ } catch {
38
+ }
39
+ }
25
40
  function resolveSessionId(provided) {
26
41
  if (provided) return provided;
27
- if (typeof sessionStorage !== "undefined") {
28
- const existing = sessionStorage.getItem(SESSION_STORAGE_KEY);
29
- if (existing) return existing;
30
- const id = createSessionId();
31
- sessionStorage.setItem(SESSION_STORAGE_KEY, id);
32
- return id;
33
- }
34
- return createSessionId();
42
+ const existing = safeSessionStorageGetItem(SESSION_STORAGE_KEY);
43
+ if (existing) return existing;
44
+ const id = createSessionId();
45
+ safeSessionStorageSetItem(SESSION_STORAGE_KEY, id);
46
+ return id;
35
47
  }
36
48
  function courseStartedStorageKey(sessionId, courseId) {
37
49
  return `${COURSE_STARTED_PREFIX}${sessionId}:${courseId ?? ""}`;
38
50
  }
39
51
  function hasCourseStarted(sessionId, courseId) {
40
- if (typeof sessionStorage === "undefined") return false;
41
- return sessionStorage.getItem(courseStartedStorageKey(sessionId, courseId)) === "1";
52
+ if (!courseId) return false;
53
+ return safeSessionStorageGetItem(courseStartedStorageKey(sessionId, courseId)) === "1";
42
54
  }
43
55
  function markCourseStarted(sessionId, courseId) {
44
- if (typeof sessionStorage === "undefined") return;
45
- sessionStorage.setItem(courseStartedStorageKey(sessionId, courseId), "1");
56
+ if (!courseId) return;
57
+ safeSessionStorageSetItem(courseStartedStorageKey(sessionId, courseId), "1");
46
58
  }
47
59
  function createTrackingClientFromConfig(config) {
48
60
  if (config.tracking?.enabled === false) {
@@ -72,6 +84,7 @@ function LessonkitProvider(props) {
72
84
  courseIdRef.current = config.courseId;
73
85
  const trackingRef = useRef(createTrackingClient());
74
86
  const [tracking, setTracking] = useState(() => trackingRef.current);
87
+ const courseStartedInProviderRef = useRef(false);
75
88
  const trackingEnabled = config.tracking?.enabled;
76
89
  const trackingSink = config.tracking?.sink;
77
90
  const trackingBatchSink = config.tracking?.batchSink;
@@ -85,8 +98,13 @@ function LessonkitProvider(props) {
85
98
  setTracking(next);
86
99
  const sessionId = sessionIdRef.current;
87
100
  const cid = courseIdRef.current;
88
- if (!hasCourseStarted(sessionId, cid)) {
89
- markCourseStarted(sessionId, cid);
101
+ const shouldEmitCourseStarted = cid ? !hasCourseStarted(sessionId, cid) : !courseStartedInProviderRef.current;
102
+ if (shouldEmitCourseStarted) {
103
+ if (cid) {
104
+ markCourseStarted(sessionId, cid);
105
+ } else {
106
+ courseStartedInProviderRef.current = true;
107
+ }
90
108
  next.track({
91
109
  name: "course_started",
92
110
  timestamp: nowIso(),
@@ -120,8 +138,16 @@ function LessonkitProvider(props) {
120
138
  xapiRef.current = next;
121
139
  setXapi(next);
122
140
  void (async () => {
123
- if (prev) await prev.flush();
124
- await next?.flush();
141
+ if (prev) {
142
+ try {
143
+ await prev.flush();
144
+ } catch {
145
+ }
146
+ }
147
+ try {
148
+ await next?.flush();
149
+ } catch {
150
+ }
125
151
  })();
126
152
  return () => {
127
153
  void prev?.flush();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lessonkit/react",
3
- "version": "0.2.1",
3
+ "version": "0.3.0",
4
4
  "private": false,
5
5
  "description": "React components and hooks for building learning experiences with LessonKit.",
6
6
  "license": "Apache-2.0",
@@ -50,9 +50,9 @@
50
50
  "react-dom": ">=18"
51
51
  },
52
52
  "dependencies": {
53
- "@lessonkit/accessibility": "0.2.1",
54
- "@lessonkit/core": "0.2.1",
55
- "@lessonkit/xapi": "0.2.1"
53
+ "@lessonkit/accessibility": "0.3.0",
54
+ "@lessonkit/core": "0.3.0",
55
+ "@lessonkit/xapi": "0.3.0"
56
56
  },
57
57
  "devDependencies": {
58
58
  "@testing-library/react": "^16.3.0",