@playcademy/sdk 0.2.0 → 0.2.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
@@ -1,4 +1,3 @@
1
- import * as _playcademy_realtime_server_types from '@playcademy/realtime/server/types';
2
1
  import * as drizzle_orm_pg_core from 'drizzle-orm/pg-core';
3
2
  import * as _playcademy_timeback_types from '@playcademy/timeback/types';
4
3
  import { AUTH_PROVIDER_IDS } from '@playcademy/constants';
@@ -736,177 +735,70 @@ declare const inventoryItems: drizzle_orm_pg_core.PgTableWithColumns<{
736
735
  };
737
736
  dialect: "pg";
738
737
  }>;
739
- declare const gameTimebackIntegrations: drizzle_orm_pg_core.PgTableWithColumns<{
740
- name: "game_timeback_integrations";
741
- schema: undefined;
742
- columns: {
743
- id: drizzle_orm_pg_core.PgColumn<{
744
- name: "id";
745
- tableName: "game_timeback_integrations";
746
- dataType: "string";
747
- columnType: "PgUUID";
748
- data: string;
749
- driverParam: string;
750
- notNull: true;
751
- hasDefault: true;
752
- isPrimaryKey: true;
753
- isAutoincrement: false;
754
- hasRuntimeDefault: false;
755
- enumValues: undefined;
756
- baseColumn: never;
757
- identity: undefined;
758
- generated: undefined;
759
- }, {}, {}>;
760
- gameId: drizzle_orm_pg_core.PgColumn<{
761
- name: "game_id";
762
- tableName: "game_timeback_integrations";
763
- dataType: "string";
764
- columnType: "PgUUID";
765
- data: string;
766
- driverParam: string;
767
- notNull: true;
768
- hasDefault: false;
769
- isPrimaryKey: false;
770
- isAutoincrement: false;
771
- hasRuntimeDefault: false;
772
- enumValues: undefined;
773
- baseColumn: never;
774
- identity: undefined;
775
- generated: undefined;
776
- }, {}, {}>;
777
- courseId: drizzle_orm_pg_core.PgColumn<{
778
- name: "course_id";
779
- tableName: "game_timeback_integrations";
780
- dataType: "string";
781
- columnType: "PgText";
782
- data: string;
783
- driverParam: string;
784
- notNull: true;
785
- hasDefault: false;
786
- isPrimaryKey: false;
787
- isAutoincrement: false;
788
- hasRuntimeDefault: false;
789
- enumValues: [string, ...string[]];
790
- baseColumn: never;
791
- identity: undefined;
792
- generated: undefined;
793
- }, {}, {}>;
794
- grade: drizzle_orm_pg_core.PgColumn<{
795
- name: "grade";
796
- tableName: "game_timeback_integrations";
797
- dataType: "number";
798
- columnType: "PgInteger";
799
- data: number;
800
- driverParam: string | number;
801
- notNull: true;
802
- hasDefault: false;
803
- isPrimaryKey: false;
804
- isAutoincrement: false;
805
- hasRuntimeDefault: false;
806
- enumValues: undefined;
807
- baseColumn: never;
808
- identity: undefined;
809
- generated: undefined;
810
- }, {}, {}>;
811
- subject: drizzle_orm_pg_core.PgColumn<{
812
- name: "subject";
813
- tableName: "game_timeback_integrations";
814
- dataType: "string";
815
- columnType: "PgText";
816
- data: string;
817
- driverParam: string;
818
- notNull: true;
819
- hasDefault: false;
820
- isPrimaryKey: false;
821
- isAutoincrement: false;
822
- hasRuntimeDefault: false;
823
- enumValues: [string, ...string[]];
824
- baseColumn: never;
825
- identity: undefined;
826
- generated: undefined;
827
- }, {}, {}>;
828
- totalXp: drizzle_orm_pg_core.PgColumn<{
829
- name: "total_xp";
830
- tableName: "game_timeback_integrations";
831
- dataType: "number";
832
- columnType: "PgInteger";
833
- data: number;
834
- driverParam: string | number;
835
- notNull: false;
836
- hasDefault: false;
837
- isPrimaryKey: false;
838
- isAutoincrement: false;
839
- hasRuntimeDefault: false;
840
- enumValues: undefined;
841
- baseColumn: never;
842
- identity: undefined;
843
- generated: undefined;
844
- }, {}, {}>;
845
- lastVerifiedAt: drizzle_orm_pg_core.PgColumn<{
846
- name: "last_verified_at";
847
- tableName: "game_timeback_integrations";
848
- dataType: "date";
849
- columnType: "PgTimestamp";
850
- data: Date;
851
- driverParam: string;
852
- notNull: false;
853
- hasDefault: false;
854
- isPrimaryKey: false;
855
- isAutoincrement: false;
856
- hasRuntimeDefault: false;
857
- enumValues: undefined;
858
- baseColumn: never;
859
- identity: undefined;
860
- generated: undefined;
861
- }, {}, {}>;
862
- createdAt: drizzle_orm_pg_core.PgColumn<{
863
- name: "created_at";
864
- tableName: "game_timeback_integrations";
865
- dataType: "date";
866
- columnType: "PgTimestamp";
867
- data: Date;
868
- driverParam: string;
869
- notNull: true;
870
- hasDefault: true;
871
- isPrimaryKey: false;
872
- isAutoincrement: false;
873
- hasRuntimeDefault: false;
874
- enumValues: undefined;
875
- baseColumn: never;
876
- identity: undefined;
877
- generated: undefined;
878
- }, {}, {}>;
879
- updatedAt: drizzle_orm_pg_core.PgColumn<{
880
- name: "updated_at";
881
- tableName: "game_timeback_integrations";
882
- dataType: "date";
883
- columnType: "PgTimestamp";
884
- data: Date;
885
- driverParam: string;
886
- notNull: true;
887
- hasDefault: true;
888
- isPrimaryKey: false;
889
- isAutoincrement: false;
890
- hasRuntimeDefault: false;
891
- enumValues: undefined;
892
- baseColumn: never;
893
- identity: undefined;
894
- generated: undefined;
895
- }, {}, {}>;
896
- };
897
- dialect: "pg";
898
- }>;
899
738
  type Item = typeof items.$inferSelect;
900
739
  type InventoryItem = typeof inventoryItems.$inferSelect;
901
740
 
902
741
  type User = typeof users.$inferSelect;
742
+ /**
743
+ * TimeBack enrollment information for a game.
744
+ */
745
+ type UserEnrollment = {
746
+ gameId?: string;
747
+ courseId: string;
748
+ grade: number;
749
+ subject: string;
750
+ orgId?: string;
751
+ };
752
+ /**
753
+ * TimeBack user role (matches OneRoster spec).
754
+ */
755
+ type TimebackUserRole = 'administrator' | 'aide' | 'guardian' | 'parent' | 'proctor' | 'relative' | 'student' | 'teacher';
756
+ /**
757
+ * Organization type (matches OneRoster spec).
758
+ */
759
+ type TimebackOrgType = 'department' | 'school' | 'district' | 'local' | 'state' | 'national';
760
+ /**
761
+ * TimeBack organization data for a user.
762
+ * Represents schools, districts, or other educational organizations.
763
+ */
764
+ type UserOrganization = {
765
+ /** Organization ID (OneRoster sourcedId) */
766
+ id: string;
767
+ /** Organization name */
768
+ name: string | null;
769
+ /** Organization type (school, district, etc.) */
770
+ type: TimebackOrgType | string;
771
+ /** Whether this is the user's primary organization */
772
+ isPrimary: boolean;
773
+ };
774
+ /**
775
+ * TimeBack student profile (role + organizations).
776
+ * Subset of UserTimebackData returned by OneRoster API.
777
+ */
778
+ type TimebackStudentProfile = {
779
+ /** User's primary role in TimeBack (student, parent, teacher, etc.) */
780
+ role: TimebackUserRole;
781
+ /** User's organizations (schools/districts) */
782
+ organizations: UserOrganization[];
783
+ };
784
+ /**
785
+ * TimeBack-related data for a user.
786
+ */
787
+ type UserTimebackData = TimebackStudentProfile & {
788
+ /** User's TimeBack ID (sourcedId) */
789
+ id: string;
790
+ /** Course enrollments */
791
+ enrollments: UserEnrollment[];
792
+ };
903
793
  /**
904
794
  * User data with authentication provider information.
905
795
  * Returned by the /users/me endpoint with additional auth context.
906
796
  */
907
- type AuthenticatedUser = User & {
797
+ type AuthenticatedUser = Omit<User, 'timebackId'> & {
908
798
  /** Whether the user authenticated via Timeback SSO */
909
799
  hasTimebackAccount: boolean;
800
+ /** TimeBack data (id, role, enrollments, organizations) - only present if user has a timeback account */
801
+ timeback?: UserTimebackData;
910
802
  };
911
803
  /**
912
804
  * Basic user information in the shape of the claims from identity providers
@@ -932,177 +824,6 @@ interface UserInfo {
932
824
  type InventoryItemWithItem = Omit<InventoryItem, 'itemId'> & {
933
825
  item: Item;
934
826
  };
935
- type GameTimebackIntegration = typeof gameTimebackIntegrations.$inferSelect;
936
- type TodayXpResponse = {
937
- xp: number;
938
- date: string;
939
- };
940
- type TotalXpResponse = {
941
- totalXp: number;
942
- };
943
- type XpHistoryResponse = {
944
- history: Array<{
945
- date: string;
946
- xp: number;
947
- }>;
948
- };
949
- type TimebackSetupRequest = {
950
- gameId: string;
951
- config: {
952
- organization: {
953
- name: string;
954
- type: string;
955
- identifier: string;
956
- };
957
- course: {
958
- title: string;
959
- subjects: string[];
960
- grades: number[];
961
- courseCode: string;
962
- level: string;
963
- gradingScheme: string;
964
- metadata?: Record<string, unknown>;
965
- };
966
- component: {
967
- title: string;
968
- sortOrder: number;
969
- prerequisites: string[];
970
- prerequisiteCriteria: string;
971
- };
972
- resource: {
973
- title: string;
974
- vendorResourceId: string;
975
- vendorId: string;
976
- applicationId: string;
977
- roles: string[];
978
- importance: string;
979
- metadata: {
980
- type?: string;
981
- launchUrl?: string;
982
- toolProvider?: string;
983
- instructionalMethod?: string;
984
- subject?: string;
985
- grades?: number[];
986
- language?: string;
987
- xp?: number;
988
- [key: string]: unknown;
989
- };
990
- };
991
- componentResource: {
992
- title: string;
993
- sortOrder: number;
994
- lessonType: string | null;
995
- };
996
- };
997
- verbose?: boolean;
998
- };
999
- /**
1000
- * Minimal course configuration for TimeBack integration (used in user-facing config).
1001
- *
1002
- * NOTE: Per-course overrides (title, courseCode, level, metadata) are defined
1003
- * in @playcademy/sdk/server as TimebackCourseConfigWithOverrides.
1004
- * This base type only includes the minimal required fields.
1005
- *
1006
- * For totalXp, use metadata.metrics.totalXp (aligns with upstream TimeBack structure).
1007
- */
1008
- type TimebackCourseConfig = {
1009
- subject: string;
1010
- grade: number;
1011
- };
1012
- /**
1013
- * Derived course configuration after merging base + per-course overrides + templating.
1014
- * This is what the CLI sends to the Platform API in PlatformTimebackSetupRequest.
1015
- */
1016
- type DerivedPlatformCourseConfig = TimebackCourseConfig & {
1017
- title: string;
1018
- courseCode: string;
1019
- level: string;
1020
- metadata?: Record<string, unknown>;
1021
- totalXp?: number | null;
1022
- masterableUnits?: number | null;
1023
- };
1024
- type PlatformTimebackSetupRequest = {
1025
- gameId: string;
1026
- courses: DerivedPlatformCourseConfig[];
1027
- baseConfig: {
1028
- organization: {
1029
- name: string;
1030
- type: string;
1031
- identifier: string;
1032
- };
1033
- component: {
1034
- title: string;
1035
- titleSuffix?: string;
1036
- sortOrder: number;
1037
- prerequisites: string[];
1038
- prerequisiteCriteria: string;
1039
- };
1040
- resource: {
1041
- title: string;
1042
- titleSuffix?: string;
1043
- vendorResourceId: string;
1044
- vendorId: string;
1045
- applicationId: string;
1046
- roles: string[];
1047
- importance: string;
1048
- metadata: {
1049
- type?: string;
1050
- launchUrl?: string;
1051
- toolProvider?: string;
1052
- instructionalMethod?: string;
1053
- language?: string;
1054
- [key: string]: unknown;
1055
- };
1056
- };
1057
- componentResource: {
1058
- title: string;
1059
- titleSuffix?: string;
1060
- sortOrder: number;
1061
- lessonType: string | null;
1062
- };
1063
- };
1064
- verbose?: boolean;
1065
- };
1066
- type PlatformTimebackSetupResponse = {
1067
- integrations: GameTimebackIntegration[];
1068
- verbose?: Array<{
1069
- integration: GameTimebackIntegration;
1070
- config: {
1071
- course: unknown;
1072
- component: unknown;
1073
- resource: unknown;
1074
- componentResource: unknown;
1075
- };
1076
- }>;
1077
- };
1078
- type TimebackVerificationResources = {
1079
- course: {
1080
- found: boolean;
1081
- data?: unknown;
1082
- };
1083
- component: {
1084
- found: boolean;
1085
- data?: unknown;
1086
- };
1087
- resource: {
1088
- found: boolean;
1089
- data?: unknown;
1090
- };
1091
- componentResource: {
1092
- found: boolean;
1093
- data?: unknown;
1094
- };
1095
- };
1096
- type TimebackVerifyCourseResult = {
1097
- integration: GameTimebackIntegration;
1098
- resources: TimebackVerificationResources;
1099
- status: 'success' | 'error';
1100
- errors?: string[];
1101
- };
1102
- type TimebackVerifyAllResponse = {
1103
- status: 'success' | 'error';
1104
- results: TimebackVerifyCourseResult[];
1105
- };
1106
827
  type EndActivityResponse = {
1107
828
  status: 'ok';
1108
829
  courseId: string;
@@ -1113,13 +834,8 @@ type EndActivityResponse = {
1113
834
  inProgress?: string;
1114
835
  };
1115
836
 
1116
- /**
1117
- * Combined response type for summary method
1118
- */
1119
- type XpSummaryResponse = {
1120
- today: TodayXpResponse;
1121
- total: TotalXpResponse;
1122
- };
837
+ /** Permitted HTTP verbs */
838
+ type Method = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
1123
839
 
1124
840
  /**
1125
841
  * Auto-initializes a PlaycademyClient with context from the environment.
@@ -1186,208 +902,103 @@ declare function init<T extends PlaycademyClient = PlaycademyClient>(this: new (
1186
902
  */
1187
903
  declare function login(baseUrl: string, email: string, password: string): Promise<LoginResponse>;
1188
904
 
1189
- /** Permitted HTTP verbs */
1190
- type Method = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
905
+ /**
906
+ * @fileoverview Authentication Strategy Pattern
907
+ *
908
+ * Provides different authentication strategies for the Playcademy SDK.
909
+ * Each strategy knows how to add its authentication headers to requests.
910
+ */
911
+
912
+ /**
913
+ * Base interface for authentication strategies
914
+ */
915
+ interface AuthStrategy {
916
+ /** Get the token value */
917
+ getToken(): string | null;
918
+ /** Get the token type */
919
+ getType(): TokenType;
920
+ /** Get authentication headers for a request */
921
+ getHeaders(): Record<string, string>;
922
+ }
1191
923
 
1192
924
  /**
1193
- * Main Playcademy SDK client for interacting with the platform API.
1194
- * Provides namespaced access to all platform features including games, users, inventory, and more.
925
+ * Base Playcademy SDK client with shared infrastructure.
926
+ * Provides authentication, HTTP requests, events, connection monitoring,
927
+ * and fundamental namespaces used by all clients.
928
+ *
929
+ * Extended by PlaycademyClient (game SDK) and PlaycademyInternalClient (platform SDK).
1195
930
  */
1196
- declare class PlaycademyClient {
931
+ declare abstract class PlaycademyBaseClient {
1197
932
  baseUrl: string;
1198
933
  gameUrl?: string;
1199
- private authStrategy;
1200
- private gameId?;
1201
- private config;
1202
- private listeners;
1203
- private internalClientSessionId?;
1204
- private authContext?;
1205
- private initPayload?;
1206
- private connectionManager?;
934
+ protected authStrategy: AuthStrategy;
935
+ protected gameId?: string;
936
+ protected config: Partial<ClientConfig>;
937
+ protected listeners: EventListeners;
938
+ protected internalClientSessionId?: string;
939
+ protected authContext?: {
940
+ isInIframe: boolean;
941
+ };
942
+ protected initPayload?: InitPayload;
943
+ protected connectionManager?: ConnectionManager;
1207
944
  /**
1208
945
  * Internal session manager for automatic session lifecycle.
1209
- *
1210
- * This manager handles starting and ending game sessions automatically.
1211
- * Game developers don't need to call these methods directly - they're managed
1212
- * by the SDK during initialization and cleanup.
1213
- *
1214
946
  * @private
1215
947
  * @internal
1216
948
  */
1217
- private _sessionManager;
1218
- /**
1219
- * Creates a new PlaycademyClient instance.
1220
- *
1221
- * @param config - Optional configuration object
1222
- * @param config.baseUrl - Base URL (e.g., 'https://hub.playcademy.net' or '/'). SDK automatically appends /api
1223
- * @param config.token - Authentication token
1224
- * @param config.tokenType - Optional token type (auto-detected if not provided)
1225
- * @param config.gameId - Game ID for automatic session management
1226
- * @param config.autoStartSession - Automatically start a game session?
1227
- */
949
+ protected _sessionManager: {
950
+ startSession: (gameId: string) => Promise<{
951
+ sessionId: string;
952
+ }>;
953
+ endSession: (sessionId: string, gameId: string) => Promise<void>;
954
+ };
1228
955
  constructor(config?: Partial<ClientConfig>);
1229
956
  /**
1230
957
  * Gets the effective base URL for API requests.
1231
- * Converts relative URLs to absolute URLs in browser environments.
1232
- * Note: baseUrl already includes /api suffix from constructor.
1233
- *
1234
- * @returns The complete base URL for API requests (with /api suffix)
1235
958
  */
1236
959
  getBaseUrl(): string;
1237
960
  /**
1238
961
  * Gets the effective game backend URL for integration requests.
1239
- * Throws if gameUrl is not configured.
1240
- *
1241
- * @returns The complete game backend URL for API requests (with /api suffix)
1242
- * @throws PlaycademyError if gameUrl is not set
1243
962
  */
1244
- private getGameBackendUrl;
963
+ protected getGameBackendUrl(): string;
1245
964
  /**
1246
965
  * Simple ping method for testing connectivity.
1247
- *
1248
- * @returns 'pong' string response
1249
966
  */
1250
967
  ping(): string;
1251
968
  /**
1252
969
  * Sets the authentication token for API requests.
1253
- * Emits an 'authChange' event when the token changes.
1254
- *
1255
- * @param token - The authentication token, or null to clear
1256
- * @param tokenType - Optional token type (auto-detected if not provided)
1257
970
  */
1258
971
  setToken(token: string | null, tokenType?: TokenType): void;
1259
972
  /**
1260
973
  * Gets the current token type.
1261
- *
1262
- * @returns The token type
1263
974
  */
1264
975
  getTokenType(): TokenType;
1265
976
  /**
1266
977
  * Gets the current authentication token.
1267
- *
1268
- * @returns The current token or null if not authenticated
1269
- *
1270
- * @example
1271
- * ```typescript
1272
- * // Send token to your backend for verification
1273
- * const token = client.getToken()
1274
- * const response = await fetch('/api/auth/playcademy', {
1275
- * method: 'POST',
1276
- * body: JSON.stringify({ gameToken: token })
1277
- * })
1278
- * ```
1279
978
  */
1280
979
  getToken(): string | null;
1281
980
  /**
1282
- * Checks if the client has a valid API token for making Playcademy API requests.
1283
- *
1284
- * For games (iframe context): Checks if we have a valid token from the parent.
1285
- * For Cademy (standalone): Checks if we have a token from better-auth.
1286
- *
1287
- * Note: This checks for API authentication, not whether a user has linked
1288
- * their identity via OAuth.
1289
- *
1290
- * @returns true if API token exists, false otherwise
1291
- *
1292
- * @example
1293
- * ```typescript
1294
- * if (client.isAuthenticated()) {
1295
- * // Can make API calls
1296
- * const games = await client.games.list()
1297
- * } else {
1298
- * console.error('No API token available')
1299
- * }
1300
- * ```
981
+ * Checks if the client has a valid API token.
1301
982
  */
1302
983
  isAuthenticated(): boolean;
1303
984
  /**
1304
985
  * Registers a callback to be called when authentication state changes.
1305
- *
1306
- * @param callback - Function to call when auth state changes
1307
986
  */
1308
987
  onAuthChange(callback: (token: string | null) => void): void;
1309
988
  /**
1310
989
  * Registers a callback to be called when connection issues are detected.
1311
- *
1312
- * This is a convenience method that filters connection change events to only
1313
- * fire when the connection degrades (offline or degraded states). Use this
1314
- * when you want to handle disconnects without being notified of recoveries.
1315
- *
1316
- * For all connection state changes, use `client.on('connectionChange', ...)` instead.
1317
- *
1318
- * @param callback - Function to call when connection state changes to offline or degraded
1319
- * @returns Cleanup function to unregister the callback
1320
- *
1321
- * @example
1322
- * ```typescript
1323
- * const cleanup = client.onDisconnect(({ state, reason, displayAlert }) => {
1324
- * console.log(`Connection ${state}: ${reason}`)
1325
- *
1326
- * if (state === 'offline') {
1327
- * // Save state and return to game lobby
1328
- * displayAlert?.('Connection lost. Your progress has been saved.', { type: 'error' })
1329
- * saveGameState()
1330
- * returnToLobby()
1331
- * } else if (state === 'degraded') {
1332
- * displayAlert?.('Slow connection detected.', { type: 'warning' })
1333
- * }
1334
- * })
1335
- *
1336
- * // Later: cleanup() to unregister
1337
- * ```
1338
- *
1339
- * @see Connection monitoring documentation in SDK Browser docs for detailed usage examples
1340
- * @see {@link ConnectionManager.onDisconnect} for the underlying implementation
1341
990
  */
1342
991
  onDisconnect(callback: (context: DisconnectContext) => void | Promise<void>): () => void;
1343
992
  /**
1344
993
  * Gets the current connection state.
1345
- *
1346
- * Returns the last known connection state without triggering a new check.
1347
- * Use `checkConnection()` to force an immediate verification.
1348
- *
1349
- * @returns Current connection state ('online', 'offline', 'degraded') or 'unknown' if monitoring is disabled
1350
- *
1351
- * @example
1352
- * ```typescript
1353
- * const state = client.getConnectionState()
1354
- * if (state === 'offline') {
1355
- * console.log('No connection available')
1356
- * }
1357
- * ```
1358
- *
1359
- * @see {@link checkConnection} to trigger an immediate connection check
1360
- * @see {@link ConnectionManager.getState} for the underlying implementation
1361
994
  */
1362
995
  getConnectionState(): ConnectionState | 'unknown';
1363
996
  /**
1364
997
  * Manually triggers a connection check immediately.
1365
- *
1366
- * Forces a heartbeat ping to verify connectivity right now, bypassing the normal
1367
- * interval. Useful when you need to verify connection status before a critical
1368
- * operation (e.g., saving important game state).
1369
- *
1370
- * @returns Promise resolving to the current connection state after verification
1371
- *
1372
- * @example
1373
- * ```typescript
1374
- * // Check before critical operation
1375
- * const state = await client.checkConnection()
1376
- * if (state !== 'online') {
1377
- * alert('Please check your internet connection before saving')
1378
- * return
1379
- * }
1380
- *
1381
- * await client.games.saveState(importantData)
1382
- * ```
1383
- *
1384
- * @see {@link getConnectionState} to get the last known state without checking
1385
- * @see {@link ConnectionManager.checkNow} for the underlying implementation
1386
998
  */
1387
999
  checkConnection(): Promise<ConnectionState | 'unknown'>;
1388
1000
  /**
1389
1001
  * Sets the authentication context for the client.
1390
- * This is called during initialization to store environment info.
1391
1002
  * @internal
1392
1003
  */
1393
1004
  _setAuthContext(context: {
@@ -1395,28 +1006,14 @@ declare class PlaycademyClient {
1395
1006
  }): void;
1396
1007
  /**
1397
1008
  * Registers an event listener for client events.
1398
- *
1399
- * @param event - The event type to listen for
1400
- * @param callback - Function to call when the event is emitted
1401
1009
  */
1402
1010
  on<E extends keyof ClientEvents>(event: E, callback: (payload: ClientEvents[E]) => void): void;
1403
1011
  /**
1404
1012
  * Emits an event to all registered listeners.
1405
- *
1406
- * @param event - The event type to emit
1407
- * @param payload - The event payload
1408
1013
  */
1409
- private emit;
1014
+ protected emit<E extends keyof ClientEvents>(event: E, payload: ClientEvents[E]): void;
1410
1015
  /**
1411
1016
  * Makes an authenticated HTTP request to the platform API.
1412
- *
1413
- * @param path - API endpoint path
1414
- * @param method - HTTP method
1415
- * @param options - Optional request configuration
1416
- * @param options.body - Request body
1417
- * @param options.headers - Additional headers
1418
- * @param options.raw - If true, returns raw Response instead of parsing
1419
- * @returns Promise resolving to the response data or raw Response
1420
1017
  */
1421
1018
  protected request<T>(path: string, method: Method, options?: {
1422
1019
  body?: unknown;
@@ -1425,49 +1022,64 @@ declare class PlaycademyClient {
1425
1022
  }): Promise<T>;
1426
1023
  /**
1427
1024
  * Makes an authenticated HTTP request to the game's backend Worker.
1428
- * Uses gameUrl if set, otherwise falls back to platform API.
1429
- *
1430
- * @param path - API endpoint path
1431
- * @param method - HTTP method
1432
- * @param body - Request body (optional)
1433
- * @param headers - Additional headers (optional)
1434
- * @param raw - If true, returns raw Response instead of parsing (optional)
1435
- * @returns Promise resolving to the response data or raw Response
1436
1025
  */
1437
1026
  protected requestGameBackend<T>(path: string, method: Method, body?: unknown, headers?: Record<string, string>, raw?: boolean): Promise<T>;
1438
1027
  /**
1439
1028
  * Ensures a gameId is available, throwing an error if not.
1440
- *
1441
- * @returns The gameId
1442
- * @throws PlaycademyError if no gameId is configured
1443
1029
  */
1444
- private _ensureGameId;
1030
+ protected _ensureGameId(): string;
1445
1031
  /**
1446
1032
  * Detects and sets the authentication context (iframe vs standalone).
1447
- * Safe to call in any environment - isInIframe handles browser detection.
1448
1033
  */
1449
1034
  private _detectAuthContext;
1450
1035
  /**
1451
1036
  * Initializes connection monitoring if enabled.
1452
- * Safe to call in any environment - only runs in browser.
1453
1037
  */
1454
1038
  private _initializeConnectionMonitor;
1455
1039
  /**
1456
1040
  * Initializes an internal game session for automatic session management.
1457
- * Only starts a session if:
1458
- * 1. A gameId is configured
1459
- * 2. No session already exists
1460
- * 3. autoStartSession is enabled (defaults to false)
1461
1041
  */
1462
1042
  private _initializeInternalSession;
1463
- /** Identity provider connection methods (connect external accounts) */
1043
+ /**
1044
+ * Current user data and inventory management.
1045
+ * - `me()` - Get authenticated user profile
1046
+ * - `inventory.get()` - List user's items
1047
+ * - `inventory.add(slug, qty)` - Award items to user
1048
+ */
1049
+ users: {
1050
+ me: () => Promise<AuthenticatedUser>;
1051
+ inventory: {
1052
+ get: () => Promise<InventoryItemWithItem[]>;
1053
+ add: (identifier: string, qty: number) => Promise<InventoryMutationResponse>;
1054
+ remove: (identifier: string, qty: number) => Promise<InventoryMutationResponse>;
1055
+ quantity: (identifier: string) => Promise<number>;
1056
+ has: (identifier: string, minQuantity?: number) => Promise<boolean>;
1057
+ };
1058
+ };
1059
+ }
1060
+
1061
+ /**
1062
+ * Playcademy SDK client for game developers.
1063
+ * Provides namespaced access to platform features for games running inside Cademy.
1064
+ */
1065
+ declare class PlaycademyClient extends PlaycademyBaseClient {
1066
+ /**
1067
+ * Connect external identity providers to the user's Playcademy account.
1068
+ * - `connect(provider)` - Link Discord, Google, etc. via OAuth popup
1069
+ */
1464
1070
  identity: {
1465
1071
  connect: (options: AuthOptions) => Promise<AuthResult>;
1466
1072
  _getContext: () => {
1467
1073
  isInIframe: boolean;
1468
1074
  };
1469
1075
  };
1470
- /** Runtime methods (getGameToken, exit) */
1076
+ /**
1077
+ * Game runtime lifecycle and asset loading.
1078
+ * - `exit()` - Return to Cademy hub
1079
+ * - `getGameToken()` - Get short-lived auth token
1080
+ * - `assets.url()`, `assets.json()`, `assets.fetch()` - Load game assets
1081
+ * - `on('pause')`, `on('resume')` - Handle visibility changes
1082
+ */
1471
1083
  runtime: {
1472
1084
  getGameToken: (gameId: string, options?: {
1473
1085
  apply?: boolean;
@@ -1502,64 +1114,58 @@ declare class PlaycademyClient {
1502
1114
  arrayBuffer: (path: string) => Promise<ArrayBuffer>;
1503
1115
  };
1504
1116
  };
1505
- /** User methods (me, inventory management) */
1506
- users: {
1507
- me: () => Promise<AuthenticatedUser>;
1508
- inventory: {
1509
- get: () => Promise<InventoryItemWithItem[]>;
1510
- add: (identifier: string, qty: number) => Promise<InventoryMutationResponse>;
1511
- remove: (identifier: string, qty: number) => Promise<InventoryMutationResponse>;
1512
- quantity: (identifier: string) => Promise<number>;
1513
- has: (identifier: string, minQuantity?: number) => Promise<boolean>;
1514
- };
1515
- };
1516
- /** TimeBack XP methods (today, total, history) */
1117
+ /**
1118
+ * TimeBack integration for activity tracking and user context.
1119
+ *
1120
+ * User context (cached from init, refreshable):
1121
+ * - `user.role` - User's role (student, parent, teacher, etc.)
1122
+ * - `user.enrollments` - Courses the player is enrolled in for this game
1123
+ * - `user.organizations` - Schools/districts the player belongs to
1124
+ * - `user.fetch()` - Refresh user context from server
1125
+ *
1126
+ * Activity tracking:
1127
+ * - `startActivity(metadata)` - Begin tracking an activity
1128
+ * - `pauseActivity()` / `resumeActivity()` - Pause/resume timer
1129
+ * - `endActivity(scoreData)` - Submit activity results to TimeBack
1130
+ */
1517
1131
  timeback: {
1132
+ readonly user: TimebackUser;
1518
1133
  startActivity: (metadata: _playcademy_timeback_types.ActivityData) => void;
1519
1134
  pauseActivity: () => void;
1520
1135
  resumeActivity: () => void;
1521
1136
  endActivity: (data: _playcademy_timeback_types.EndActivityScoreData) => Promise<EndActivityResponse>;
1522
- management: {
1523
- setup: (request: PlatformTimebackSetupRequest) => Promise<PlatformTimebackSetupResponse>;
1524
- verify: (gameId: string) => Promise<TimebackVerifyAllResponse>;
1525
- cleanup: (gameId: string) => Promise<void>;
1526
- get: (gameId: string) => Promise<GameTimebackIntegration[]>;
1527
- getConfig: (gameId: string) => Promise<TimebackSetupRequest["config"]>;
1528
- };
1529
- xp: {
1530
- today: (options?: {
1531
- date?: string;
1532
- timezone?: string;
1533
- }) => Promise<TodayXpResponse>;
1534
- total: () => Promise<TotalXpResponse>;
1535
- history: (options?: {
1536
- startDate?: string;
1537
- endDate?: string;
1538
- }) => Promise<XpHistoryResponse>;
1539
- summary: (options?: {
1540
- date?: string;
1541
- timezone?: string;
1542
- }) => Promise<XpSummaryResponse>;
1543
- };
1544
1137
  };
1545
- /** Credits methods (credits management) */
1138
+ /**
1139
+ * Playcademy Credits (platform currency) management.
1140
+ * - `get()` - Get user's credit balance
1141
+ * - `add(amount)` - Award credits to user
1142
+ */
1546
1143
  credits: {
1547
1144
  balance: () => Promise<number>;
1548
1145
  add: (amount: number) => Promise<number>;
1549
1146
  spend: (amount: number) => Promise<number>;
1550
1147
  };
1551
- /** Platform-wide scores methods (submit, getUserScores) */
1148
+ /**
1149
+ * Game score submission and leaderboards.
1150
+ * - `submit(gameId, score, metadata?)` - Record a game score
1151
+ */
1552
1152
  scores: {
1553
1153
  submit: (gameId: string, score: number, metadata?: Record<string, unknown>) => Promise<ScoreSubmission>;
1554
1154
  };
1555
- /** Realtime methods (token) */
1155
+ /**
1156
+ * Realtime multiplayer authentication.
1157
+ * - `getToken()` - Get token for WebSocket/realtime connections
1158
+ */
1556
1159
  realtime: {
1557
1160
  token: {
1558
1161
  get: () => Promise<RealtimeTokenResponse>;
1559
1162
  };
1560
- open(channel?: string, url?: string): Promise<_playcademy_realtime_server_types.RealtimeChannel>;
1561
1163
  };
1562
- /** Backend methods for calling custom game API routes */
1164
+ /**
1165
+ * Make requests to your game's custom backend API routes.
1166
+ * - `get(path)`, `post(path, body)`, `put()`, `delete()` - HTTP methods
1167
+ * - Routes are relative to your game's deployment (e.g., '/hello' → your-game.playcademy.gg/api/hello)
1168
+ */
1563
1169
  backend: {
1564
1170
  get<T = unknown>(path: string, headers?: Record<string, string>): Promise<T>;
1565
1171
  post<T = unknown>(path: string, body?: unknown, headers?: Record<string, string>): Promise<T>;
@@ -1580,9 +1186,70 @@ declare class PlaycademyClient {
1580
1186
  };
1581
1187
  }
1582
1188
 
1189
+ /**
1190
+ * Type definitions for the game timeback namespace.
1191
+ *
1192
+ * Re-exports core types from @playcademy/data for SDK consumers,
1193
+ * plus SDK-specific types like TimebackInitContext.
1194
+ */
1195
+
1196
+ /**
1197
+ * A TimeBack enrollment for the current game session.
1198
+ * Alias for UserEnrollment without the optional gameId.
1199
+ */
1200
+ type TimebackEnrollment = Omit<UserEnrollment, 'gameId'>;
1201
+ /**
1202
+ * A TimeBack organization (school/district) for the current user.
1203
+ * Alias for UserOrganization.
1204
+ */
1205
+ type TimebackOrganization = UserOrganization;
1206
+ /**
1207
+ * TimeBack context passed during game initialization.
1208
+ * This is sent from the platform (cademy) to the game iframe via postMessage.
1209
+ */
1210
+ interface TimebackInitContext {
1211
+ /** User's TimeBack ID */
1212
+ id: string;
1213
+ /** User's role in TimeBack (student, parent, teacher, etc.) */
1214
+ role: TimebackUserRole;
1215
+ /** User's enrollments for this game (one per grade/subject combo) */
1216
+ enrollments: TimebackEnrollment[];
1217
+ /** User's organizations (schools/districts) */
1218
+ organizations: TimebackOrganization[];
1219
+ }
1220
+ /**
1221
+ * TimeBack user context with current data (may be stale from init).
1222
+ * Use `fetch()` to get fresh data from the server.
1223
+ */
1224
+ interface TimebackUserContext {
1225
+ /** User's TimeBack ID */
1226
+ id: string | undefined;
1227
+ /** User's role in TimeBack (student, parent, teacher, etc.) */
1228
+ role: TimebackUserRole | undefined;
1229
+ /** User's enrollments for this game */
1230
+ enrollments: TimebackEnrollment[];
1231
+ /** User's organizations (schools/districts) */
1232
+ organizations: TimebackOrganization[];
1233
+ }
1234
+ /**
1235
+ * TimeBack user object with both cached getters and fetch method.
1236
+ */
1237
+ interface TimebackUser extends TimebackUserContext {
1238
+ /**
1239
+ * Fetch TimeBack data from the server (cached for 5 min).
1240
+ * Updates the cached values so subsequent property access returns fresh data.
1241
+ * @param options - Cache options (pass { force: true } to bypass cache)
1242
+ * @returns Promise resolving to fresh user context
1243
+ */
1244
+ fetch(options?: {
1245
+ force?: boolean;
1246
+ }): Promise<TimebackUserContext>;
1247
+ }
1248
+
1583
1249
  /**
1584
1250
  * Core client configuration and lifecycle types
1585
1251
  */
1252
+
1586
1253
  type TokenType = 'session' | 'apiKey' | 'gameJwt';
1587
1254
  interface ClientConfig {
1588
1255
  baseUrl: string;
@@ -1615,6 +1282,20 @@ interface DisconnectContext {
1615
1282
  duration?: number;
1616
1283
  }) => void;
1617
1284
  }
1285
+ interface InitPayload {
1286
+ /** Hub API base URL */
1287
+ baseUrl: string;
1288
+ /** Game deployment URL (serves both frontend assets and backend API) */
1289
+ gameUrl?: string;
1290
+ /** Short-lived game token */
1291
+ token: string;
1292
+ /** Game ID */
1293
+ gameId: string;
1294
+ /** Realtime WebSocket URL */
1295
+ realtimeUrl?: string;
1296
+ /** Timeback context (if user has a Timeback account) */
1297
+ timeback?: TimebackInitContext;
1298
+ }
1618
1299
  type GameContextPayload = {
1619
1300
  token: string;
1620
1301
  baseUrl: string;
@@ -1622,6 +1303,9 @@ type GameContextPayload = {
1622
1303
  gameId: string;
1623
1304
  forwardKeys?: string[];
1624
1305
  };
1306
+ type EventListeners = {
1307
+ [E in keyof ClientEvents]?: Array<(payload: ClientEvents[E]) => void>;
1308
+ };
1625
1309
  interface ClientEvents {
1626
1310
  authChange: {
1627
1311
  token: string | null;