@playcademy/sdk 0.24.0 → 0.24.1-beta.2

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/dist/index.d.ts CHANGED
@@ -225,132 +225,6 @@ declare const ASSESSMENT_PURPOSES: readonly ['end_of_course', 'diagnostic', 'rev
225
225
  */
226
226
  declare const PLAYCADEMY_ASSESSMENT_CONTRACT_VERSION: 2;
227
227
 
228
- /**
229
- * User Types
230
- *
231
- * Enums, DTOs and API response types. Database row types are in @playcademy/data/types.
232
- *
233
- * @module types/user
234
- */
235
-
236
- type UserRoleEnumType = UserRole;
237
- type DeveloperStatusEnumType = 'none' | 'pending' | 'approved';
238
- type TimebackUserRole = 'administrator' | 'aide' | 'guardian' | 'parent' | 'proctor' | 'relative' | 'student' | 'teacher';
239
- type TimebackOrgType = 'department' | 'school' | 'district' | 'local' | 'state' | 'national';
240
- interface UserEnrollment {
241
- gameId?: string;
242
- courseId: string;
243
- id?: string;
244
- grade: number;
245
- subject: string;
246
- orgId?: string;
247
- }
248
- interface UserOrganization {
249
- id: string;
250
- name: string | null;
251
- type: TimebackOrgType | string;
252
- isPrimary: boolean;
253
- }
254
- interface TimebackStudentProfile {
255
- role: TimebackUserRole;
256
- organizations: UserOrganization[];
257
- }
258
- /**
259
- * Timeback identity on `/users/me`: who the student is, without enrollments.
260
- * Enrollments come from `/timeback/user` (or `client.timeback.user`).
261
- */
262
- interface UserTimebackProfile extends TimebackStudentProfile {
263
- id: string;
264
- }
265
- type LocalDaySource = 'browser_last_seen' | 'platform_default';
266
- interface LocalDayContext {
267
- timeZone: string;
268
- source: LocalDaySource;
269
- /** ISO 8601 timestamp for the last persisted timezone observation. */
270
- observedAt: string | null;
271
- }
272
- /**
273
- * OpenID Connect UserInfo claims (NOT a database row).
274
- */
275
- interface UserInfo {
276
- sub: string;
277
- email: string;
278
- name: string | null;
279
- email_verified?: boolean;
280
- given_name?: string;
281
- family_name?: string;
282
- issuer?: string;
283
- lti_roles?: unknown;
284
- lti_context?: unknown;
285
- lti_resource_link?: unknown;
286
- timeback_id?: string;
287
- }
288
- interface DemoProfile {
289
- displayName: string;
290
- isDefault: boolean;
291
- }
292
- /**
293
- * Update shape for `client.demo.profile.update(...)`.
294
- *
295
- * Kept as a named type so callers typed against it pick up new fields
296
- * automatically, but `displayName` is the only updatable field today and
297
- * the server's `DemoProfileSchema` requires it — a no-field payload would
298
- * 400 at runtime, so we model that at the type level too. When additional
299
- * fields land, make them required/optional individually based on server
300
- * validation, rather than blanket-optional.
301
- */
302
- interface DemoProfileUpdate {
303
- displayName: string;
304
- }
305
- /**
306
- * Authenticated user for API responses.
307
- * Differs from UserRow: omits timebackId, adds hasTimebackAccount and timeback.
308
- */
309
- interface AuthenticatedUser {
310
- id: string;
311
- email: string;
312
- emailVerified: boolean;
313
- name: string | null;
314
- image: string | null;
315
- username: string | null;
316
- role: UserRoleEnumType;
317
- developerStatus: DeveloperStatusEnumType;
318
- createdAt: Date;
319
- updatedAt: Date;
320
- hasTimebackAccount: boolean;
321
- localDay: LocalDayContext;
322
- timeback?: UserTimebackProfile;
323
- }
324
-
325
- /**
326
- * Leaderboard Types
327
- *
328
- * @module types/leaderboard
329
- */
330
- type LeaderboardTimeframe = 'all_time' | 'monthly' | 'weekly' | 'daily';
331
- interface LeaderboardOptions {
332
- timeframe?: LeaderboardTimeframe;
333
- limit?: number;
334
- offset?: number;
335
- gameId?: string;
336
- }
337
- /**
338
- * Leaderboard entry with required game context.
339
- * Used when fetching leaderboards for a specific game.
340
- */
341
- interface GameLeaderboardEntry {
342
- rank: number;
343
- userId: string;
344
- username: string;
345
- userImage?: string | null;
346
- score: number;
347
- achievedAt: Date;
348
- metadata?: Record<string, unknown>;
349
- gameId: string;
350
- gameTitle: string;
351
- gameSlug: string;
352
- }
353
-
354
228
  /**
355
229
  * TimeBack Enums & Literal Types
356
230
  *
@@ -423,8 +297,18 @@ interface ActivityData {
423
297
  subject: CaliperSubject;
424
298
  /** Activity display name (optional) */
425
299
  activityName?: string;
426
- /** Course identifier (auto-filled from config if not provided) */
300
+ /**
301
+ * Optional course to report against. When omitted, the SDK fills it from
302
+ * the student's enrollment for this grade/subject (preferring a
303
+ * hole-filling course). The platform uses it if it's the base course or one
304
+ * of that grade/subject's hole-filling courses, otherwise the base course.
305
+ */
427
306
  courseId?: string;
307
+ /**
308
+ * The student's enrollment in `courseId`. Filled by the SDK alongside an
309
+ * automatic `courseId`; recorded on hole-filling Caliper events.
310
+ */
311
+ enrollmentId?: string;
428
312
  /** Course display name (auto-filled from config if not provided) */
429
313
  courseName?: string;
430
314
  /** Student email address (optional) */
@@ -494,6 +378,12 @@ interface EndActivityScoreData {
494
378
  * @module types/timeback/api
495
379
  */
496
380
 
381
+ /**
382
+ * `base` courses form a game's grade ladder (one active per grade and subject).
383
+ * `hole_filling` courses are remediation courses Timeback enrolls students into
384
+ * after a failed test; each sits beside a base course.
385
+ */
386
+ type GameTimebackIntegrationKind = 'base' | 'hole_filling';
497
387
  interface HeartbeatRequestBase {
498
388
  gameId: string;
499
389
  studentId: string;
@@ -515,6 +405,8 @@ interface HeartbeatRequestBase {
515
405
  appName?: string;
516
406
  sensorUrl?: string;
517
407
  courseId?: string;
408
+ /** The student's enrollment in `courseId`, sent by the SDK with the course hint. */
409
+ enrollmentId?: string;
518
410
  courseName?: string;
519
411
  studentEmail?: string;
520
412
  };
@@ -568,6 +460,8 @@ interface EndActivityRequest {
568
460
  appName?: string;
569
461
  sensorUrl?: string;
570
462
  courseId?: string;
463
+ /** The student's enrollment in `courseId`, sent by the SDK with the course hint. */
464
+ enrollmentId?: string;
571
465
  courseName?: string;
572
466
  studentEmail?: string;
573
467
  };
@@ -597,7 +491,14 @@ interface TimebackPromotionResult {
597
491
  }
598
492
  interface EndActivityResponse {
599
493
  status: 'ok';
494
+ /** The course the activity was recorded against. */
600
495
  courseId: string;
496
+ /**
497
+ * `'hole_filling'` when the activity was recorded against a hole-filling
498
+ * course (the student is enrolled there, or the game passed its
499
+ * `courseId`); `'base'` otherwise.
500
+ */
501
+ courseKind?: GameTimebackIntegrationKind;
601
502
  xpAwarded: number;
602
503
  masteredUnits?: number;
603
504
  pctCompleteApp?: number;
@@ -1353,6 +1254,146 @@ interface FinalizeAssessmentInput extends AssessmentMasteryAward {
1353
1254
  xpAwarded: number;
1354
1255
  }
1355
1256
 
1257
+ /**
1258
+ * User Types
1259
+ *
1260
+ * Enums, DTOs and API response types. Database row types are in @playcademy/data/types.
1261
+ *
1262
+ * @module types/user
1263
+ */
1264
+
1265
+ type UserRoleEnumType = UserRole;
1266
+ type DeveloperStatusEnumType = 'none' | 'pending' | 'approved';
1267
+ type TimebackUserRole = 'administrator' | 'aide' | 'guardian' | 'parent' | 'proctor' | 'relative' | 'student' | 'teacher';
1268
+ type TimebackOrgType = 'department' | 'school' | 'district' | 'local' | 'state' | 'national';
1269
+ interface UserEnrollment {
1270
+ gameId?: string;
1271
+ courseId: string;
1272
+ id?: string;
1273
+ grade: number;
1274
+ subject: string;
1275
+ orgId?: string;
1276
+ /**
1277
+ * `base` for the game's grade courses, `hole_filling` for a remediation
1278
+ * course Timeback enrolled the student in. Absent means `base`.
1279
+ */
1280
+ kind?: GameTimebackIntegrationKind;
1281
+ /** For hole-filling enrollments: the course ID of the base course it belongs to. */
1282
+ baseCourseId?: string;
1283
+ /** For hole-filling enrollments: when the enrollment started (ISO 8601). */
1284
+ beginDate?: string | null;
1285
+ /**
1286
+ * For hole-filling enrollments: enrollment metadata as set by Timeback
1287
+ * (e.g. `assignmentId`, `testType`). Every field is optional.
1288
+ */
1289
+ enrollmentMetadata?: Record<string, unknown>;
1290
+ }
1291
+ interface UserOrganization {
1292
+ id: string;
1293
+ name: string | null;
1294
+ type: TimebackOrgType | string;
1295
+ isPrimary: boolean;
1296
+ }
1297
+ interface TimebackStudentProfile {
1298
+ role: TimebackUserRole;
1299
+ organizations: UserOrganization[];
1300
+ }
1301
+ /**
1302
+ * Timeback identity on `/users/me`: who the student is, without enrollments.
1303
+ * Enrollments come from `/timeback/user` (or `client.timeback.user`).
1304
+ */
1305
+ interface UserTimebackProfile extends TimebackStudentProfile {
1306
+ id: string;
1307
+ }
1308
+ type LocalDaySource = 'browser_last_seen' | 'platform_default';
1309
+ interface LocalDayContext {
1310
+ timeZone: string;
1311
+ source: LocalDaySource;
1312
+ /** ISO 8601 timestamp for the last persisted timezone observation. */
1313
+ observedAt: string | null;
1314
+ }
1315
+ /**
1316
+ * OpenID Connect UserInfo claims (NOT a database row).
1317
+ */
1318
+ interface UserInfo {
1319
+ sub: string;
1320
+ email: string;
1321
+ name: string | null;
1322
+ email_verified?: boolean;
1323
+ given_name?: string;
1324
+ family_name?: string;
1325
+ issuer?: string;
1326
+ lti_roles?: unknown;
1327
+ lti_context?: unknown;
1328
+ lti_resource_link?: unknown;
1329
+ timeback_id?: string;
1330
+ }
1331
+ interface DemoProfile {
1332
+ displayName: string;
1333
+ isDefault: boolean;
1334
+ }
1335
+ /**
1336
+ * Update shape for `client.demo.profile.update(...)`.
1337
+ *
1338
+ * Kept as a named type so callers typed against it pick up new fields
1339
+ * automatically, but `displayName` is the only updatable field today and
1340
+ * the server's `DemoProfileSchema` requires it — a no-field payload would
1341
+ * 400 at runtime, so we model that at the type level too. When additional
1342
+ * fields land, make them required/optional individually based on server
1343
+ * validation, rather than blanket-optional.
1344
+ */
1345
+ interface DemoProfileUpdate {
1346
+ displayName: string;
1347
+ }
1348
+ /**
1349
+ * Authenticated user for API responses.
1350
+ * Differs from UserRow: omits timebackId, adds hasTimebackAccount and timeback.
1351
+ */
1352
+ interface AuthenticatedUser {
1353
+ id: string;
1354
+ email: string;
1355
+ emailVerified: boolean;
1356
+ name: string | null;
1357
+ image: string | null;
1358
+ username: string | null;
1359
+ role: UserRoleEnumType;
1360
+ developerStatus: DeveloperStatusEnumType;
1361
+ createdAt: Date;
1362
+ updatedAt: Date;
1363
+ hasTimebackAccount: boolean;
1364
+ localDay: LocalDayContext;
1365
+ timeback?: UserTimebackProfile;
1366
+ }
1367
+
1368
+ /**
1369
+ * Leaderboard Types
1370
+ *
1371
+ * @module types/leaderboard
1372
+ */
1373
+ type LeaderboardTimeframe = 'all_time' | 'monthly' | 'weekly' | 'daily';
1374
+ interface LeaderboardOptions {
1375
+ timeframe?: LeaderboardTimeframe;
1376
+ limit?: number;
1377
+ offset?: number;
1378
+ gameId?: string;
1379
+ }
1380
+ /**
1381
+ * Leaderboard entry with required game context.
1382
+ * Used when fetching leaderboards for a specific game.
1383
+ */
1384
+ interface GameLeaderboardEntry {
1385
+ rank: number;
1386
+ userId: string;
1387
+ username: string;
1388
+ userImage?: string | null;
1389
+ score: number;
1390
+ achievedAt: Date;
1391
+ metadata?: Record<string, unknown>;
1392
+ gameId: string;
1393
+ gameTitle: string;
1394
+ gameSlug: string;
1395
+ }
1396
+
1356
1397
  /**
1357
1398
  * Cache configuration types for runtime customization
1358
1399
  */
@@ -3020,6 +3061,8 @@ interface EmbedTimebackRecording {
3020
3061
  subject: TimebackSubject;
3021
3062
  /** Course id hint, same semantics as `startActivity()`. */
3022
3063
  courseId?: string;
3064
+ /** The student's enrollment in `courseId`; filled with it automatically. */
3065
+ enrollmentId?: string;
3023
3066
  }
3024
3067
  /**
3025
3068
  * Everything a parent needs to resume an interrupted launch later. The
package/dist/index.js CHANGED
@@ -2289,6 +2289,23 @@ function createTimebackActivityTracker(client) {
2289
2289
  };
2290
2290
  }
2291
2291
 
2292
+ // src/core/timeback/course-hint.ts
2293
+ function withEnrollmentCourseHint(metadata, enrollments) {
2294
+ if (metadata.courseId !== undefined) {
2295
+ return metadata;
2296
+ }
2297
+ const matching = enrollments.filter((enrollment) => enrollment.grade === metadata.grade && enrollment.subject === metadata.subject);
2298
+ const enrollment = matching.find((candidate) => candidate.kind === "hole_filling") ?? matching[0];
2299
+ if (!enrollment) {
2300
+ return metadata;
2301
+ }
2302
+ return {
2303
+ ...metadata,
2304
+ courseId: enrollment.courseId,
2305
+ ...enrollment.id && { enrollmentId: enrollment.id }
2306
+ };
2307
+ }
2308
+
2292
2309
  // src/namespaces/game/embed.ts
2293
2310
  function createEmbedNamespace(client) {
2294
2311
  return {
@@ -2325,7 +2342,7 @@ function createEmbedNamespace(client) {
2325
2342
  const session = new ChildSession({
2326
2343
  slug: options.slug,
2327
2344
  container: options.container,
2328
- timeback: options.timeback,
2345
+ timeback: options.timeback && withEnrollmentCourseHint(options.timeback, client.timeback.user?.enrollments ?? []),
2329
2346
  resume: resume.envelope,
2330
2347
  resumeStore: resume.store,
2331
2348
  reporter: {
@@ -3023,7 +3040,7 @@ function createTimebackEngine(client) {
3023
3040
  activity: {
3024
3041
  currentRunId: activityTracker.currentRunId,
3025
3042
  isManuallyPaused: activityTracker.isActivityManuallyPaused,
3026
- start: activityTracker.startActivity,
3043
+ start: (metadata, options) => activityTracker.startActivity(withEnrollmentCourseHint(metadata, userStore.snapshot()?.enrollments ?? []), options),
3027
3044
  tryStartAssessment: activityTracker.tryStartAssessmentTracking,
3028
3045
  pause: activityTracker.pauseActivity,
3029
3046
  resume: activityTracker.resumeActivity,
@@ -3765,7 +3782,7 @@ async function request({
3765
3782
  return rawText && rawText.length > 0 ? rawText : undefined;
3766
3783
  }
3767
3784
  // src/version.ts
3768
- var SDK_VERSION = "0.24.0";
3785
+ var SDK_VERSION = "0.24.1-beta.2";
3769
3786
 
3770
3787
  // src/clients/base.ts
3771
3788
  class PlaycademyBaseClient {