@playcademy/sdk 0.2.11 → 0.2.13

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
@@ -450,6 +450,23 @@ interface AuthenticatedUser {
450
450
  timeback?: UserTimebackData;
451
451
  }
452
452
 
453
+ /**
454
+ * TimeBack Enums & Literal Types
455
+ *
456
+ * Basic type definitions used throughout the TimeBack integration.
457
+ *
458
+ * @module types/timeback/types
459
+ */
460
+ /**
461
+ * Valid TimeBack subject values for course configuration.
462
+ * These are the supported subject values for OneRoster courses.
463
+ */
464
+ type TimebackSubject = 'Reading' | 'Language' | 'Vocabulary' | 'Social Studies' | 'Writing' | 'Science' | 'FastMath' | 'Math' | 'None';
465
+ /**
466
+ * Grade levels per AE OneRoster GradeEnum.
467
+ * -1 = Pre-K, 0 = Kindergarten, 1-12 = Grades 1-12, 13 = AP
468
+ */
469
+ type TimebackGrade = -1 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13;
453
470
  /**
454
471
  * Valid Caliper subject values.
455
472
  * Matches OneRoster subjects, with "None" as a Caliper-specific fallback.
@@ -618,7 +635,7 @@ declare const items: drizzle_orm_pg_core.PgTableWithColumns<{
618
635
  tableName: "items";
619
636
  dataType: "string";
620
637
  columnType: "PgEnumColumn";
621
- data: "accessory" | "badge" | "collectible" | "consumable" | "currency" | "other" | "trophy" | "unlock" | "upgrade";
638
+ data: "currency" | "badge" | "trophy" | "collectible" | "consumable" | "unlock" | "upgrade" | "accessory" | "other";
622
639
  driverParam: string;
623
640
  notNull: true;
624
641
  hasDefault: true;
@@ -800,71 +817,6 @@ type InventoryItemWithItem = InventoryItemRow & {
800
817
  item: ItemRow;
801
818
  };
802
819
 
803
- /**
804
- * Auto-initializes a PlaycademyClient with context from the environment.
805
- * Works in both iframe mode (production/development) and standalone mode (local dev).
806
- *
807
- * This is the recommended way to initialize the SDK as it automatically:
808
- * - Detects the runtime environment (iframe vs standalone)
809
- * - Configures the client with the appropriate context
810
- * - Sets up event listeners for token refresh
811
- * - Exposes the client for debugging in development mode
812
- *
813
- * @param options - Optional configuration overrides
814
- * @param options.baseUrl - Override the base URL for API requests
815
- * @returns Promise resolving to a fully initialized PlaycademyClient
816
- * @throws Error if not running in a browser context
817
- *
818
- * @example
819
- * ```typescript
820
- * // Default initialization
821
- * const client = await PlaycademyClient.init()
822
- *
823
- * // With custom base URL
824
- * const client = await PlaycademyClient.init({ baseUrl: 'https://custom.api.com' })
825
- * ```
826
- */
827
- declare function init<T extends PlaycademyClient = PlaycademyClient>(this: new (...args: ConstructorParameters<typeof PlaycademyClient>) => T, options?: {
828
- baseUrl?: string;
829
- allowedParentOrigins?: string[];
830
- onDisconnect?: DisconnectHandler;
831
- enableConnectionMonitoring?: boolean;
832
- }): Promise<T>;
833
-
834
- /**
835
- * Authenticates a user with email and password.
836
- *
837
- * This is a standalone authentication method that doesn't require an initialized client.
838
- * Use this for login flows before creating a client instance.
839
- *
840
- * @deprecated Use client.auth.login() instead for better error handling and automatic token management
841
- *
842
- * @param baseUrl - The base URL of the Playcademy API
843
- * @param email - User's email address
844
- * @param password - User's password
845
- * @returns Promise resolving to authentication response with token
846
- * @throws PlaycademyError if authentication fails or network error occurs
847
- *
848
- * @example
849
- * ```typescript
850
- * // Preferred approach:
851
- * const client = new PlaycademyClient({ baseUrl: '/api' })
852
- * const result = await client.auth.login({
853
- * email: 'user@example.com',
854
- * password: 'password'
855
- * })
856
- *
857
- * // Legacy approach (still works):
858
- * try {
859
- * const response = await PlaycademyClient.login('/api', 'user@example.com', 'password')
860
- * const client = new PlaycademyClient({ token: response.token })
861
- * } catch (error) {
862
- * console.error('Login failed:', error.message)
863
- * }
864
- * ```
865
- */
866
- declare function login(baseUrl: string, email: string, password: string): Promise<LoginResponse>;
867
-
868
820
  /**
869
821
  * @fileoverview Authentication Strategy Pattern
870
822
  *
@@ -999,6 +951,9 @@ declare abstract class PlaycademyBaseClient {
999
951
  * Initializes connection monitoring if enabled.
1000
952
  */
1001
953
  private _initializeConnectionMonitor;
954
+ /**
955
+ * Initializes an internal game session for automatic session management.
956
+ */
1002
957
  private _initializeInternalSession;
1003
958
  /**
1004
959
  * Current user data and inventory management.
@@ -1018,6 +973,71 @@ declare abstract class PlaycademyBaseClient {
1018
973
  };
1019
974
  }
1020
975
 
976
+ /**
977
+ * Auto-initializes a PlaycademyClient with context from the environment.
978
+ * Works in both iframe mode (production/development) and standalone mode (local dev).
979
+ *
980
+ * This is the recommended way to initialize the SDK as it automatically:
981
+ * - Detects the runtime environment (iframe vs standalone)
982
+ * - Configures the client with the appropriate context
983
+ * - Sets up event listeners for token refresh
984
+ * - Exposes the client for debugging in development mode
985
+ *
986
+ * @param options - Optional configuration overrides
987
+ * @param options.baseUrl - Override the base URL for API requests
988
+ * @returns Promise resolving to a fully initialized PlaycademyClient
989
+ * @throws Error if not running in a browser context
990
+ *
991
+ * @example
992
+ * ```typescript
993
+ * // Default initialization
994
+ * const client = await PlaycademyClient.init()
995
+ *
996
+ * // With custom base URL
997
+ * const client = await PlaycademyClient.init({ baseUrl: 'https://custom.api.com' })
998
+ * ```
999
+ */
1000
+ declare function init<T extends PlaycademyBaseClient = PlaycademyBaseClient>(this: new (config?: Partial<ClientConfig>) => T, options?: {
1001
+ baseUrl?: string;
1002
+ allowedParentOrigins?: string[];
1003
+ onDisconnect?: DisconnectHandler;
1004
+ enableConnectionMonitoring?: boolean;
1005
+ }): Promise<T>;
1006
+
1007
+ /**
1008
+ * Authenticates a user with email and password.
1009
+ *
1010
+ * This is a standalone authentication method that doesn't require an initialized client.
1011
+ * Use this for login flows before creating a client instance.
1012
+ *
1013
+ * @deprecated Use client.auth.login() instead for better error handling and automatic token management
1014
+ *
1015
+ * @param baseUrl - The base URL of the Playcademy API
1016
+ * @param email - User's email address
1017
+ * @param password - User's password
1018
+ * @returns Promise resolving to authentication response with token
1019
+ * @throws PlaycademyError if authentication fails or network error occurs
1020
+ *
1021
+ * @example
1022
+ * ```typescript
1023
+ * // Preferred approach:
1024
+ * const client = new PlaycademyClient({ baseUrl: '/api' })
1025
+ * const result = await client.auth.login({
1026
+ * email: 'user@example.com',
1027
+ * password: 'password'
1028
+ * })
1029
+ *
1030
+ * // Legacy approach (still works):
1031
+ * try {
1032
+ * const response = await PlaycademyClient.login('/api', 'user@example.com', 'password')
1033
+ * const client = new PlaycademyClient({ token: response.token })
1034
+ * } catch (error) {
1035
+ * console.error('Login failed:', error.message)
1036
+ * }
1037
+ * ```
1038
+ */
1039
+ declare function login(baseUrl: string, email: string, password: string): Promise<LoginResponse>;
1040
+
1021
1041
  /**
1022
1042
  * Playcademy SDK client for game developers.
1023
1043
  * Provides namespaced access to platform features for games running inside Cademy.
@@ -1042,8 +1062,8 @@ declare class PlaycademyClient extends PlaycademyBaseClient {
1042
1062
  */
1043
1063
  runtime: {
1044
1064
  getGameToken: (gameId: string, options?: {
1045
- apply?: boolean | undefined;
1046
- } | undefined) => Promise<GameTokenResponse>;
1065
+ apply?: boolean;
1066
+ }) => Promise<GameTokenResponse>;
1047
1067
  exit: () => Promise<void>;
1048
1068
  onInit: (handler: (context: GameContextPayload) => void) => void;
1049
1069
  onTokenRefresh: (handler: (data: {
@@ -1067,7 +1087,7 @@ declare class PlaycademyClient extends PlaycademyBaseClient {
1067
1087
  getListenerCounts: () => Record<string, number>;
1068
1088
  assets: {
1069
1089
  url(pathOrStrings: string | TemplateStringsArray, ...values: unknown[]): string;
1070
- fetch: (path: string, options?: RequestInit | undefined) => Promise<Response>;
1090
+ fetch: (path: string, options?: RequestInit) => Promise<Response>;
1071
1091
  json: <T = unknown>(path: string) => Promise<T>;
1072
1092
  blob: (path: string) => Promise<Blob>;
1073
1093
  text: (path: string) => Promise<string>;
@@ -1110,7 +1130,7 @@ declare class PlaycademyClient extends PlaycademyBaseClient {
1110
1130
  * - `submit(gameId, score, metadata?)` - Record a game score
1111
1131
  */
1112
1132
  scores: {
1113
- submit: (gameId: string, score: number, metadata?: Record<string, unknown> | undefined) => Promise<ScoreSubmission>;
1133
+ submit: (gameId: string, score: number, metadata?: Record<string, unknown>) => Promise<ScoreSubmission>;
1114
1134
  };
1115
1135
  /**
1116
1136
  * Realtime multiplayer authentication.
@@ -1127,13 +1147,13 @@ declare class PlaycademyClient extends PlaycademyBaseClient {
1127
1147
  * - Routes are relative to your game's deployment (e.g., '/hello' → your-game.playcademy.gg/api/hello)
1128
1148
  */
1129
1149
  backend: {
1130
- get<T = unknown>(path: string, headers?: Record<string, string> | undefined): Promise<T>;
1131
- post<T = unknown>(path: string, body?: unknown, headers?: Record<string, string> | undefined): Promise<T>;
1132
- put<T = unknown>(path: string, body?: unknown, headers?: Record<string, string> | undefined): Promise<T>;
1133
- patch<T = unknown>(path: string, body?: unknown, headers?: Record<string, string> | undefined): Promise<T>;
1134
- delete<T = unknown>(path: string, headers?: Record<string, string> | undefined): Promise<T>;
1135
- request<T = unknown>(path: string, method: Method, body?: unknown, headers?: Record<string, string> | undefined): Promise<T>;
1136
- download(path: string, method?: Method, body?: unknown, headers?: Record<string, string> | undefined): Promise<Response>;
1150
+ get<T = unknown>(path: string, headers?: Record<string, string>): Promise<T>;
1151
+ post<T = unknown>(path: string, body?: unknown, headers?: Record<string, string>): Promise<T>;
1152
+ put<T = unknown>(path: string, body?: unknown, headers?: Record<string, string>): Promise<T>;
1153
+ patch<T = unknown>(path: string, body?: unknown, headers?: Record<string, string>): Promise<T>;
1154
+ delete<T = unknown>(path: string, headers?: Record<string, string>): Promise<T>;
1155
+ request<T = unknown>(path: string, method: Method, body?: unknown, headers?: Record<string, string>): Promise<T>;
1156
+ download(path: string, method?: Method, body?: unknown, headers?: Record<string, string>): Promise<Response>;
1137
1157
  url(pathOrStrings: string | TemplateStringsArray, ...values: unknown[]): string;
1138
1158
  };
1139
1159
  /** Auto-initializes a PlaycademyClient with context from the environment */
@@ -1191,6 +1211,45 @@ interface TimebackUserContext {
1191
1211
  /** User's organizations (schools/districts) */
1192
1212
  organizations: TimebackOrganization[];
1193
1213
  }
1214
+ /**
1215
+ * XP data access for the current user.
1216
+ * Results are cached for 5 seconds to avoid redundant network requests.
1217
+ */
1218
+ interface TimebackUserXp {
1219
+ /**
1220
+ * Fetch XP data from the server.
1221
+ * Returns XP for all courses in this game, or filter by grade/subject.
1222
+ * Results are cached for 5 seconds (use `force: true` to bypass).
1223
+ *
1224
+ * @param options - Query options
1225
+ * @param options.grade - Grade level to filter (must be used with subject)
1226
+ * @param options.subject - Subject to filter (must be used with grade)
1227
+ * @param options.include - Additional data to include: 'perCourse', 'today'
1228
+ * @param options.force - Bypass cache and fetch fresh data (default: false)
1229
+ * @returns Promise resolving to XP data
1230
+ *
1231
+ * @example
1232
+ * ```typescript
1233
+ * // Get total XP for all game courses
1234
+ * const xp = await client.timeback.user.xp.fetch()
1235
+ *
1236
+ * // Get XP for a specific grade/subject
1237
+ * const xp = await client.timeback.user.xp.fetch({
1238
+ * grade: 3,
1239
+ * subject: 'Math'
1240
+ * })
1241
+ *
1242
+ * // Get XP with per-course breakdown
1243
+ * const xp = await client.timeback.user.xp.fetch({
1244
+ * include: ['perCourse', 'today']
1245
+ * })
1246
+ *
1247
+ * // Force fresh data
1248
+ * const xp = await client.timeback.user.xp.fetch({ force: true })
1249
+ * ```
1250
+ */
1251
+ fetch(options?: GetXpOptions): Promise<XpResponse>;
1252
+ }
1194
1253
  /**
1195
1254
  * TimeBack user object with both cached getters and fetch method.
1196
1255
  */
@@ -1204,6 +1263,42 @@ interface TimebackUser extends TimebackUserContext {
1204
1263
  fetch(options?: {
1205
1264
  force?: boolean;
1206
1265
  }): Promise<TimebackUserContext>;
1266
+ /**
1267
+ * XP data for the current user.
1268
+ * Call `xp.fetch()` to get XP from the server.
1269
+ */
1270
+ xp: TimebackUserXp;
1271
+ }
1272
+ /**
1273
+ * Options for querying student XP.
1274
+ */
1275
+ interface GetXpOptions {
1276
+ /** Grade level to filter (must be used with subject) */
1277
+ grade?: TimebackGrade;
1278
+ /** Subject to filter (must be used with grade) */
1279
+ subject?: TimebackSubject;
1280
+ /** Additional data to include: 'perCourse', 'today' */
1281
+ include?: ('perCourse' | 'today')[];
1282
+ /** Bypass cache and fetch fresh data (default: false) */
1283
+ force?: boolean;
1284
+ }
1285
+ /**
1286
+ * XP data for a single course.
1287
+ */
1288
+ interface CourseXp {
1289
+ grade: TimebackGrade;
1290
+ subject: TimebackSubject;
1291
+ title: string;
1292
+ totalXp: number;
1293
+ todayXp?: number;
1294
+ }
1295
+ /**
1296
+ * Response from XP query.
1297
+ */
1298
+ interface XpResponse {
1299
+ totalXp: number;
1300
+ todayXp?: number;
1301
+ courses?: CourseXp[];
1207
1302
  }
1208
1303
 
1209
1304
  /**
package/dist/index.js CHANGED
@@ -1185,7 +1185,7 @@ var ACHIEVEMENT_DEFINITIONS = [
1185
1185
  }
1186
1186
  ];
1187
1187
  // ../constants/src/typescript.ts
1188
- var TSC_PACKAGE = "@typescript/native-preview";
1188
+ var TSC_PACKAGE = "typescript";
1189
1189
  var USE_NATIVE_TSC = TSC_PACKAGE.includes("native-preview");
1190
1190
  // ../constants/src/overworld.ts
1191
1191
  var ITEM_SLUGS = {
@@ -1212,7 +1212,8 @@ var BADGES = {
1212
1212
  };
1213
1213
  // ../constants/src/timeback.ts
1214
1214
  var TIMEBACK_ROUTES = {
1215
- END_ACTIVITY: "/integrations/timeback/end-activity"
1215
+ END_ACTIVITY: "/integrations/timeback/end-activity",
1216
+ GET_XP: "/integrations/timeback/xp"
1216
1217
  };
1217
1218
  // src/core/cache/singleton-cache.ts
1218
1219
  function createSingletonCache() {
@@ -1390,6 +1391,26 @@ function createTTLCache(options) {
1390
1391
  return { get, clear, size, prune, getKeys, has };
1391
1392
  }
1392
1393
 
1394
+ // src/core/guards.ts
1395
+ var VALID_GRADES = [-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13];
1396
+ var VALID_SUBJECTS = [
1397
+ "Reading",
1398
+ "Language",
1399
+ "Vocabulary",
1400
+ "Social Studies",
1401
+ "Writing",
1402
+ "Science",
1403
+ "FastMath",
1404
+ "Math",
1405
+ "None"
1406
+ ];
1407
+ function isValidGrade(value) {
1408
+ return typeof value === "number" && Number.isInteger(value) && VALID_GRADES.includes(value);
1409
+ }
1410
+ function isValidSubject(value) {
1411
+ return typeof value === "string" && VALID_SUBJECTS.includes(value);
1412
+ }
1413
+
1393
1414
  // src/namespaces/game/timeback.ts
1394
1415
  function createTimebackNamespace(client) {
1395
1416
  let currentActivity = null;
@@ -1397,6 +1418,10 @@ function createTimebackNamespace(client) {
1397
1418
  ttl: 5 * 60 * 1000,
1398
1419
  keyPrefix: "game.timeback.user"
1399
1420
  });
1421
+ const xpCache = createTTLCache({
1422
+ ttl: 5000,
1423
+ keyPrefix: "game.timeback.xp"
1424
+ });
1400
1425
  const getTimeback = () => client["initPayload"]?.timeback;
1401
1426
  return {
1402
1427
  get user() {
@@ -1427,6 +1452,49 @@ function createTimebackNamespace(client) {
1427
1452
  organizations: response.organizations
1428
1453
  };
1429
1454
  }, options);
1455
+ },
1456
+ xp: {
1457
+ fetch: async (options) => {
1458
+ const hasGrade = options?.grade !== undefined;
1459
+ const hasSubject = options?.subject !== undefined;
1460
+ if (hasGrade !== hasSubject) {
1461
+ throw new Error("Both grade and subject must be provided together");
1462
+ }
1463
+ if (hasGrade && !isValidGrade(options.grade)) {
1464
+ throw new Error(`Invalid grade: ${options.grade}. Valid grades: ${VALID_GRADES.join(", ")}`);
1465
+ }
1466
+ if (hasSubject && !isValidSubject(options.subject)) {
1467
+ throw new Error(`Invalid subject: ${options.subject}. Valid subjects: ${VALID_SUBJECTS.join(", ")}`);
1468
+ }
1469
+ const validIncludeOptions = ["perCourse", "today"];
1470
+ if (options?.include?.length) {
1471
+ for (const opt of options.include) {
1472
+ if (!validIncludeOptions.includes(opt)) {
1473
+ throw new Error(`Invalid include option: ${opt}. Valid options: ${validIncludeOptions.join(", ")}`);
1474
+ }
1475
+ }
1476
+ }
1477
+ const cacheKey = [
1478
+ options?.grade ?? "",
1479
+ options?.subject ?? "",
1480
+ options?.include?.toSorted().join(",") ?? ""
1481
+ ].join(":");
1482
+ return xpCache.get(cacheKey, async () => {
1483
+ const params = new URLSearchParams;
1484
+ if (hasGrade) {
1485
+ params.set("grade", String(options.grade));
1486
+ }
1487
+ if (hasSubject) {
1488
+ params.set("subject", options.subject);
1489
+ }
1490
+ if (options?.include?.length) {
1491
+ params.set("include", options.include.join(","));
1492
+ }
1493
+ const queryString = params.toString();
1494
+ const endpoint = `${TIMEBACK_ROUTES.GET_XP}${queryString ? `?${queryString}` : ""}`;
1495
+ return client["requestGameBackend"](endpoint, "GET");
1496
+ }, { force: options?.force });
1497
+ }
1430
1498
  }
1431
1499
  };
1432
1500
  },