@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 +172 -77
- package/dist/index.js +70 -2
- package/dist/internal.d.ts +417 -787
- package/dist/internal.js +70 -2
- package/dist/server.d.ts +36 -0
- package/dist/server.js +28 -2
- package/dist/types.d.ts +203 -122
- package/package.json +3 -3
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: "
|
|
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
|
|
1046
|
-
}
|
|
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
|
|
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>
|
|
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>
|
|
1131
|
-
post<T = unknown>(path: string, body?: unknown, headers?: Record<string, string>
|
|
1132
|
-
put<T = unknown>(path: string, body?: unknown, headers?: Record<string, string>
|
|
1133
|
-
patch<T = unknown>(path: string, body?: unknown, headers?: Record<string, string>
|
|
1134
|
-
delete<T = unknown>(path: string, headers?: Record<string, string>
|
|
1135
|
-
request<T = unknown>(path: string, method: Method, body?: unknown, headers?: Record<string, string>
|
|
1136
|
-
download(path: string, method?: Method, body?: unknown, headers?: Record<string, string>
|
|
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 = "
|
|
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
|
},
|