@lessonkit/xapi 0.3.1 → 0.5.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 +8 -3
- package/dist/index.cjs +141 -37
- package/dist/index.d.cts +9 -3
- package/dist/index.d.ts +9 -3
- package/dist/index.js +139 -36
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -18,8 +18,8 @@ npm install @lessonkit/xapi
|
|
|
18
18
|
import { createXAPIClient } from "@lessonkit/xapi";
|
|
19
19
|
|
|
20
20
|
const xapi = createXAPIClient({
|
|
21
|
+
courseId: "cyber-basics",
|
|
21
22
|
transport: (statement) => {
|
|
22
|
-
// Send to your LRS (or queue offline).
|
|
23
23
|
console.log(statement);
|
|
24
24
|
},
|
|
25
25
|
});
|
|
@@ -27,8 +27,13 @@ const xapi = createXAPIClient({
|
|
|
27
27
|
xapi.completeLesson({ lessonId: "phishing-101", durationMs: 1500, success: true, score: 7, maxScore: 10 });
|
|
28
28
|
```
|
|
29
29
|
|
|
30
|
-
|
|
30
|
+
Prefer mapping from telemetry: `telemetryEventToXAPIStatement(event)` (canonical object URNs).
|
|
31
31
|
|
|
32
|
+
## Notes (0.5.0)
|
|
33
|
+
|
|
34
|
+
- `createXAPIClient` requires `courseId` for lifecycle helpers; React uses the mapper after each `track()`.
|
|
32
35
|
- If the transport throws/rejects, statements are queued in-memory.
|
|
33
|
-
-
|
|
36
|
+
- Call `await xapi.flush()` to retry queued statements.
|
|
37
|
+
|
|
38
|
+
See [`docs/TELEMETRY.md`](../../docs/TELEMETRY.md).
|
|
34
39
|
|
package/dist/index.cjs
CHANGED
|
@@ -21,7 +21,8 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
21
21
|
var index_exports = {};
|
|
22
22
|
__export(index_exports, {
|
|
23
23
|
createInMemoryXAPIQueue: () => createInMemoryXAPIQueue,
|
|
24
|
-
createXAPIClient: () => createXAPIClient
|
|
24
|
+
createXAPIClient: () => createXAPIClient,
|
|
25
|
+
telemetryEventToXAPIStatement: () => telemetryEventToXAPIStatement
|
|
25
26
|
});
|
|
26
27
|
module.exports = __toCommonJS(index_exports);
|
|
27
28
|
|
|
@@ -48,6 +49,9 @@ function createInMemoryXAPIQueue() {
|
|
|
48
49
|
}
|
|
49
50
|
|
|
50
51
|
// src/client.ts
|
|
52
|
+
var import_core2 = require("@lessonkit/core");
|
|
53
|
+
|
|
54
|
+
// src/telemetryMap.ts
|
|
51
55
|
var import_core = require("@lessonkit/core");
|
|
52
56
|
|
|
53
57
|
// src/id.ts
|
|
@@ -65,14 +69,112 @@ function formatDurationMs(ms) {
|
|
|
65
69
|
return `PT${fixed}S`;
|
|
66
70
|
}
|
|
67
71
|
|
|
68
|
-
// src/
|
|
72
|
+
// src/telemetryMap.ts
|
|
69
73
|
var XAPIVerbs = {
|
|
70
|
-
|
|
71
|
-
completed: "http://adlnet.gov/expapi/verbs/completed"
|
|
74
|
+
initialized: "http://adlnet.gov/expapi/verbs/initialized",
|
|
75
|
+
completed: "http://adlnet.gov/expapi/verbs/completed",
|
|
76
|
+
answered: "http://adlnet.gov/expapi/verbs/answered",
|
|
77
|
+
experienced: "http://adlnet.gov/expapi/verbs/experienced"
|
|
72
78
|
};
|
|
79
|
+
function telemetryEventToXAPIStatement(event) {
|
|
80
|
+
const { courseId } = event;
|
|
81
|
+
switch (event.name) {
|
|
82
|
+
case "course_started":
|
|
83
|
+
return statementFor((0, import_core.buildLessonkitUrn)({ courseId }), XAPIVerbs.initialized, event.timestamp);
|
|
84
|
+
case "course_completed":
|
|
85
|
+
return statementFor((0, import_core.buildLessonkitUrn)({ courseId }), XAPIVerbs.completed, event.timestamp);
|
|
86
|
+
case "lesson_started": {
|
|
87
|
+
const lessonId = event.lessonId;
|
|
88
|
+
return statementFor(
|
|
89
|
+
(0, import_core.buildLessonkitUrn)({ courseId, lessonId }),
|
|
90
|
+
XAPIVerbs.initialized,
|
|
91
|
+
event.timestamp
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
case "lesson_completed": {
|
|
95
|
+
const lessonId = event.lessonId;
|
|
96
|
+
const data = event.data;
|
|
97
|
+
const result = {};
|
|
98
|
+
if (typeof data?.durationMs === "number") {
|
|
99
|
+
result.duration = formatDurationMs(data.durationMs);
|
|
100
|
+
}
|
|
101
|
+
if (typeof data?.success === "boolean") result.success = data.success;
|
|
102
|
+
if (typeof data?.score === "number" || typeof data?.maxScore === "number") {
|
|
103
|
+
const max = typeof data.maxScore === "number" ? data.maxScore : void 0;
|
|
104
|
+
const raw = typeof data.score === "number" ? data.score : void 0;
|
|
105
|
+
result.score = {
|
|
106
|
+
raw,
|
|
107
|
+
max,
|
|
108
|
+
min: 0,
|
|
109
|
+
scaled: typeof raw === "number" && typeof max === "number" && max > 0 ? raw / max : void 0
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
return statementFor((0, import_core.buildLessonkitUrn)({ courseId, lessonId }), XAPIVerbs.completed, event.timestamp, {
|
|
113
|
+
result: Object.keys(result).length ? result : void 0
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
case "lesson_time_on_task":
|
|
117
|
+
return null;
|
|
118
|
+
case "quiz_answered": {
|
|
119
|
+
const lessonId = event.lessonId;
|
|
120
|
+
const checkId = event.data.checkId;
|
|
121
|
+
return statementFor(
|
|
122
|
+
(0, import_core.buildLessonkitUrn)({ courseId, lessonId, checkId }),
|
|
123
|
+
XAPIVerbs.answered,
|
|
124
|
+
event.timestamp
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
case "quiz_completed": {
|
|
128
|
+
const lessonId = event.lessonId;
|
|
129
|
+
const checkId = event.data.checkId;
|
|
130
|
+
const { score, maxScore } = event.data;
|
|
131
|
+
const result = {};
|
|
132
|
+
if (typeof score === "number" || typeof maxScore === "number") {
|
|
133
|
+
const max = typeof maxScore === "number" ? maxScore : void 0;
|
|
134
|
+
const raw = typeof score === "number" ? score : void 0;
|
|
135
|
+
result.score = {
|
|
136
|
+
raw,
|
|
137
|
+
max,
|
|
138
|
+
min: 0,
|
|
139
|
+
scaled: typeof raw === "number" && typeof max === "number" && max > 0 ? raw / max : void 0
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
return statementFor(
|
|
143
|
+
(0, import_core.buildLessonkitUrn)({ courseId, lessonId, checkId }),
|
|
144
|
+
XAPIVerbs.completed,
|
|
145
|
+
event.timestamp,
|
|
146
|
+
{ result: Object.keys(result).length ? result : void 0 }
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
case "interaction": {
|
|
150
|
+
const lessonId = event.lessonId;
|
|
151
|
+
const blockId = event.data?.blockId;
|
|
152
|
+
if (!lessonId || !blockId) return null;
|
|
153
|
+
return statementFor(
|
|
154
|
+
(0, import_core.buildLessonkitUrn)({ courseId, lessonId, blockId }),
|
|
155
|
+
XAPIVerbs.experienced,
|
|
156
|
+
event.timestamp
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
default:
|
|
160
|
+
return null;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
function statementFor(objectId, verb, timestamp, extra) {
|
|
164
|
+
return {
|
|
165
|
+
id: cryptoRandomId(),
|
|
166
|
+
timestamp,
|
|
167
|
+
verb,
|
|
168
|
+
object: { id: objectId },
|
|
169
|
+
result: extra?.result,
|
|
170
|
+
context: extra?.context
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// src/client.ts
|
|
73
175
|
function createXAPIClient(opts) {
|
|
74
176
|
const transport = opts?.transport;
|
|
75
|
-
const
|
|
177
|
+
const courseId = opts?.courseId;
|
|
76
178
|
const queue = opts?.queue ?? createInMemoryXAPIQueue();
|
|
77
179
|
const sendOrQueue = (statement) => {
|
|
78
180
|
if (!transport) {
|
|
@@ -83,6 +185,10 @@ function createXAPIClient(opts) {
|
|
|
83
185
|
queue.enqueue(statement);
|
|
84
186
|
});
|
|
85
187
|
};
|
|
188
|
+
const emit = (event) => {
|
|
189
|
+
const statement = telemetryEventToXAPIStatement(event);
|
|
190
|
+
if (statement) sendOrQueue(statement);
|
|
191
|
+
};
|
|
86
192
|
return {
|
|
87
193
|
send: (statement) => {
|
|
88
194
|
sendOrQueue(statement);
|
|
@@ -93,46 +199,44 @@ function createXAPIClient(opts) {
|
|
|
93
199
|
await queue.flush(transport);
|
|
94
200
|
},
|
|
95
201
|
startedLesson: ({ lessonId }) => {
|
|
96
|
-
|
|
97
|
-
|
|
202
|
+
if (!courseId) return;
|
|
203
|
+
emit({
|
|
204
|
+
name: "lesson_started",
|
|
205
|
+
timestamp: (0, import_core2.nowIso)(),
|
|
206
|
+
courseId,
|
|
207
|
+
lessonId,
|
|
208
|
+
data: { lessonId }
|
|
209
|
+
});
|
|
98
210
|
},
|
|
99
|
-
completeLesson: ({
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
const statement = statementFor(`${baseId}:lesson:${lessonId}`, XAPIVerbs.completed, {
|
|
114
|
-
result: Object.keys(result).length ? result : void 0
|
|
211
|
+
completeLesson: ({
|
|
212
|
+
lessonId,
|
|
213
|
+
durationMs,
|
|
214
|
+
score,
|
|
215
|
+
maxScore,
|
|
216
|
+
success
|
|
217
|
+
}) => {
|
|
218
|
+
if (!courseId) return;
|
|
219
|
+
emit({
|
|
220
|
+
name: "lesson_completed",
|
|
221
|
+
timestamp: (0, import_core2.nowIso)(),
|
|
222
|
+
courseId,
|
|
223
|
+
lessonId,
|
|
224
|
+
data: { lessonId, durationMs, score, maxScore, success }
|
|
115
225
|
});
|
|
116
|
-
sendOrQueue(statement);
|
|
117
226
|
},
|
|
118
227
|
completeCourse: () => {
|
|
119
|
-
|
|
120
|
-
|
|
228
|
+
if (!courseId) return;
|
|
229
|
+
emit({
|
|
230
|
+
name: "course_completed",
|
|
231
|
+
timestamp: (0, import_core2.nowIso)(),
|
|
232
|
+
courseId
|
|
233
|
+
});
|
|
121
234
|
}
|
|
122
235
|
};
|
|
123
236
|
}
|
|
124
|
-
function statementFor(objectId, verb, extra) {
|
|
125
|
-
return {
|
|
126
|
-
id: cryptoRandomId(),
|
|
127
|
-
timestamp: (0, import_core.nowIso)(),
|
|
128
|
-
verb,
|
|
129
|
-
object: { id: objectId },
|
|
130
|
-
result: extra?.result,
|
|
131
|
-
context: extra?.context
|
|
132
|
-
};
|
|
133
|
-
}
|
|
134
237
|
// Annotate the CommonJS export names for ESM import in node:
|
|
135
238
|
0 && (module.exports = {
|
|
136
239
|
createInMemoryXAPIQueue,
|
|
137
|
-
createXAPIClient
|
|
240
|
+
createXAPIClient,
|
|
241
|
+
telemetryEventToXAPIStatement
|
|
138
242
|
});
|
package/dist/index.d.cts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { LessonId } from '@lessonkit/core';
|
|
1
|
+
import { LessonId, CourseId, TelemetryEvent } from '@lessonkit/core';
|
|
2
2
|
|
|
3
3
|
type XAPIStatement = {
|
|
4
4
|
id: string;
|
|
@@ -38,8 +38,14 @@ declare function createInMemoryXAPIQueue(): XAPIQueue;
|
|
|
38
38
|
|
|
39
39
|
declare function createXAPIClient(opts?: {
|
|
40
40
|
transport?: XAPITransport;
|
|
41
|
-
|
|
41
|
+
courseId?: CourseId;
|
|
42
42
|
queue?: XAPIQueue;
|
|
43
43
|
}): XAPIClient;
|
|
44
44
|
|
|
45
|
-
|
|
45
|
+
/**
|
|
46
|
+
* Map a LessonKit telemetry event to an xAPI statement, or null if the event should not emit xAPI.
|
|
47
|
+
* `lesson_time_on_task` returns null (companion metric; lesson_completed carries duration).
|
|
48
|
+
*/
|
|
49
|
+
declare function telemetryEventToXAPIStatement(event: TelemetryEvent): XAPIStatement | null;
|
|
50
|
+
|
|
51
|
+
export { type XAPIClient, type XAPIQueue, type XAPIStatement, type XAPITransport, createInMemoryXAPIQueue, createXAPIClient, telemetryEventToXAPIStatement };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { LessonId } from '@lessonkit/core';
|
|
1
|
+
import { LessonId, CourseId, TelemetryEvent } from '@lessonkit/core';
|
|
2
2
|
|
|
3
3
|
type XAPIStatement = {
|
|
4
4
|
id: string;
|
|
@@ -38,8 +38,14 @@ declare function createInMemoryXAPIQueue(): XAPIQueue;
|
|
|
38
38
|
|
|
39
39
|
declare function createXAPIClient(opts?: {
|
|
40
40
|
transport?: XAPITransport;
|
|
41
|
-
|
|
41
|
+
courseId?: CourseId;
|
|
42
42
|
queue?: XAPIQueue;
|
|
43
43
|
}): XAPIClient;
|
|
44
44
|
|
|
45
|
-
|
|
45
|
+
/**
|
|
46
|
+
* Map a LessonKit telemetry event to an xAPI statement, or null if the event should not emit xAPI.
|
|
47
|
+
* `lesson_time_on_task` returns null (companion metric; lesson_completed carries duration).
|
|
48
|
+
*/
|
|
49
|
+
declare function telemetryEventToXAPIStatement(event: TelemetryEvent): XAPIStatement | null;
|
|
50
|
+
|
|
51
|
+
export { type XAPIClient, type XAPIQueue, type XAPIStatement, type XAPITransport, createInMemoryXAPIQueue, createXAPIClient, telemetryEventToXAPIStatement };
|
package/dist/index.js
CHANGED
|
@@ -23,6 +23,9 @@ function createInMemoryXAPIQueue() {
|
|
|
23
23
|
// src/client.ts
|
|
24
24
|
import { nowIso } from "@lessonkit/core";
|
|
25
25
|
|
|
26
|
+
// src/telemetryMap.ts
|
|
27
|
+
import { buildLessonkitUrn } from "@lessonkit/core";
|
|
28
|
+
|
|
26
29
|
// src/id.ts
|
|
27
30
|
function cryptoRandomId() {
|
|
28
31
|
const g = globalThis;
|
|
@@ -38,14 +41,112 @@ function formatDurationMs(ms) {
|
|
|
38
41
|
return `PT${fixed}S`;
|
|
39
42
|
}
|
|
40
43
|
|
|
41
|
-
// src/
|
|
44
|
+
// src/telemetryMap.ts
|
|
42
45
|
var XAPIVerbs = {
|
|
43
|
-
|
|
44
|
-
completed: "http://adlnet.gov/expapi/verbs/completed"
|
|
46
|
+
initialized: "http://adlnet.gov/expapi/verbs/initialized",
|
|
47
|
+
completed: "http://adlnet.gov/expapi/verbs/completed",
|
|
48
|
+
answered: "http://adlnet.gov/expapi/verbs/answered",
|
|
49
|
+
experienced: "http://adlnet.gov/expapi/verbs/experienced"
|
|
45
50
|
};
|
|
51
|
+
function telemetryEventToXAPIStatement(event) {
|
|
52
|
+
const { courseId } = event;
|
|
53
|
+
switch (event.name) {
|
|
54
|
+
case "course_started":
|
|
55
|
+
return statementFor(buildLessonkitUrn({ courseId }), XAPIVerbs.initialized, event.timestamp);
|
|
56
|
+
case "course_completed":
|
|
57
|
+
return statementFor(buildLessonkitUrn({ courseId }), XAPIVerbs.completed, event.timestamp);
|
|
58
|
+
case "lesson_started": {
|
|
59
|
+
const lessonId = event.lessonId;
|
|
60
|
+
return statementFor(
|
|
61
|
+
buildLessonkitUrn({ courseId, lessonId }),
|
|
62
|
+
XAPIVerbs.initialized,
|
|
63
|
+
event.timestamp
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
case "lesson_completed": {
|
|
67
|
+
const lessonId = event.lessonId;
|
|
68
|
+
const data = event.data;
|
|
69
|
+
const result = {};
|
|
70
|
+
if (typeof data?.durationMs === "number") {
|
|
71
|
+
result.duration = formatDurationMs(data.durationMs);
|
|
72
|
+
}
|
|
73
|
+
if (typeof data?.success === "boolean") result.success = data.success;
|
|
74
|
+
if (typeof data?.score === "number" || typeof data?.maxScore === "number") {
|
|
75
|
+
const max = typeof data.maxScore === "number" ? data.maxScore : void 0;
|
|
76
|
+
const raw = typeof data.score === "number" ? data.score : void 0;
|
|
77
|
+
result.score = {
|
|
78
|
+
raw,
|
|
79
|
+
max,
|
|
80
|
+
min: 0,
|
|
81
|
+
scaled: typeof raw === "number" && typeof max === "number" && max > 0 ? raw / max : void 0
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
return statementFor(buildLessonkitUrn({ courseId, lessonId }), XAPIVerbs.completed, event.timestamp, {
|
|
85
|
+
result: Object.keys(result).length ? result : void 0
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
case "lesson_time_on_task":
|
|
89
|
+
return null;
|
|
90
|
+
case "quiz_answered": {
|
|
91
|
+
const lessonId = event.lessonId;
|
|
92
|
+
const checkId = event.data.checkId;
|
|
93
|
+
return statementFor(
|
|
94
|
+
buildLessonkitUrn({ courseId, lessonId, checkId }),
|
|
95
|
+
XAPIVerbs.answered,
|
|
96
|
+
event.timestamp
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
case "quiz_completed": {
|
|
100
|
+
const lessonId = event.lessonId;
|
|
101
|
+
const checkId = event.data.checkId;
|
|
102
|
+
const { score, maxScore } = event.data;
|
|
103
|
+
const result = {};
|
|
104
|
+
if (typeof score === "number" || typeof maxScore === "number") {
|
|
105
|
+
const max = typeof maxScore === "number" ? maxScore : void 0;
|
|
106
|
+
const raw = typeof score === "number" ? score : void 0;
|
|
107
|
+
result.score = {
|
|
108
|
+
raw,
|
|
109
|
+
max,
|
|
110
|
+
min: 0,
|
|
111
|
+
scaled: typeof raw === "number" && typeof max === "number" && max > 0 ? raw / max : void 0
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
return statementFor(
|
|
115
|
+
buildLessonkitUrn({ courseId, lessonId, checkId }),
|
|
116
|
+
XAPIVerbs.completed,
|
|
117
|
+
event.timestamp,
|
|
118
|
+
{ result: Object.keys(result).length ? result : void 0 }
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
case "interaction": {
|
|
122
|
+
const lessonId = event.lessonId;
|
|
123
|
+
const blockId = event.data?.blockId;
|
|
124
|
+
if (!lessonId || !blockId) return null;
|
|
125
|
+
return statementFor(
|
|
126
|
+
buildLessonkitUrn({ courseId, lessonId, blockId }),
|
|
127
|
+
XAPIVerbs.experienced,
|
|
128
|
+
event.timestamp
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
default:
|
|
132
|
+
return null;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
function statementFor(objectId, verb, timestamp, extra) {
|
|
136
|
+
return {
|
|
137
|
+
id: cryptoRandomId(),
|
|
138
|
+
timestamp,
|
|
139
|
+
verb,
|
|
140
|
+
object: { id: objectId },
|
|
141
|
+
result: extra?.result,
|
|
142
|
+
context: extra?.context
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// src/client.ts
|
|
46
147
|
function createXAPIClient(opts) {
|
|
47
148
|
const transport = opts?.transport;
|
|
48
|
-
const
|
|
149
|
+
const courseId = opts?.courseId;
|
|
49
150
|
const queue = opts?.queue ?? createInMemoryXAPIQueue();
|
|
50
151
|
const sendOrQueue = (statement) => {
|
|
51
152
|
if (!transport) {
|
|
@@ -56,6 +157,10 @@ function createXAPIClient(opts) {
|
|
|
56
157
|
queue.enqueue(statement);
|
|
57
158
|
});
|
|
58
159
|
};
|
|
160
|
+
const emit = (event) => {
|
|
161
|
+
const statement = telemetryEventToXAPIStatement(event);
|
|
162
|
+
if (statement) sendOrQueue(statement);
|
|
163
|
+
};
|
|
59
164
|
return {
|
|
60
165
|
send: (statement) => {
|
|
61
166
|
sendOrQueue(statement);
|
|
@@ -66,45 +171,43 @@ function createXAPIClient(opts) {
|
|
|
66
171
|
await queue.flush(transport);
|
|
67
172
|
},
|
|
68
173
|
startedLesson: ({ lessonId }) => {
|
|
69
|
-
|
|
70
|
-
|
|
174
|
+
if (!courseId) return;
|
|
175
|
+
emit({
|
|
176
|
+
name: "lesson_started",
|
|
177
|
+
timestamp: nowIso(),
|
|
178
|
+
courseId,
|
|
179
|
+
lessonId,
|
|
180
|
+
data: { lessonId }
|
|
181
|
+
});
|
|
71
182
|
},
|
|
72
|
-
completeLesson: ({
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
const statement = statementFor(`${baseId}:lesson:${lessonId}`, XAPIVerbs.completed, {
|
|
87
|
-
result: Object.keys(result).length ? result : void 0
|
|
183
|
+
completeLesson: ({
|
|
184
|
+
lessonId,
|
|
185
|
+
durationMs,
|
|
186
|
+
score,
|
|
187
|
+
maxScore,
|
|
188
|
+
success
|
|
189
|
+
}) => {
|
|
190
|
+
if (!courseId) return;
|
|
191
|
+
emit({
|
|
192
|
+
name: "lesson_completed",
|
|
193
|
+
timestamp: nowIso(),
|
|
194
|
+
courseId,
|
|
195
|
+
lessonId,
|
|
196
|
+
data: { lessonId, durationMs, score, maxScore, success }
|
|
88
197
|
});
|
|
89
|
-
sendOrQueue(statement);
|
|
90
198
|
},
|
|
91
199
|
completeCourse: () => {
|
|
92
|
-
|
|
93
|
-
|
|
200
|
+
if (!courseId) return;
|
|
201
|
+
emit({
|
|
202
|
+
name: "course_completed",
|
|
203
|
+
timestamp: nowIso(),
|
|
204
|
+
courseId
|
|
205
|
+
});
|
|
94
206
|
}
|
|
95
207
|
};
|
|
96
208
|
}
|
|
97
|
-
function statementFor(objectId, verb, extra) {
|
|
98
|
-
return {
|
|
99
|
-
id: cryptoRandomId(),
|
|
100
|
-
timestamp: nowIso(),
|
|
101
|
-
verb,
|
|
102
|
-
object: { id: objectId },
|
|
103
|
-
result: extra?.result,
|
|
104
|
-
context: extra?.context
|
|
105
|
-
};
|
|
106
|
-
}
|
|
107
209
|
export {
|
|
108
210
|
createInMemoryXAPIQueue,
|
|
109
|
-
createXAPIClient
|
|
211
|
+
createXAPIClient,
|
|
212
|
+
telemetryEventToXAPIStatement
|
|
110
213
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lessonkit/xapi",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "xAPI statement generation primitives for LessonKit.",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
"lint": "echo \"(no lint configured yet)\""
|
|
46
46
|
},
|
|
47
47
|
"dependencies": {
|
|
48
|
-
"@lessonkit/core": "0.
|
|
48
|
+
"@lessonkit/core": "0.5.0"
|
|
49
49
|
},
|
|
50
50
|
"devDependencies": {
|
|
51
51
|
"tsup": "^8.5.0",
|