@learnpack/learnpack 2.1.39 → 2.1.40

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.
Files changed (196) hide show
  1. package/README.md +370 -35
  2. package/bin/run +17 -17
  3. package/bin/run.cmd +3 -3
  4. package/lib/commands/audit.d.ts +6 -6
  5. package/lib/commands/audit.js +342 -342
  6. package/lib/commands/clean.d.ts +8 -8
  7. package/lib/commands/clean.js +25 -25
  8. package/lib/commands/download.d.ts +13 -13
  9. package/lib/commands/download.js +55 -55
  10. package/lib/commands/init.d.ts +9 -9
  11. package/lib/commands/init.js +123 -123
  12. package/lib/commands/login.d.ts +14 -14
  13. package/lib/commands/login.js +37 -37
  14. package/lib/commands/logout.d.ts +14 -14
  15. package/lib/commands/logout.js +37 -37
  16. package/lib/commands/publish.d.ts +14 -14
  17. package/lib/commands/publish.js +82 -82
  18. package/lib/commands/start.d.ts +7 -7
  19. package/lib/commands/start.js +239 -208
  20. package/lib/commands/test.d.ts +6 -6
  21. package/lib/commands/test.js +62 -62
  22. package/lib/index.d.ts +1 -1
  23. package/lib/index.js +4 -4
  24. package/lib/managers/config/allowed_files.d.ts +5 -5
  25. package/lib/managers/config/allowed_files.js +30 -30
  26. package/lib/managers/config/defaults.d.ts +41 -41
  27. package/lib/managers/config/defaults.js +44 -44
  28. package/lib/managers/config/exercise.d.ts +36 -36
  29. package/lib/managers/config/exercise.js +236 -236
  30. package/lib/managers/config/index.d.ts +3 -3
  31. package/lib/managers/config/index.js +337 -337
  32. package/lib/managers/file.d.ts +14 -14
  33. package/lib/managers/file.js +153 -153
  34. package/lib/managers/gitpod.d.ts +3 -3
  35. package/lib/managers/gitpod.js +67 -67
  36. package/lib/managers/server/index.d.ts +6 -6
  37. package/lib/managers/server/index.js +58 -58
  38. package/lib/managers/server/routes.d.ts +4 -4
  39. package/lib/managers/server/routes.js +219 -219
  40. package/lib/managers/session.d.ts +3 -3
  41. package/lib/managers/session.js +125 -125
  42. package/lib/managers/socket.d.ts +3 -3
  43. package/lib/managers/socket.js +176 -176
  44. package/lib/managers/telemetry.d.ts +74 -74
  45. package/lib/managers/telemetry.js +206 -206
  46. package/lib/managers/test.js +84 -84
  47. package/lib/models/action.d.ts +2 -2
  48. package/lib/models/action.js +2 -2
  49. package/lib/models/audit.d.ts +15 -15
  50. package/lib/models/audit.js +2 -2
  51. package/lib/models/config-manager.d.ts +21 -21
  52. package/lib/models/config-manager.js +2 -2
  53. package/lib/models/config.d.ts +68 -67
  54. package/lib/models/config.js +2 -2
  55. package/lib/models/counter.d.ts +11 -11
  56. package/lib/models/counter.js +2 -2
  57. package/lib/models/errors.d.ts +15 -15
  58. package/lib/models/errors.js +2 -2
  59. package/lib/models/exercise-obj.d.ts +30 -30
  60. package/lib/models/exercise-obj.js +2 -2
  61. package/lib/models/file.d.ts +5 -5
  62. package/lib/models/file.js +2 -2
  63. package/lib/models/findings.d.ts +17 -17
  64. package/lib/models/findings.js +2 -2
  65. package/lib/models/flags.d.ts +10 -10
  66. package/lib/models/flags.js +2 -2
  67. package/lib/models/front-matter.d.ts +11 -11
  68. package/lib/models/front-matter.js +2 -2
  69. package/lib/models/gitpod-data.d.ts +16 -16
  70. package/lib/models/gitpod-data.js +2 -2
  71. package/lib/models/language.d.ts +4 -4
  72. package/lib/models/language.js +2 -2
  73. package/lib/models/package.d.ts +7 -7
  74. package/lib/models/package.js +2 -2
  75. package/lib/models/plugin-config.d.ts +16 -16
  76. package/lib/models/plugin-config.js +2 -2
  77. package/lib/models/session.d.ts +31 -31
  78. package/lib/models/session.js +2 -2
  79. package/lib/models/socket.d.ts +36 -32
  80. package/lib/models/socket.js +2 -2
  81. package/lib/models/status.d.ts +1 -1
  82. package/lib/models/status.js +2 -2
  83. package/lib/models/success-types.d.ts +1 -1
  84. package/lib/models/success-types.js +2 -2
  85. package/lib/plugin/command/compile.d.ts +6 -6
  86. package/lib/plugin/command/compile.js +18 -18
  87. package/lib/plugin/command/test.d.ts +6 -6
  88. package/lib/plugin/command/test.js +25 -25
  89. package/lib/plugin/index.d.ts +27 -27
  90. package/lib/plugin/index.js +7 -7
  91. package/lib/plugin/plugin.d.ts +8 -8
  92. package/lib/plugin/plugin.js +68 -68
  93. package/lib/plugin/utils.d.ts +16 -16
  94. package/lib/plugin/utils.js +58 -58
  95. package/lib/ui/download.d.ts +5 -5
  96. package/lib/ui/download.js +61 -61
  97. package/lib/utils/BaseCommand.d.ts +8 -8
  98. package/lib/utils/BaseCommand.js +41 -41
  99. package/lib/utils/SessionCommand.d.ts +10 -10
  100. package/lib/utils/SessionCommand.js +43 -43
  101. package/lib/utils/api.d.ts +14 -14
  102. package/lib/utils/api.js +255 -255
  103. package/lib/utils/audit.d.ts +16 -16
  104. package/lib/utils/audit.js +303 -303
  105. package/lib/utils/checkNotInstalled.d.ts +2 -0
  106. package/lib/utils/checkNotInstalled.js +36 -0
  107. package/lib/utils/console.d.ts +12 -12
  108. package/lib/utils/console.js +19 -19
  109. package/lib/utils/errors.d.ts +17 -17
  110. package/lib/utils/errors.js +100 -100
  111. package/lib/utils/exercisesQueue.d.ts +9 -9
  112. package/lib/utils/exercisesQueue.js +38 -38
  113. package/lib/utils/fileQueue.d.ts +40 -40
  114. package/lib/utils/fileQueue.js +168 -168
  115. package/lib/utils/misc.d.ts +1 -1
  116. package/lib/utils/misc.js +23 -23
  117. package/lib/utils/osOperations.d.ts +5 -5
  118. package/lib/utils/osOperations.js +72 -72
  119. package/lib/utils/validators.d.ts +5 -5
  120. package/lib/utils/validators.js +17 -17
  121. package/lib/utils/watcher.d.ts +2 -2
  122. package/lib/utils/watcher.js +25 -25
  123. package/oclif.manifest.json +1 -1
  124. package/package.json +139 -139
  125. package/src/commands/audit.ts +443 -443
  126. package/src/commands/clean.ts +29 -29
  127. package/src/commands/download.ts +61 -61
  128. package/src/commands/init.ts +170 -170
  129. package/src/commands/login.ts +42 -42
  130. package/src/commands/logout.ts +43 -43
  131. package/src/commands/publish.ts +107 -107
  132. package/src/commands/start.ts +53 -23
  133. package/src/commands/test.ts +85 -85
  134. package/src/index.ts +1 -1
  135. package/src/managers/config/allowed_files.ts +29 -29
  136. package/src/managers/config/defaults.ts +42 -42
  137. package/src/managers/config/exercise.ts +311 -311
  138. package/src/managers/config/index.ts +455 -455
  139. package/src/managers/file.ts +196 -196
  140. package/src/managers/gitpod.ts +84 -84
  141. package/src/managers/server/index.ts +78 -78
  142. package/src/managers/server/routes.ts +330 -330
  143. package/src/managers/session.ts +145 -145
  144. package/src/managers/socket.ts +250 -250
  145. package/src/managers/telemetry.ts +346 -346
  146. package/src/managers/test.ts +83 -83
  147. package/src/models/action.ts +10 -10
  148. package/src/models/audit.ts +16 -16
  149. package/src/models/config-manager.ts +23 -23
  150. package/src/models/config.ts +5 -3
  151. package/src/models/counter.ts +11 -11
  152. package/src/models/errors.ts +22 -22
  153. package/src/models/exercise-obj.ts +29 -29
  154. package/src/models/file.ts +5 -5
  155. package/src/models/findings.ts +18 -18
  156. package/src/models/flags.ts +10 -10
  157. package/src/models/front-matter.ts +11 -11
  158. package/src/models/gitpod-data.ts +19 -19
  159. package/src/models/language.ts +4 -4
  160. package/src/models/package.ts +7 -7
  161. package/src/models/plugin-config.ts +17 -17
  162. package/src/models/session.ts +34 -34
  163. package/src/models/socket.ts +5 -0
  164. package/src/models/status.ts +16 -16
  165. package/src/models/success-types.ts +1 -1
  166. package/src/plugin/command/compile.ts +17 -17
  167. package/src/plugin/command/test.ts +30 -30
  168. package/src/plugin/index.ts +6 -6
  169. package/src/plugin/plugin.ts +94 -94
  170. package/src/plugin/utils.ts +87 -87
  171. package/src/types/node-fetch.d.ts +1 -1
  172. package/src/ui/download.ts +71 -71
  173. package/src/utils/BaseCommand.ts +48 -48
  174. package/src/utils/SessionCommand.ts +43 -43
  175. package/src/utils/api.ts +303 -303
  176. package/src/utils/audit.ts +393 -393
  177. package/src/utils/checkNotInstalled.ts +46 -0
  178. package/src/utils/console.ts +24 -24
  179. package/src/utils/errors.ts +117 -117
  180. package/src/utils/exercisesQueue.ts +51 -51
  181. package/src/utils/fileQueue.ts +198 -198
  182. package/src/utils/misc.ts +23 -23
  183. package/src/utils/osOperations.ts +79 -79
  184. package/src/utils/templates/gitignore.txt +19 -19
  185. package/src/utils/templates/incremental/.learn/exercises/01-hello-world/README.es.md +24 -24
  186. package/src/utils/templates/incremental/.learn/exercises/01-hello-world/README.md +24 -24
  187. package/src/utils/templates/incremental/README.ejs +4 -4
  188. package/src/utils/templates/incremental/README.es.ejs +4 -4
  189. package/src/utils/templates/isolated/01-hello-world/README.es.md +26 -26
  190. package/src/utils/templates/isolated/01-hello-world/README.md +26 -26
  191. package/src/utils/templates/isolated/README.ejs +4 -4
  192. package/src/utils/templates/isolated/README.es.ejs +4 -4
  193. package/src/utils/templates/no-grading/README.ejs +4 -4
  194. package/src/utils/templates/no-grading/README.es.ejs +4 -4
  195. package/src/utils/validators.ts +18 -18
  196. package/src/utils/watcher.ts +27 -27
@@ -1,346 +1,346 @@
1
- import { IFile } from "../models/file"
2
- import API from "../utils/api"
3
- import Console from "../utils/console"
4
-
5
- const fs = require("fs")
6
-
7
- function createUUID(): string {
8
- return (
9
- Math.random().toString(36).slice(2, 10) +
10
- Math.random().toString(36).slice(2, 10)
11
- )
12
- }
13
-
14
- function stringToBase64(input: string): string {
15
- return Buffer.from(input).toString("base64")
16
- }
17
-
18
- type TCompilationAttempt = {
19
- source_code: string;
20
- stdout: string;
21
- exit_code: number;
22
- starting_at: number;
23
- ending_at: number;
24
- };
25
-
26
- type TTestAttempt = {
27
- source_code: string;
28
- stdout: string;
29
- exit_code: number;
30
- starting_at: number;
31
- ending_at: number;
32
- };
33
-
34
- type TAIInteraction = {
35
- student_message: string;
36
- source_code: string;
37
- ai_response: string;
38
- starting_at: number;
39
- ending_at: number;
40
- };
41
-
42
- export type TStep = {
43
- slug: string;
44
- position: number;
45
- files: IFile[];
46
- is_testeable: boolean;
47
- opened_at?: number; // The time when the step was opened
48
- completed_at?: number; // If the step has tests, the time when all the tests passed, else, the time when the user opens the next step
49
- compilations: TCompilationAttempt[]; // Everytime the user tries to compile the code
50
- tests: TTestAttempt[]; // Everytime the user tries to run the tests
51
- ai_interactions: TAIInteraction[]; // Everytime the user interacts with the AI
52
- };
53
-
54
- type TWorkoutSession = {
55
- started_at: number;
56
- ended_at?: number;
57
- };
58
-
59
- type TStudent = {
60
- token: string;
61
- user_id: string;
62
- email: string;
63
- };
64
-
65
- export interface ITelemetryJSONSchema {
66
- telemetry_id?: string;
67
- user_id?: number | string;
68
- slug: string;
69
- agent?: string;
70
- tutorial_started_at?: number;
71
- last_interaction_at?: number;
72
- steps: Array<TStep>; // The steps should be the same as the exercise
73
- workout_session: TWorkoutSession[]; // It start when the user starts Learnpack, if the last_interaction_at is available, it automatically fills with that
74
- // number and start another session
75
- }
76
-
77
- type TStepEvent = "compile" | "test" | "ai_interaction" | "open_step";
78
-
79
- export type TTelemetryUrls = {
80
- streaming?: string;
81
- batch?: string;
82
- };
83
-
84
- interface ITelemetryManager {
85
- current: ITelemetryJSONSchema | null;
86
- configPath: string | null;
87
- urls: TTelemetryUrls;
88
- salute: (message: string) => void;
89
- start: (
90
- agent: string,
91
- steps: TStep[],
92
- path: string,
93
- tutorialSlug: string
94
- ) => void;
95
- prevStep?: number;
96
- registerStepEvent: (
97
- stepPosition: number,
98
- event: TStepEvent,
99
- data: any
100
- ) => void;
101
- streamEvent: (stepPosition: number, event: string, data: any) => void;
102
- submit: () => Promise<void>;
103
- finishWorkoutSession: () => void;
104
- setStudent: (student: TStudent) => void;
105
- save: () => void;
106
- retrieve: (
107
- agent: string,
108
- steps: TStep[]
109
- ) => Promise<ITelemetryJSONSchema | null>;
110
- }
111
-
112
- const TelemetryManager: ITelemetryManager = {
113
- current: null,
114
- urls: {},
115
- configPath: "",
116
- salute: message => {
117
- Console.info(message)
118
- },
119
-
120
- start: function (agent, steps, path, tutorialSlug) {
121
- this.configPath = path
122
- if (!this.current) {
123
- this.retrieve(agent, steps)
124
- .then(data => {
125
- const prevTelemetry = data
126
- if (prevTelemetry) {
127
- this.current = prevTelemetry
128
- this.finishWorkoutSession()
129
- } else {
130
- this.current = {
131
- telemetry_id: createUUID(),
132
- slug: tutorialSlug,
133
- agent,
134
- tutorial_started_at: Date.now(),
135
- steps,
136
- workout_session: [
137
- {
138
- started_at: Date.now(),
139
- },
140
- ],
141
- }
142
- }
143
-
144
- this.save()
145
- this.submit()
146
- })
147
- .catch(error => {
148
- Console.debug(error)
149
- })
150
- }
151
- },
152
- // verifyStudent: function () {
153
- // if (!this.current) {
154
- // return;
155
- // }
156
-
157
- // if (!this.current.user_id) {
158
-
159
- // }
160
- // },
161
-
162
- setStudent: function (student) {
163
- if (!this.current) {
164
- return
165
- }
166
-
167
- this.current.user_id = student.user_id
168
- this.save()
169
- this.submit()
170
- },
171
- finishWorkoutSession: function () {
172
- if (!this.current) {
173
- return
174
- }
175
-
176
- const lastSession =
177
- this.current?.workout_session[this.current.workout_session.length - 1]
178
- if (
179
- lastSession &&
180
- !lastSession.ended_at &&
181
- this.current?.last_interaction_at
182
- ) {
183
- lastSession.ended_at = this.current.last_interaction_at
184
- this.current.workout_session.push({
185
- started_at: Date.now(),
186
- })
187
- }
188
- },
189
-
190
- registerStepEvent: function (stepPosition, event, data) {
191
- if (!this.current) {
192
- // throw new Error("Telemetry has not been started");
193
- return
194
- }
195
-
196
- const step = this.current.steps[stepPosition]
197
- if (!step) {
198
- return
199
- }
200
-
201
- if (data.source_code) {
202
- data.source_code = stringToBase64(data.source_code)
203
- }
204
-
205
- if (data.stdout) {
206
- data.stdout = stringToBase64(data.stdout)
207
- }
208
-
209
- if (data.stderr) {
210
- data.stderr = stringToBase64(data.stderr)
211
- }
212
-
213
- if (Object.prototype.hasOwnProperty.call(data, "exitCode")) {
214
- data.exit_code = data.exitCode
215
- data.exitCode = undefined
216
- }
217
-
218
- switch (event) {
219
- case "compile":
220
- if (!step.compilations) {
221
- step.compilations = []
222
- }
223
-
224
- step.compilations.push(data)
225
- this.current.steps[stepPosition] = step
226
- break
227
- case "test":
228
- if (!step.tests) {
229
- step.tests = []
230
- }
231
-
232
- // data.stdout =
233
- step.tests.push(data)
234
- if (data.exit_code === 0) {
235
- step.completed_at = Date.now()
236
- }
237
-
238
- this.current.steps[stepPosition] = step
239
- break
240
- case "ai_interaction":
241
- if (!step.ai_interactions) {
242
- step.ai_interactions = []
243
- }
244
-
245
- step.ai_interactions.push(data)
246
- break
247
- case "open_step": {
248
- const now = Date.now()
249
-
250
- if (!step.opened_at) {
251
- step.opened_at = now
252
- this.current.steps[stepPosition] = step
253
- }
254
-
255
- if (this.prevStep || this.prevStep === 0) {
256
- const prevStep = this.current.steps[this.prevStep]
257
- if (!prevStep.is_testeable && !prevStep.completed_at) {
258
- prevStep.completed_at = now
259
- this.current.steps[this.prevStep] = prevStep
260
- }
261
- }
262
-
263
- this.prevStep = stepPosition
264
-
265
- this.submit()
266
- break
267
- }
268
-
269
- default:
270
- throw new Error(`Event type ${event} is not supported`)
271
- }
272
-
273
- this.current.last_interaction_at = Date.now()
274
- this.streamEvent(stepPosition, event, data)
275
- this.save()
276
- },
277
- retrieve: function () {
278
- return new Promise((resolve, reject) => {
279
- fs.readFile(
280
- `${this.configPath}/telemetry.json`,
281
- "utf8",
282
- (err: any, data: any) => {
283
- if (err) {
284
- if (err.code === "ENOENT") {
285
- // File does not exist, resolve with undefined
286
- resolve(null)
287
- } else {
288
- reject(err)
289
- }
290
- } else {
291
- resolve(JSON.parse(data))
292
- }
293
- }
294
- )
295
- })
296
- },
297
- submit: async function () {
298
- Console.debug("Submitting telemetry...")
299
-
300
- if (!this.current)
301
- return Promise.resolve()
302
- const url = this.urls.batch
303
- if (!url) {
304
- return
305
- }
306
-
307
- const body = this.current
308
-
309
- API.sendBatchTelemetry(url, body)
310
- },
311
- save: function () {
312
- fs.writeFile(
313
- `${this.configPath}/telemetry.json`,
314
- JSON.stringify(this.current),
315
- (err: any) => {
316
- if (err)
317
- throw err
318
- }
319
- )
320
- },
321
-
322
- streamEvent: async function (stepPosition, event, data) {
323
- if (!this.current)
324
- return
325
-
326
- const url = this.urls.streaming
327
- if (!url) {
328
- return
329
- }
330
-
331
- const stepSlug = this.current.steps[stepPosition].slug
332
-
333
- const body = {
334
- slug: stepSlug,
335
- telemetry_id: this.current.telemetry_id,
336
- user_id: this.current.user_id,
337
- step_position: stepPosition,
338
- event,
339
- data,
340
- }
341
-
342
- API.sendStreamTelemetry(url, body)
343
- },
344
- }
345
-
346
- export default TelemetryManager
1
+ import { IFile } from "../models/file"
2
+ import API from "../utils/api"
3
+ import Console from "../utils/console"
4
+
5
+ const fs = require("fs")
6
+
7
+ function createUUID(): string {
8
+ return (
9
+ Math.random().toString(36).slice(2, 10) +
10
+ Math.random().toString(36).slice(2, 10)
11
+ )
12
+ }
13
+
14
+ function stringToBase64(input: string): string {
15
+ return Buffer.from(input).toString("base64")
16
+ }
17
+
18
+ type TCompilationAttempt = {
19
+ source_code: string;
20
+ stdout: string;
21
+ exit_code: number;
22
+ starting_at: number;
23
+ ending_at: number;
24
+ };
25
+
26
+ type TTestAttempt = {
27
+ source_code: string;
28
+ stdout: string;
29
+ exit_code: number;
30
+ starting_at: number;
31
+ ending_at: number;
32
+ };
33
+
34
+ type TAIInteraction = {
35
+ student_message: string;
36
+ source_code: string;
37
+ ai_response: string;
38
+ starting_at: number;
39
+ ending_at: number;
40
+ };
41
+
42
+ export type TStep = {
43
+ slug: string;
44
+ position: number;
45
+ files: IFile[];
46
+ is_testeable: boolean;
47
+ opened_at?: number; // The time when the step was opened
48
+ completed_at?: number; // If the step has tests, the time when all the tests passed, else, the time when the user opens the next step
49
+ compilations: TCompilationAttempt[]; // Everytime the user tries to compile the code
50
+ tests: TTestAttempt[]; // Everytime the user tries to run the tests
51
+ ai_interactions: TAIInteraction[]; // Everytime the user interacts with the AI
52
+ };
53
+
54
+ type TWorkoutSession = {
55
+ started_at: number;
56
+ ended_at?: number;
57
+ };
58
+
59
+ type TStudent = {
60
+ token: string;
61
+ user_id: string;
62
+ email: string;
63
+ };
64
+
65
+ export interface ITelemetryJSONSchema {
66
+ telemetry_id?: string;
67
+ user_id?: number | string;
68
+ slug: string;
69
+ agent?: string;
70
+ tutorial_started_at?: number;
71
+ last_interaction_at?: number;
72
+ steps: Array<TStep>; // The steps should be the same as the exercise
73
+ workout_session: TWorkoutSession[]; // It start when the user starts Learnpack, if the last_interaction_at is available, it automatically fills with that
74
+ // number and start another session
75
+ }
76
+
77
+ type TStepEvent = "compile" | "test" | "ai_interaction" | "open_step";
78
+
79
+ export type TTelemetryUrls = {
80
+ streaming?: string;
81
+ batch?: string;
82
+ };
83
+
84
+ interface ITelemetryManager {
85
+ current: ITelemetryJSONSchema | null;
86
+ configPath: string | null;
87
+ urls: TTelemetryUrls;
88
+ salute: (message: string) => void;
89
+ start: (
90
+ agent: string,
91
+ steps: TStep[],
92
+ path: string,
93
+ tutorialSlug: string
94
+ ) => void;
95
+ prevStep?: number;
96
+ registerStepEvent: (
97
+ stepPosition: number,
98
+ event: TStepEvent,
99
+ data: any
100
+ ) => void;
101
+ streamEvent: (stepPosition: number, event: string, data: any) => void;
102
+ submit: () => Promise<void>;
103
+ finishWorkoutSession: () => void;
104
+ setStudent: (student: TStudent) => void;
105
+ save: () => void;
106
+ retrieve: (
107
+ agent: string,
108
+ steps: TStep[]
109
+ ) => Promise<ITelemetryJSONSchema | null>;
110
+ }
111
+
112
+ const TelemetryManager: ITelemetryManager = {
113
+ current: null,
114
+ urls: {},
115
+ configPath: "",
116
+ salute: message => {
117
+ Console.info(message)
118
+ },
119
+
120
+ start: function (agent, steps, path, tutorialSlug) {
121
+ this.configPath = path
122
+ if (!this.current) {
123
+ this.retrieve(agent, steps)
124
+ .then(data => {
125
+ const prevTelemetry = data
126
+ if (prevTelemetry) {
127
+ this.current = prevTelemetry
128
+ this.finishWorkoutSession()
129
+ } else {
130
+ this.current = {
131
+ telemetry_id: createUUID(),
132
+ slug: tutorialSlug,
133
+ agent,
134
+ tutorial_started_at: Date.now(),
135
+ steps,
136
+ workout_session: [
137
+ {
138
+ started_at: Date.now(),
139
+ },
140
+ ],
141
+ }
142
+ }
143
+
144
+ this.save()
145
+ this.submit()
146
+ })
147
+ .catch(error => {
148
+ Console.debug(error)
149
+ })
150
+ }
151
+ },
152
+ // verifyStudent: function () {
153
+ // if (!this.current) {
154
+ // return;
155
+ // }
156
+
157
+ // if (!this.current.user_id) {
158
+
159
+ // }
160
+ // },
161
+
162
+ setStudent: function (student) {
163
+ if (!this.current) {
164
+ return
165
+ }
166
+
167
+ this.current.user_id = student.user_id
168
+ this.save()
169
+ this.submit()
170
+ },
171
+ finishWorkoutSession: function () {
172
+ if (!this.current) {
173
+ return
174
+ }
175
+
176
+ const lastSession =
177
+ this.current?.workout_session[this.current.workout_session.length - 1]
178
+ if (
179
+ lastSession &&
180
+ !lastSession.ended_at &&
181
+ this.current?.last_interaction_at
182
+ ) {
183
+ lastSession.ended_at = this.current.last_interaction_at
184
+ this.current.workout_session.push({
185
+ started_at: Date.now(),
186
+ })
187
+ }
188
+ },
189
+
190
+ registerStepEvent: function (stepPosition, event, data) {
191
+ if (!this.current) {
192
+ // throw new Error("Telemetry has not been started");
193
+ return
194
+ }
195
+
196
+ const step = this.current.steps[stepPosition]
197
+ if (!step) {
198
+ return
199
+ }
200
+
201
+ if (data.source_code) {
202
+ data.source_code = stringToBase64(data.source_code)
203
+ }
204
+
205
+ if (data.stdout) {
206
+ data.stdout = stringToBase64(data.stdout)
207
+ }
208
+
209
+ if (data.stderr) {
210
+ data.stderr = stringToBase64(data.stderr)
211
+ }
212
+
213
+ if (Object.prototype.hasOwnProperty.call(data, "exitCode")) {
214
+ data.exit_code = data.exitCode
215
+ data.exitCode = undefined
216
+ }
217
+
218
+ switch (event) {
219
+ case "compile":
220
+ if (!step.compilations) {
221
+ step.compilations = []
222
+ }
223
+
224
+ step.compilations.push(data)
225
+ this.current.steps[stepPosition] = step
226
+ break
227
+ case "test":
228
+ if (!step.tests) {
229
+ step.tests = []
230
+ }
231
+
232
+ // data.stdout =
233
+ step.tests.push(data)
234
+ if (data.exit_code === 0) {
235
+ step.completed_at = Date.now()
236
+ }
237
+
238
+ this.current.steps[stepPosition] = step
239
+ break
240
+ case "ai_interaction":
241
+ if (!step.ai_interactions) {
242
+ step.ai_interactions = []
243
+ }
244
+
245
+ step.ai_interactions.push(data)
246
+ break
247
+ case "open_step": {
248
+ const now = Date.now()
249
+
250
+ if (!step.opened_at) {
251
+ step.opened_at = now
252
+ this.current.steps[stepPosition] = step
253
+ }
254
+
255
+ if (this.prevStep || this.prevStep === 0) {
256
+ const prevStep = this.current.steps[this.prevStep]
257
+ if (!prevStep.is_testeable && !prevStep.completed_at) {
258
+ prevStep.completed_at = now
259
+ this.current.steps[this.prevStep] = prevStep
260
+ }
261
+ }
262
+
263
+ this.prevStep = stepPosition
264
+
265
+ this.submit()
266
+ break
267
+ }
268
+
269
+ default:
270
+ throw new Error(`Event type ${event} is not supported`)
271
+ }
272
+
273
+ this.current.last_interaction_at = Date.now()
274
+ this.streamEvent(stepPosition, event, data)
275
+ this.save()
276
+ },
277
+ retrieve: function () {
278
+ return new Promise((resolve, reject) => {
279
+ fs.readFile(
280
+ `${this.configPath}/telemetry.json`,
281
+ "utf8",
282
+ (err: any, data: any) => {
283
+ if (err) {
284
+ if (err.code === "ENOENT") {
285
+ // File does not exist, resolve with undefined
286
+ resolve(null)
287
+ } else {
288
+ reject(err)
289
+ }
290
+ } else {
291
+ resolve(JSON.parse(data))
292
+ }
293
+ }
294
+ )
295
+ })
296
+ },
297
+ submit: async function () {
298
+ Console.debug("Submitting telemetry...")
299
+
300
+ if (!this.current)
301
+ return Promise.resolve()
302
+ const url = this.urls.batch
303
+ if (!url) {
304
+ return
305
+ }
306
+
307
+ const body = this.current
308
+
309
+ API.sendBatchTelemetry(url, body)
310
+ },
311
+ save: function () {
312
+ fs.writeFile(
313
+ `${this.configPath}/telemetry.json`,
314
+ JSON.stringify(this.current),
315
+ (err: any) => {
316
+ if (err)
317
+ throw err
318
+ }
319
+ )
320
+ },
321
+
322
+ streamEvent: async function (stepPosition, event, data) {
323
+ if (!this.current)
324
+ return
325
+
326
+ const url = this.urls.streaming
327
+ if (!url) {
328
+ return
329
+ }
330
+
331
+ const stepSlug = this.current.steps[stepPosition].slug
332
+
333
+ const body = {
334
+ slug: stepSlug,
335
+ telemetry_id: this.current.telemetry_id,
336
+ user_id: this.current.user_id,
337
+ step_position: stepPosition,
338
+ event,
339
+ data,
340
+ }
341
+
342
+ API.sendStreamTelemetry(url, body)
343
+ },
344
+ }
345
+
346
+ export default TelemetryManager