@playcademy/sdk 0.5.0-beta.1 → 0.5.1-beta.1

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
@@ -534,6 +534,23 @@ interface UserInfo {
534
534
  lti_resource_link?: unknown;
535
535
  timeback_id?: string;
536
536
  }
537
+ interface DemoProfile {
538
+ displayName: string;
539
+ isDefault: boolean;
540
+ }
541
+ /**
542
+ * Update shape for `client.demo.profile.update(...)`.
543
+ *
544
+ * Kept as a named type so callers typed against it pick up new fields
545
+ * automatically, but `displayName` is the only updatable field today and
546
+ * the server's `DemoProfileSchema` requires it — a no-field payload would
547
+ * 400 at runtime, so we model that at the type level too. When additional
548
+ * fields land, make them required/optional individually based on server
549
+ * validation, rather than blanket-optional.
550
+ */
551
+ interface DemoProfileUpdate {
552
+ displayName: string;
553
+ }
537
554
  /**
538
555
  * Authenticated user for API responses.
539
556
  * Differs from UserRow: omits timebackId, adds hasTimebackAccount and timeback.
@@ -554,6 +571,35 @@ interface AuthenticatedUser {
554
571
  timeback?: UserTimebackData;
555
572
  }
556
573
 
574
+ /**
575
+ * Leaderboard Types
576
+ *
577
+ * @module types/leaderboard
578
+ */
579
+ type LeaderboardTimeframe = 'all_time' | 'monthly' | 'weekly' | 'daily';
580
+ interface LeaderboardOptions {
581
+ timeframe?: LeaderboardTimeframe;
582
+ limit?: number;
583
+ offset?: number;
584
+ gameId?: string;
585
+ }
586
+ /**
587
+ * Leaderboard entry with required game context.
588
+ * Used when fetching leaderboards for a specific game.
589
+ */
590
+ interface GameLeaderboardEntry {
591
+ rank: number;
592
+ userId: string;
593
+ username: string;
594
+ userImage?: string | null;
595
+ score: number;
596
+ achievedAt: Date;
597
+ metadata?: Record<string, unknown>;
598
+ gameId: string;
599
+ gameTitle: string;
600
+ gameSlug: string;
601
+ }
602
+
557
603
  declare const items: drizzle_orm_pg_core.PgTableWithColumns<{
558
604
  name: "items";
559
605
  schema: undefined;
@@ -830,195 +876,6 @@ type InventoryItemWithItem = InventoryItemRow & {
830
876
  item: ItemRow;
831
877
  };
832
878
 
833
- /**
834
- * @fileoverview Authentication Strategy Pattern
835
- *
836
- * Provides different authentication strategies for the Playcademy SDK.
837
- * Each strategy knows how to add its authentication headers to requests.
838
- */
839
-
840
- /**
841
- * Base interface for authentication strategies
842
- */
843
- interface AuthStrategy {
844
- /** Get the token value */
845
- getToken(): string | null;
846
- /** Get the token type */
847
- getType(): TokenType;
848
- /** Get authentication headers for a request */
849
- getHeaders(): Record<string, string>;
850
- }
851
-
852
- /**
853
- * Base Playcademy SDK client with shared infrastructure.
854
- * Provides authentication, HTTP requests, events, connection monitoring,
855
- * and fundamental namespaces used by all clients.
856
- *
857
- * Extended by PlaycademyClient (game SDK) and PlaycademyInternalClient (platform SDK).
858
- */
859
- declare abstract class PlaycademyBaseClient {
860
- baseUrl: string;
861
- gameUrl?: string;
862
- protected authStrategy: AuthStrategy;
863
- protected gameId?: string;
864
- protected config: Partial<ClientConfig>;
865
- protected listeners: EventListeners;
866
- protected internalClientSessionId?: string;
867
- protected authContext?: {
868
- isInIframe: boolean;
869
- };
870
- protected initPayload?: InitPayload;
871
- protected connectionManager?: ConnectionManager;
872
- protected launchId?: string;
873
- /**
874
- * Internal session manager for automatic session lifecycle.
875
- * @private
876
- * @internal
877
- */
878
- protected _sessionManager: {
879
- startSession: (gameId: string) => Promise<{
880
- sessionId: string;
881
- }>;
882
- endSession: (sessionId: string, gameId: string) => Promise<void>;
883
- };
884
- constructor(config?: Partial<ClientConfig>);
885
- /**
886
- * Gets the effective base URL for API requests.
887
- */
888
- getBaseUrl(): string;
889
- /**
890
- * Gets the effective game backend URL for integration requests.
891
- */
892
- protected getGameBackendUrl(): string;
893
- /**
894
- * Simple ping method for testing connectivity.
895
- */
896
- ping(): string;
897
- /**
898
- * Sets the authentication token for API requests.
899
- */
900
- setToken(token: string | null, tokenType?: TokenType): void;
901
- setLaunchId(launchId: string | null | undefined): void;
902
- /**
903
- * Gets the current token type.
904
- */
905
- getTokenType(): TokenType;
906
- /**
907
- * Gets the current authentication token.
908
- */
909
- getToken(): string | null;
910
- /**
911
- * Checks if the client has a valid API token.
912
- */
913
- isAuthenticated(): boolean;
914
- /**
915
- * Registers a callback to be called when authentication state changes.
916
- */
917
- onAuthChange(callback: (token: string | null) => void): void;
918
- /**
919
- * Registers a callback to be called when connection issues are detected.
920
- */
921
- onDisconnect(callback: (context: DisconnectContext) => void | Promise<void>): () => void;
922
- /**
923
- * Gets the current connection state.
924
- */
925
- getConnectionState(): ConnectionState | 'unknown';
926
- /**
927
- * Manually triggers a connection check immediately.
928
- */
929
- checkConnection(): Promise<ConnectionState | 'unknown'>;
930
- /**
931
- * Sets the authentication context for the client.
932
- * @internal
933
- */
934
- _setAuthContext(context: {
935
- isInIframe: boolean;
936
- }): void;
937
- /**
938
- * Registers an event listener for client events.
939
- */
940
- on<E extends keyof ClientEvents>(event: E, callback: (payload: ClientEvents[E]) => void): void;
941
- /**
942
- * Emits an event to all registered listeners.
943
- */
944
- protected emit<E extends keyof ClientEvents>(event: E, payload: ClientEvents[E]): void;
945
- /**
946
- * Makes an authenticated HTTP request to the platform API.
947
- */
948
- protected request<T>(path: string, method: Method, options?: {
949
- body?: unknown;
950
- headers?: Record<string, string>;
951
- raw?: boolean;
952
- retryPolicy?: RetryPolicy;
953
- }): Promise<T>;
954
- /**
955
- * Makes an authenticated HTTP request to the game's backend Worker.
956
- */
957
- protected requestGameBackend<T>(path: string, method: Method, body?: unknown, headers?: Record<string, string>, options?: {
958
- raw?: boolean;
959
- retryPolicy?: RetryPolicy;
960
- }): Promise<T>;
961
- /**
962
- * Ensures a gameId is available, throwing an error if not.
963
- */
964
- protected _ensureGameId(): string;
965
- /**
966
- * Detects and sets the authentication context (iframe vs standalone).
967
- */
968
- private _detectAuthContext;
969
- /**
970
- * Initializes connection monitoring if enabled.
971
- */
972
- private _initializeConnectionMonitor;
973
- private _initializeInternalSession;
974
- /**
975
- * Current user data and inventory management.
976
- * - `me()` - Get authenticated user profile
977
- * - `inventory.get()` - List user's items
978
- * - `inventory.add(slug, qty)` - Award items to user
979
- */
980
- users: {
981
- me: () => Promise<AuthenticatedUser>;
982
- inventory: {
983
- get: () => Promise<InventoryItemWithItem[]>;
984
- add: (identifier: string, qty: number) => Promise<InventoryMutationResponse>;
985
- remove: (identifier: string, qty: number) => Promise<InventoryMutationResponse>;
986
- quantity: (identifier: string) => Promise<number>;
987
- has: (identifier: string, minQuantity?: number) => Promise<boolean>;
988
- };
989
- };
990
- }
991
-
992
- /**
993
- * Options for configuring activity tracking behavior.
994
- */
995
- interface StartActivityOptions {
996
- /**
997
- * How long the tab can stay hidden before the timing window resets on return.
998
- * Defaults to 10 minutes. Set to `Infinity` to disable.
999
- */
1000
- hiddenTimeoutMs?: number;
1001
- /**
1002
- * How often to flush periodic heartbeats with accumulated time data.
1003
- * Defaults to 15 seconds. Set to `Infinity` to disable the interval;
1004
- * final unload/endActivity flushes still run.
1005
- */
1006
- heartbeatIntervalMs?: number;
1007
- /**
1008
- * Stable identifier for this activity run. When provided, it is used on
1009
- * every heartbeat and on endActivity instead of a freshly-generated UUID.
1010
- *
1011
- * Pass the same `runId` across multiple `startActivity()` calls (for
1012
- * example, after the player closes and reopens a resumable activity) so
1013
- * downstream systems can correlate related sessions into a single run.
1014
- *
1015
- * Must be a UUID (the backend validates it as such) and unique per
1016
- * logical run. If omitted, the SDK generates a new UUID on each call,
1017
- * which means every session is treated as its own run.
1018
- */
1019
- runId?: string;
1020
- }
1021
-
1022
879
  /**
1023
880
  * Auto-initializes a PlaycademyClient with context from the environment.
1024
881
  * Works in both iframe mode (production/development) and standalone mode (local dev).
@@ -1085,1271 +942,1531 @@ declare function init<T extends PlaycademyBaseClient = PlaycademyBaseClient>(thi
1085
942
  declare function login(baseUrl: string, email: string, password: string): Promise<LoginResponse>;
1086
943
 
1087
944
  /**
1088
- * Playcademy SDK client for game developers.
1089
- * Provides namespaced access to platform features for games running inside Cademy.
945
+ * @fileoverview Playcademy Messaging System
946
+ *
947
+ * This file implements a unified messaging system for the Playcademy platform that handles
948
+ * communication between different contexts:
949
+ *
950
+ * 1. **Iframe-to-Parent Communication**: When games run inside iframes (production/development),
951
+ * they need to communicate with the parent window using postMessage API
952
+ *
953
+ * 2. **Local Communication**: When games run in the same context (local development),
954
+ * they use CustomEvents for internal messaging
955
+ *
956
+ * The system automatically detects the runtime environment and chooses the appropriate
957
+ * transport method, abstracting this complexity from the developer.
958
+ *
959
+ * **Architecture Overview**:
960
+ * - Games run in iframes for security and isolation
961
+ * - Parent window (Playcademy shell) manages game lifecycle
962
+ * - Messages flow bidirectionally between parent and iframe
963
+ * - Local development mode simulates this architecture without iframes
1090
964
  */
1091
- declare class PlaycademyClient extends PlaycademyBaseClient {
965
+
966
+ /**
967
+ * Enumeration of all message types used in the Playcademy messaging system.
968
+ *
969
+ * **Message Flow Patterns**:
970
+ *
971
+ * **Parent → Game (Overworld → Game)**:
972
+ * - INIT: Provides game with authentication token and configuration
973
+ * - TOKEN_REFRESH: Updates game's authentication token before expiry
974
+ * - PAUSE/RESUME: Controls game execution state
975
+ * - FORCE_EXIT: Immediately terminates the game
976
+ * - OVERLAY: Shows/hides UI overlays over the game
977
+ *
978
+ * **Game → Parent (Game → Overworld)**:
979
+ * - READY: Game has loaded and is ready to receive messages
980
+ * - EXIT: Game requests to be closed (user clicked exit, game ended, etc.)
981
+ * - TELEMETRY: Game reports performance metrics (FPS, memory usage, etc.)
982
+ */
983
+ declare enum MessageEvents {
1092
984
  /**
1093
- * Connect external identity providers to the user's Playcademy account.
1094
- * - `connect(provider)` - Link Discord, Google, etc. via OAuth popup
985
+ * Initializes the game with authentication context and configuration.
986
+ * Sent immediately after game iframe loads.
987
+ * Payload:
988
+ * - `baseUrl`: string
989
+ * - `token`: string
990
+ * - `gameId`: string
1095
991
  */
1096
- identity: {
1097
- connect: (options: AuthOptions) => Promise<AuthResult>;
1098
- _getContext: () => {
1099
- isInIframe: boolean;
1100
- };
1101
- };
992
+ INIT = "PLAYCADEMY_INIT",
1102
993
  /**
1103
- * Game runtime lifecycle and asset loading.
1104
- * - `exit()` - Return to Cademy hub
1105
- * - `getGameToken()` - Get short-lived auth token
1106
- * - `assets.url()`, `assets.json()`, `assets.fetch()` - Load game assets
1107
- * - `on('pause')`, `on('resume')` - Handle visibility changes
994
+ * Updates the game's authentication token before it expires.
995
+ * Sent periodically to maintain valid authentication.
996
+ * Payload:
997
+ * - `token`: string
998
+ * - `exp`: number
1108
999
  */
1109
- runtime: {
1110
- getGameToken: (gameId: string, options?: {
1111
- apply?: boolean | undefined;
1112
- } | undefined) => Promise<GameTokenResponse>;
1113
- exit: () => Promise<void>;
1114
- onInit: (handler: (context: GameContextPayload) => void) => void;
1115
- onTokenRefresh: (handler: (data: {
1116
- token: string;
1117
- exp: number;
1118
- }) => void) => void;
1119
- onPause: (handler: () => void) => void;
1120
- onResume: (handler: () => void) => void;
1121
- onForceExit: (handler: () => void) => void;
1122
- onOverlay: (handler: (isVisible: boolean) => void) => void;
1123
- ready: () => void;
1124
- sendTelemetry: (data: {
1125
- fps: number;
1126
- mem: number;
1127
- }) => void;
1128
- removeListener: (eventType: MessageEvents, handler: ((context: GameContextPayload) => void) | ((data: {
1129
- token: string;
1130
- exp: number;
1131
- }) => void) | (() => void) | ((isVisible: boolean) => void)) => void;
1132
- removeAllListeners: () => void;
1133
- getListenerCounts: () => Record<string, number>;
1134
- assets: {
1135
- url(pathOrStrings: string | TemplateStringsArray, ...values: unknown[]): string;
1136
- fetch: (path: string, options?: RequestInit | undefined) => Promise<Response>;
1137
- json: <T = unknown>(path: string) => Promise<T>;
1138
- blob: (path: string) => Promise<Blob>;
1139
- text: (path: string) => Promise<string>;
1140
- arrayBuffer: (path: string) => Promise<ArrayBuffer>;
1141
- };
1142
- };
1000
+ TOKEN_REFRESH = "PLAYCADEMY_TOKEN_REFRESH",
1143
1001
  /**
1144
- * TimeBack integration for activity tracking and user context.
1145
- *
1146
- * User context (cached from init, refreshable):
1147
- * - `user.role` - User's role (student, parent, teacher, etc.)
1148
- * - `user.enrollments` - Courses the player is enrolled in for this game
1149
- * - `user.organizations` - Schools/districts the player belongs to
1150
- * - `user.fetch()` - Refresh user context from server
1151
- *
1152
- * Activity tracking:
1153
- * - `startActivity(metadata)` - Begin tracking an activity
1154
- * - `pauseActivity()` / `resumeActivity()` - Pause/resume timer
1155
- * - `endActivity(scoreData)` - Submit activity results to TimeBack
1002
+ * Pauses game execution (e.g., when user switches tabs).
1003
+ * Game should pause timers, animations, and user input.
1004
+ * Payload: void
1156
1005
  */
1157
- timeback: {
1158
- readonly user: TimebackUser;
1159
- startActivity: (metadata: ActivityData, options?: StartActivityOptions | undefined) => void;
1160
- pauseActivity: () => void;
1161
- resumeActivity: () => void;
1162
- endActivity: (data: EndActivityScoreData) => Promise<EndActivityResponse>;
1163
- };
1006
+ PAUSE = "PLAYCADEMY_PAUSE",
1164
1007
  /**
1165
- * Playcademy Credits (platform currency) management.
1166
- * - `get()` - Get user's credit balance
1167
- * - `add(amount)` - Award credits to user
1008
+ * Resumes game execution after being paused.
1009
+ * Game should restore timers, animations, and user input.
1010
+ * Payload: void
1168
1011
  */
1169
- credits: {
1170
- balance: () => Promise<number>;
1171
- add: (amount: number) => Promise<number>;
1172
- spend: (amount: number) => Promise<number>;
1173
- };
1012
+ RESUME = "PLAYCADEMY_RESUME",
1174
1013
  /**
1175
- * Game score submission and leaderboards.
1176
- * - `submit(gameId, score, metadata?)` - Record a game score
1014
+ * Forces immediate game termination (emergency exit).
1015
+ * Game should clean up resources and exit immediately.
1016
+ * Payload: void
1177
1017
  */
1178
- scores: {
1179
- submit: (gameId: string, score: number, metadata?: Record<string, unknown> | undefined) => Promise<ScoreSubmission>;
1180
- };
1018
+ FORCE_EXIT = "PLAYCADEMY_FORCE_EXIT",
1181
1019
  /**
1182
- * Realtime multiplayer authentication.
1183
- * - `getToken()` - Get token for WebSocket/realtime connections
1020
+ * Shows or hides UI overlays over the game.
1021
+ * Game may need to pause or adjust rendering accordingly.
1022
+ * Payload: boolean (true = show overlay, false = hide overlay)
1184
1023
  */
1185
- realtime: {
1186
- token: {
1187
- get: () => Promise<RealtimeTokenResponse>;
1188
- };
1189
- };
1024
+ OVERLAY = "PLAYCADEMY_OVERLAY",
1190
1025
  /**
1191
- * Make requests to your game's custom backend API routes.
1192
- * - `get(path)`, `post(path, body)`, `put()`, `delete()` - HTTP methods
1193
- * - Routes are relative to your game's deployment (e.g., '/hello' → your-game.playcademy.gg/api/hello)
1026
+ * Broadcasts connection state changes to games.
1027
+ * Sent by platform when network connectivity changes.
1028
+ * Payload:
1029
+ * - `state`: 'online' | 'offline' | 'degraded'
1030
+ * - `reason`: string
1194
1031
  */
1195
- backend: {
1196
- get<T = unknown>(path: string, headers?: Record<string, string> | undefined): Promise<T>;
1197
- post<T = unknown>(path: string, body?: unknown, headers?: Record<string, string> | undefined): Promise<T>;
1198
- put<T = unknown>(path: string, body?: unknown, headers?: Record<string, string> | undefined): Promise<T>;
1199
- patch<T = unknown>(path: string, body?: unknown, headers?: Record<string, string> | undefined): Promise<T>;
1200
- delete<T = unknown>(path: string, headers?: Record<string, string> | undefined): Promise<T>;
1201
- request<T = unknown>(path: string, method: Method, body?: unknown, headers?: Record<string, string> | undefined): Promise<T>;
1202
- download(path: string, method?: Method, body?: unknown, headers?: Record<string, string> | undefined): Promise<Response>;
1203
- url(pathOrStrings: string | TemplateStringsArray, ...values: unknown[]): string;
1204
- };
1205
- /** Auto-initializes a PlaycademyClient with context from the environment */
1206
- static init: typeof init;
1207
- /** Authenticates a user with email and password */
1208
- static login: typeof login;
1209
- /** Static identity utilities for OAuth operations */
1210
- static identity: {
1211
- parseOAuthState: typeof parseOAuthState;
1212
- };
1032
+ CONNECTION_STATE = "PLAYCADEMY_CONNECTION_STATE",
1033
+ /**
1034
+ * Game has finished loading and is ready to receive messages.
1035
+ * Sent once after game initialization is complete.
1036
+ * Payload: void
1037
+ */
1038
+ READY = "PLAYCADEMY_READY",
1039
+ /**
1040
+ * Game requests to be closed/exited.
1041
+ * Sent when user clicks exit button or game naturally ends.
1042
+ * Payload: void
1043
+ */
1044
+ EXIT = "PLAYCADEMY_EXIT",
1045
+ /**
1046
+ * Game reports performance telemetry data.
1047
+ * Sent periodically for monitoring and analytics.
1048
+ * Payload:
1049
+ * - `fps`: number
1050
+ * - `mem`: number
1051
+ */
1052
+ TELEMETRY = "PLAYCADEMY_TELEMETRY",
1053
+ /**
1054
+ * Game reports key events to parent.
1055
+ * Sent when certain keys are pressed within the game iframe.
1056
+ * Payload:
1057
+ * - `key`: string
1058
+ * - `code?`: string
1059
+ * - `type`: 'keydown' | 'keyup'
1060
+ */
1061
+ KEY_EVENT = "PLAYCADEMY_KEY_EVENT",
1062
+ /**
1063
+ * Game requests platform to display an alert.
1064
+ * Sent when connection issues are detected or other important events occur.
1065
+ * Payload:
1066
+ * - `message`: string
1067
+ * - `options`: `{ type?: 'info' | 'warning' | 'error', duration?: number }`
1068
+ */
1069
+ DISPLAY_ALERT = "PLAYCADEMY_DISPLAY_ALERT",
1070
+ /**
1071
+ * Game signals that demo mode has ended.
1072
+ * Sent when a demo experience reaches its CTA/upgrade boundary.
1073
+ * Payload:
1074
+ * - `score?`: number
1075
+ * - `durationMs?`: number
1076
+ * - `metadata?`: `Record<string, unknown>`
1077
+ */
1078
+ DEMO_END = "PLAYCADEMY_DEMO_END",
1079
+ /**
1080
+ * Notifies about authentication state changes.
1081
+ * Can be sent in both directions depending on auth flow.
1082
+ * Payload:
1083
+ * - `authenticated`: boolean
1084
+ * - `user`: UserInfo | null
1085
+ * - `error`: Error | null
1086
+ */
1087
+ AUTH_STATE_CHANGE = "PLAYCADEMY_AUTH_STATE_CHANGE",
1088
+ /**
1089
+ * OAuth callback data from popup/new-tab windows.
1090
+ * Sent from popup window back to parent after OAuth completes.
1091
+ * Payload:
1092
+ * - `code`: string (OAuth authorization code)
1093
+ * - `state`: string (OAuth state for CSRF protection)
1094
+ * - `error`: string | null (OAuth error if any)
1095
+ */
1096
+ AUTH_CALLBACK = "PLAYCADEMY_AUTH_CALLBACK"
1213
1097
  }
1214
-
1215
1098
  /**
1216
- * Type definitions for the game timeback namespace.
1099
+ * Type definition for message handler functions.
1100
+ * These functions are called when a specific message type is received.
1217
1101
  *
1218
- * SDK-specific types like TimebackInitContext that wrap the core types
1219
- * from @playcademy/types/user (which are re-exported via types/data.ts).
1220
- */
1221
-
1222
- /**
1223
- * A TimeBack enrollment for the current game session.
1224
- * Alias for UserEnrollment without the optional gameId.
1102
+ * @template T - The type of payload data the handler expects
1103
+ * @param payload - The data sent with the message
1225
1104
  */
1226
- type TimebackEnrollment = Omit<UserEnrollment, 'gameId'>;
1105
+ type MessageHandler<T = unknown> = (payload: T) => void;
1227
1106
  /**
1228
- * A TimeBack organization (school/district) for the current user.
1229
- * Alias for UserOrganization.
1107
+ * Type mapping that defines the payload structure for each message type.
1108
+ * This ensures type safety when sending and receiving messages.
1109
+ *
1110
+ * **Usage Examples**:
1111
+ * ```typescript
1112
+ * // Type-safe message sending
1113
+ * messaging.send(MessageEvents.INIT, { baseUrl: '/api', token: 'abc', gameId: '123' })
1114
+ *
1115
+ * // Type-safe message handling
1116
+ * messaging.listen(MessageEvents.TOKEN_REFRESH, ({ token, exp }) => {
1117
+ * console.log(`New token expires at: ${new Date(exp)}`)
1118
+ * })
1119
+ * ```
1230
1120
  */
1231
- type TimebackOrganization = UserOrganization;
1121
+ interface MessageEventMap {
1122
+ /** Game initialization context with API endpoint, auth token, and game ID */
1123
+ [MessageEvents.INIT]: GameContextPayload;
1124
+ /** Token refresh data with new token and expiration timestamp */
1125
+ [MessageEvents.TOKEN_REFRESH]: TokenRefreshPayload;
1126
+ /** Pause message has no payload data */
1127
+ [MessageEvents.PAUSE]: void;
1128
+ /** Resume message has no payload data */
1129
+ [MessageEvents.RESUME]: void;
1130
+ /** Force exit message has no payload data */
1131
+ [MessageEvents.FORCE_EXIT]: void;
1132
+ /** Overlay visibility state (true = show, false = hide) */
1133
+ [MessageEvents.OVERLAY]: boolean;
1134
+ /** Connection state change from platform */
1135
+ [MessageEvents.CONNECTION_STATE]: ConnectionStatePayload;
1136
+ /** Ready message has no payload data */
1137
+ [MessageEvents.READY]: void;
1138
+ /** Exit message has no payload data */
1139
+ [MessageEvents.EXIT]: void;
1140
+ /** Performance telemetry data */
1141
+ [MessageEvents.TELEMETRY]: TelemetryPayload;
1142
+ /** Key event data */
1143
+ [MessageEvents.KEY_EVENT]: KeyEventPayload;
1144
+ /** Display alert request from game */
1145
+ [MessageEvents.DISPLAY_ALERT]: DisplayAlertPayload;
1146
+ /** Demo end signal from game */
1147
+ [MessageEvents.DEMO_END]: DemoEndPayload;
1148
+ /** Authentication state change notification */
1149
+ [MessageEvents.AUTH_STATE_CHANGE]: AuthStateChangePayload;
1150
+ /** OAuth callback data from popup/new-tab windows */
1151
+ [MessageEvents.AUTH_CALLBACK]: AuthCallbackPayload;
1152
+ }
1232
1153
  /**
1233
- * TimeBack context passed during game initialization.
1234
- * This is sent from the platform (cademy) to the game iframe via postMessage.
1235
- */
1236
- interface TimebackInitContext {
1237
- /** User's TimeBack ID */
1238
- id: string;
1239
- /** User's role in TimeBack (student, parent, teacher, etc.) */
1240
- role: TimebackUserRole;
1241
- /** User's enrollments for this game (one per grade/subject combo) */
1242
- enrollments: TimebackEnrollment[];
1243
- /** User's organizations (schools/districts) */
1244
- organizations: TimebackOrganization[];
1245
- }
1246
- /**
1247
- * TimeBack user context with current data (may be stale from init).
1248
- * Use `fetch()` to get fresh data from the server.
1249
- */
1250
- interface TimebackUserContext {
1251
- /** User's TimeBack ID */
1252
- id: string | undefined;
1253
- /** User's role in TimeBack (student, parent, teacher, etc.) */
1254
- role: TimebackUserRole | undefined;
1255
- /** User's enrollments for this game */
1256
- enrollments: TimebackEnrollment[];
1257
- /** User's organizations (schools/districts) */
1258
- organizations: TimebackOrganization[];
1259
- }
1260
- /**
1261
- * XP data access for the current user.
1262
- * Results are cached for 5 seconds to avoid redundant network requests.
1154
+ * **PlaycademyMessaging Class**
1155
+ *
1156
+ * This is the core messaging system that handles all communication in the Playcademy platform.
1157
+ * It automatically detects the runtime environment and chooses the appropriate transport method.
1158
+ *
1159
+ * **Key Features**:
1160
+ * 1. **Automatic Transport Selection**: Detects iframe vs local context and uses appropriate method
1161
+ * 2. **Type Safety**: Full TypeScript support with payload type checking
1162
+ * 3. **Bidirectional Communication**: Handles both parent→game and game→parent messages
1163
+ * 4. **Event Cleanup**: Proper listener management to prevent memory leaks
1164
+ *
1165
+ * **Transport Methods**:
1166
+ * - **postMessage**: Used for iframe-to-parent communication (production/development)
1167
+ * - **CustomEvent**: Used for local same-context communication (local development)
1168
+ *
1169
+ * **Runtime Detection Logic**:
1170
+ * - If `window.self !== window.top`: We're in an iframe, use postMessage
1171
+ * - If `window.self === window.top`: We're in the same context, use CustomEvent
1172
+ *
1173
+ * **Example Usage**:
1174
+ * ```typescript
1175
+ * // Send a message (automatically chooses transport)
1176
+ * messaging.send(MessageEvents.READY, undefined)
1177
+ *
1178
+ * // Listen for messages (handles both transports)
1179
+ * messaging.listen(MessageEvents.INIT, (payload) => {
1180
+ * console.log('Game initialized with:', payload)
1181
+ * })
1182
+ *
1183
+ * // Clean up listeners
1184
+ * messaging.unlisten(MessageEvents.INIT, handler)
1185
+ * ```
1263
1186
  */
1264
- interface TimebackUserXp {
1187
+ declare class PlaycademyMessaging {
1265
1188
  /**
1266
- * Fetch XP data from the server.
1267
- * Returns XP for all courses in this game, or filter by grade/subject.
1268
- * Results are cached for 5 seconds (use `force: true` to bypass).
1189
+ * Internal storage for message listeners.
1269
1190
  *
1270
- * @param options - Query options
1271
- * @param options.grade - Grade level to filter (must be used with subject)
1272
- * @param options.subject - Subject to filter (must be used with grade)
1273
- * @param options.include - Additional data to include: 'perCourse', 'today'
1274
- * @param options.force - Bypass cache and fetch fresh data (default: false)
1275
- * @returns Promise resolving to XP data
1191
+ * **Structure Explanation**:
1192
+ * - Outer Map: MessageEvents Map of handlers for that event type
1193
+ * - Inner Map: MessageHandler Object containing both listener types
1194
+ * - Object: { postMessage: EventListener, customEvent: EventListener }
1195
+ *
1196
+ * **Why Two Listeners Per Handler?**:
1197
+ * Since we don't know at registration time which transport will be used,
1198
+ * we register both a postMessage listener and a CustomEvent listener.
1199
+ * The appropriate one will be triggered based on the runtime context.
1200
+ */
1201
+ private listeners;
1202
+ /**
1203
+ * **Send Message Method**
1204
+ *
1205
+ * Sends a message using the appropriate transport method based on the runtime context.
1206
+ * This is the main public API for sending messages in the Playcademy system.
1207
+ *
1208
+ * **How It Works**:
1209
+ * 1. Analyzes the message type and current runtime context
1210
+ * 2. Determines if we're in an iframe and if this message should use postMessage
1211
+ * 3. Routes to the appropriate transport method (postMessage or CustomEvent)
1212
+ *
1213
+ * **Transport Selection Logic**:
1214
+ * - **postMessage**: Used when game is in iframe and sending to parent, OR when target window is specified
1215
+ * - **CustomEvent**: Used for local/same-context communication
1216
+ *
1217
+ * **Type Safety**:
1218
+ * The generic type parameter `K` ensures that the payload type matches
1219
+ * the expected type for the given message event.
1220
+ *
1221
+ * @template K - The message event type (ensures type safety)
1222
+ * @param type - The message event type to send
1223
+ * @param payload - The data to send with the message (type-checked)
1224
+ * @param options - Optional configuration for message sending
1276
1225
  *
1277
1226
  * @example
1278
1227
  * ```typescript
1279
- * // Get total XP for all game courses
1280
- * const xp = await client.timeback.user.xp.fetch()
1228
+ * // Send game ready signal (no payload)
1229
+ * messaging.send(MessageEvents.READY, undefined)
1281
1230
  *
1282
- * // Get XP for a specific grade/subject
1283
- * const xp = await client.timeback.user.xp.fetch({
1284
- * grade: 3,
1285
- * subject: 'Math'
1286
- * })
1231
+ * // Send telemetry data (typed payload)
1232
+ * messaging.send(MessageEvents.TELEMETRY, { fps: 60, mem: 128 })
1287
1233
  *
1288
- * // Get XP with per-course breakdown
1289
- * const xp = await client.timeback.user.xp.fetch({
1290
- * include: ['perCourse', 'today']
1234
+ * // Send to specific iframe window (parent to iframe communication)
1235
+ * messaging.send(MessageEvents.INIT, { baseUrl, token, gameId }, {
1236
+ * target: iframe.contentWindow,
1237
+ * origin: '*'
1291
1238
  * })
1292
1239
  *
1293
- * // Force fresh data
1294
- * const xp = await client.timeback.user.xp.fetch({ force: true })
1240
+ * // TypeScript will error if payload type is wrong
1241
+ * messaging.send(MessageEvents.INIT, "wrong type") // Error
1295
1242
  * ```
1296
1243
  */
1297
- fetch(options?: GetXpOptions): Promise<XpResponse>;
1298
- }
1299
- /**
1300
- * TimeBack user object with both cached getters and fetch method.
1301
- */
1302
- interface TimebackUser extends TimebackUserContext {
1244
+ send<K extends MessageEvents>(type: K, payload: MessageEventMap[K], options?: {
1245
+ target?: Window;
1246
+ origin?: string;
1247
+ }): void;
1303
1248
  /**
1304
- * Fetch TimeBack data from the server (cached for 5 min).
1305
- * Updates the cached values so subsequent property access returns fresh data.
1306
- * @param options - Cache options (pass { force: true } to bypass cache)
1307
- * @returns Promise resolving to fresh user context
1249
+ * **Listen for Messages Method**
1250
+ *
1251
+ * Registers a message listener that will be called when the specified message type is received.
1252
+ * This method automatically handles both postMessage and CustomEvent sources.
1253
+ *
1254
+ * **Why Register Two Listeners?**:
1255
+ * Since we don't know at registration time which transport method will be used to send
1256
+ * messages, we register listeners for both possible sources:
1257
+ * 1. **postMessage listener**: Handles messages from iframe-to-parent communication
1258
+ * 2. **CustomEvent listener**: Handles messages from local same-context communication
1259
+ *
1260
+ * **Message Processing**:
1261
+ * - **postMessage**: Extracts data from `event.data.payload` or `event.data`
1262
+ * - **CustomEvent**: Extracts data from `event.detail`
1263
+ *
1264
+ * **Type Safety**:
1265
+ * The handler function receives the correctly typed payload based on the message type.
1266
+ *
1267
+ * @template K - The message event type (ensures type safety)
1268
+ * @param type - The message event type to listen for
1269
+ * @param handler - Function to call when the message is received
1270
+ *
1271
+ * @example
1272
+ * ```typescript
1273
+ * // Listen for game initialization
1274
+ * messaging.listen(MessageEvents.INIT, (payload) => {
1275
+ * // payload is automatically typed as GameContextPayload
1276
+ * console.log(`Game ${payload.gameId} initialized`)
1277
+ * console.log(`API base URL: ${payload.baseUrl}`)
1278
+ * })
1279
+ *
1280
+ * // Listen for token refresh
1281
+ * messaging.listen(MessageEvents.TOKEN_REFRESH, ({ token, exp }) => {
1282
+ * // payload is automatically typed as { token: string; exp: number }
1283
+ * updateAuthToken(token)
1284
+ * scheduleTokenRefresh(exp)
1285
+ * })
1286
+ * ```
1308
1287
  */
1309
- fetch(options?: {
1310
- force?: boolean;
1311
- }): Promise<TimebackUserContext>;
1288
+ listen<K extends MessageEvents>(type: K, handler: MessageHandler<MessageEventMap[K]>): void;
1312
1289
  /**
1313
- * XP data for the current user.
1314
- * Call `xp.fetch()` to get XP from the server.
1290
+ * **Remove Message Listener Method**
1291
+ *
1292
+ * Removes a previously registered message listener to prevent memory leaks and unwanted callbacks.
1293
+ * This method cleans up both the postMessage and CustomEvent listeners that were registered.
1294
+ *
1295
+ * **Why Clean Up Both Listeners?**:
1296
+ * When we registered the listener with `listen()`, we created two browser event listeners:
1297
+ * 1. A 'message' event listener for postMessage communication
1298
+ * 2. A custom event listener for local CustomEvent communication
1299
+ *
1300
+ * Both must be removed to prevent memory leaks and ensure the handler is completely unregistered.
1301
+ *
1302
+ * **Memory Management**:
1303
+ * - Removes both event listeners from the browser
1304
+ * - Cleans up internal tracking maps
1305
+ * - If no more handlers exist for a message type, removes the entire type entry
1306
+ *
1307
+ * **Safe to Call Multiple Times**:
1308
+ * This method is idempotent - calling it multiple times with the same handler is safe.
1309
+ *
1310
+ * @template K - The message event type (ensures type safety)
1311
+ * @param type - The message event type to stop listening for
1312
+ * @param handler - The exact handler function that was passed to listen()
1313
+ *
1314
+ * @example
1315
+ * ```typescript
1316
+ * // Register a handler
1317
+ * const handleInit = (payload) => console.log('Game initialized')
1318
+ * messaging.listen(MessageEvents.INIT, handleInit)
1319
+ *
1320
+ * // Later, remove the handler
1321
+ * messaging.unlisten(MessageEvents.INIT, handleInit)
1322
+ *
1323
+ * // Safe to call multiple times
1324
+ * messaging.unlisten(MessageEvents.INIT, handleInit) // No error
1325
+ * ```
1315
1326
  */
1316
- xp: TimebackUserXp;
1317
- }
1318
- /**
1319
- * Options for querying student XP.
1320
- */
1321
- interface GetXpOptions {
1322
- /** Grade level to filter (must be used with subject) */
1323
- grade?: TimebackGrade;
1324
- /** Subject to filter (must be used with grade) */
1325
- subject?: TimebackSubject;
1326
- /** Additional data to include: 'perCourse', 'today' */
1327
- include?: ('perCourse' | 'today')[];
1328
- /** Bypass cache and fetch fresh data (default: false) */
1329
- force?: boolean;
1330
- }
1331
- /**
1332
- * XP data for a single course.
1333
- */
1334
- interface CourseXp {
1335
- grade: TimebackGrade;
1336
- subject: TimebackSubject;
1337
- title: string;
1338
- totalXp: number;
1339
- todayXp?: number;
1340
- }
1341
- /**
1342
- * Response from XP query.
1343
- */
1344
- interface XpResponse {
1345
- totalXp: number;
1346
- todayXp?: number;
1347
- courses?: CourseXp[];
1348
- }
1349
-
1350
- /**
1351
- * Core client configuration and lifecycle types
1352
- */
1353
-
1354
- type TokenType = 'session' | 'apiKey' | 'gameJwt';
1355
- interface ClientConfig {
1356
- baseUrl: string;
1357
- gameUrl?: string;
1358
- token?: string;
1359
- tokenType?: TokenType;
1360
- gameId?: string;
1361
- launchId?: string;
1362
- autoStartSession?: boolean;
1363
- onDisconnect?: DisconnectHandler;
1364
- enableConnectionMonitoring?: boolean;
1365
- }
1366
- /**
1367
- * Handler called when connection state changes to offline or degraded.
1368
- * Games can implement this to handle disconnects gracefully.
1369
- */
1370
- type DisconnectHandler = (context: DisconnectContext) => void | Promise<void>;
1371
- /**
1372
- * Context provided to disconnect handlers
1373
- */
1374
- interface DisconnectContext {
1375
- /** Current connection state */
1376
- state: 'offline' | 'degraded';
1377
- /** Reason for the disconnect */
1378
- reason: string;
1379
- /** Timestamp when disconnect was detected */
1380
- timestamp: number;
1381
- /** Utility to display a platform-level alert */
1382
- displayAlert: (message: string, options?: {
1383
- type?: 'info' | 'warning' | 'error';
1384
- duration?: number;
1385
- }) => void;
1386
- }
1387
- interface InitPayload {
1388
- /** Hub API base URL */
1389
- baseUrl: string;
1390
- /** Game deployment URL (serves both frontend assets and backend API) */
1391
- gameUrl?: string;
1392
- /** Short-lived game token */
1393
- token: string;
1394
- /** Game ID */
1395
- gameId: string;
1396
- /** Realtime WebSocket URL */
1397
- realtimeUrl?: string;
1398
- /** Timeback context (if user has a Timeback account) */
1399
- timeback?: TimebackInitContext;
1400
- }
1401
- interface GameContextPayload {
1402
- token: string;
1403
- baseUrl: string;
1404
- realtimeUrl: string;
1405
- gameId: string;
1406
- forwardKeys?: string[];
1407
- }
1408
- type EventListeners = {
1409
- [E in keyof ClientEvents]?: ((payload: ClientEvents[E]) => void)[];
1410
- };
1411
- interface ClientEvents {
1412
- authChange: {
1413
- token: string | null;
1414
- };
1415
- inventoryChange: {
1416
- itemId: string;
1417
- delta: number;
1418
- newTotal: number;
1419
- };
1420
- levelUp: {
1421
- oldLevel: number;
1422
- newLevel: number;
1423
- creditsAwarded: number;
1424
- };
1425
- xpGained: {
1426
- amount: number;
1427
- totalXP: number;
1428
- leveledUp: boolean;
1429
- };
1430
- connectionChange: {
1431
- state: 'online' | 'offline' | 'degraded';
1432
- reason: string;
1433
- };
1434
- }
1435
-
1436
- /**
1437
- * Event and message payload types for SDK messaging system
1438
- */
1439
-
1440
- interface DisplayAlertPayload {
1441
- message: string;
1442
- options?: {
1443
- type?: 'info' | 'warning' | 'error';
1444
- duration?: number;
1445
- };
1446
- }
1447
- /**
1448
- * Authentication state change event payload.
1449
- * Used when authentication state changes in the application.
1450
- */
1451
- interface AuthStateChangePayload {
1452
- /** Whether the user is currently authenticated */
1453
- authenticated: boolean;
1454
- /** User information if authenticated, null otherwise */
1455
- user: UserInfo | null;
1456
- /** Error information if authentication failed */
1457
- error: Error | null;
1458
- }
1459
- /**
1460
- * OAuth callback event payload.
1461
- * Used when OAuth flow completes in popup/new-tab windows.
1462
- */
1463
- interface AuthCallbackPayload {
1464
- /** OAuth authorization code */
1465
- code: string;
1466
- /** OAuth state parameter for CSRF protection */
1467
- state: string;
1468
- /** Error message if OAuth flow failed */
1469
- error: string | null;
1470
- }
1471
- /**
1472
- * Token refresh event payload.
1473
- * Sent when authentication token is updated.
1474
- */
1475
- interface TokenRefreshPayload {
1476
- /** New authentication token */
1477
- token: string;
1478
- /** Token expiration timestamp */
1479
- exp: number;
1480
- }
1481
- /**
1482
- * Telemetry event payload.
1483
- * Performance metrics sent from the game.
1484
- */
1485
- interface TelemetryPayload {
1486
- /** Frames per second */
1487
- fps: number;
1488
- /** Memory usage in MB */
1489
- mem: number;
1490
- }
1491
- /**
1492
- * Keyboard event payload.
1493
- * Key events forwarded from the game.
1494
- */
1495
- interface KeyEventPayload {
1496
- /** Key value (e.g., 'Escape', 'F1') */
1497
- key: string;
1498
- /** Key code (optional) */
1499
- code?: string;
1500
- /** Event type */
1501
- type: 'keydown' | 'keyup';
1502
- }
1503
- /**
1504
- * Connection state payload.
1505
- * Broadcast from platform to games when connection changes.
1506
- */
1507
- interface ConnectionStatePayload {
1508
- state: 'online' | 'offline' | 'degraded';
1509
- reason: string;
1327
+ unlisten<K extends MessageEvents>(type: K, handler: MessageHandler<MessageEventMap[K]>): void;
1328
+ /**
1329
+ * **Get Messaging Context Method**
1330
+ *
1331
+ * Analyzes the current runtime environment and message type to determine the appropriate
1332
+ * transport method and configuration for sending messages.
1333
+ *
1334
+ * **Runtime Environment Detection**:
1335
+ * The method detects whether the code is running in an iframe by comparing:
1336
+ * - `window.self`: Reference to the current window
1337
+ * - `window.top`: Reference to the topmost window in the hierarchy
1338
+ *
1339
+ * If they're different, we're in an iframe. If they're the same, we're in the top-level window.
1340
+ *
1341
+ * **Message Direction Analysis**:
1342
+ * Different message types flow in different directions:
1343
+ * - **Game Parent**: READY, EXIT, TELEMETRY (use postMessage when in iframe)
1344
+ * - **Parent → Game**: INIT, TOKEN_REFRESH, PAUSE, etc. (use CustomEvent in local dev, or postMessage with target)
1345
+ *
1346
+ * **Cross-Context Communication**:
1347
+ * The messaging system supports cross-context targeting through the optional `target` parameter.
1348
+ * When a target window is specified, postMessage is used regardless of the current context.
1349
+ * This enables parent-to-iframe communication through the unified messaging API.
1350
+ *
1351
+ * **Transport Selection Logic**:
1352
+ * - **postMessage**: Used when game is in iframe AND sending to parent, OR when target window is specified
1353
+ * - **CustomEvent**: Used for all other cases (local development, same-context communication)
1354
+ *
1355
+ * **Security Considerations**:
1356
+ * The origin is currently set to '*' for development convenience, but should be
1357
+ * configurable for production security.
1358
+ *
1359
+ * @param eventType - The message event type being sent
1360
+ * @returns Configuration object with transport method and target information
1361
+ *
1362
+ * @example
1363
+ * ```typescript
1364
+ * // In iframe sending READY to parent
1365
+ * const context = getMessagingContext(MessageEvents.READY)
1366
+ * // Returns: { shouldUsePostMessage: true, target: window.parent, origin: '*' }
1367
+ *
1368
+ * // In same context sending INIT
1369
+ * const context = getMessagingContext(MessageEvents.INIT)
1370
+ * // Returns: { shouldUsePostMessage: false, target: undefined, origin: '*' }
1371
+ * ```
1372
+ */
1373
+ private getMessagingContext;
1374
+ /**
1375
+ * **Send Via PostMessage Method**
1376
+ *
1377
+ * Sends a message using the browser's postMessage API for iframe-to-parent communication.
1378
+ * This method is used when the game is running in an iframe and needs to communicate
1379
+ * with the parent window (the Playcademy shell).
1380
+ *
1381
+ * **PostMessage Protocol**:
1382
+ * The postMessage API is the standard way for iframes to communicate with their parent.
1383
+ * It's secure, cross-origin capable, and designed specifically for this use case.
1384
+ *
1385
+ * **Message Structure**:
1386
+ * The method creates a message object with the following structure:
1387
+ * - `type`: The message event type (e.g., 'PLAYCADEMY_READY')
1388
+ * - `payload`: The message data (if any)
1389
+ *
1390
+ * **Payload Handling**:
1391
+ * - **All payloads**: Wrapped in a `payload` property for consistency (e.g., { type, payload: data })
1392
+ * - **Undefined payloads**: Only the type is sent (e.g., { type })
1393
+ *
1394
+ * **Security**:
1395
+ * The origin parameter controls which domains can receive the message.
1396
+ * Currently set to '*' for development, but should be restricted in production.
1397
+ *
1398
+ * @template K - The message event type (ensures type safety)
1399
+ * @param type - The message event type to send
1400
+ * @param payload - The data to send with the message
1401
+ * @param target - The target window (defaults to parent window)
1402
+ * @param origin - The allowed origin for the message (defaults to '*')
1403
+ *
1404
+ * @example
1405
+ * ```typescript
1406
+ * // Send ready signal (no payload)
1407
+ * sendViaPostMessage(MessageEvents.READY, undefined)
1408
+ * // Sends: { type: 'PLAYCADEMY_READY' }
1409
+ *
1410
+ * // Send telemetry data (object payload)
1411
+ * sendViaPostMessage(MessageEvents.TELEMETRY, { fps: 60, mem: 128 })
1412
+ * // Sends: { type: 'PLAYCADEMY_TELEMETRY', payload: { fps: 60, mem: 128 } }
1413
+ *
1414
+ * // Send overlay state (primitive payload)
1415
+ * sendViaPostMessage(MessageEvents.OVERLAY, true)
1416
+ * // Sends: { type: 'PLAYCADEMY_OVERLAY', payload: true }
1417
+ * ```
1418
+ */
1419
+ private sendViaPostMessage;
1420
+ /**
1421
+ * **Send Via CustomEvent Method**
1422
+ *
1423
+ * Sends a message using the browser's CustomEvent API for local same-context communication.
1424
+ * This method is used when both the sender and receiver are in the same window context,
1425
+ * typically during local development or when the parent needs to send messages to the game.
1426
+ *
1427
+ * **CustomEvent Protocol**:
1428
+ * CustomEvent is a browser API for creating and dispatching custom events within the same
1429
+ * window context. It's simpler than postMessage but only works within the same origin/context.
1430
+ *
1431
+ * **Event Structure**:
1432
+ * - **type**: The event name (e.g., 'PLAYCADEMY_INIT')
1433
+ * - **detail**: The payload data attached to the event
1434
+ *
1435
+ * **When This Is Used**:
1436
+ * - Local development when game and shell run in the same context
1437
+ * - Parent-to-game communication (INIT, TOKEN_REFRESH, PAUSE, etc.)
1438
+ * - Any scenario where postMessage isn't needed
1439
+ *
1440
+ * **Event Bubbling**:
1441
+ * CustomEvents are dispatched on the window object and can be listened to by any
1442
+ * code in the same context using `addEventListener(type, handler)`.
1443
+ *
1444
+ * @template K - The message event type (ensures type safety)
1445
+ * @param type - The message event type to send (becomes the event name)
1446
+ * @param payload - The data to send with the message (stored in event.detail)
1447
+ *
1448
+ * @example
1449
+ * ```typescript
1450
+ * // Send initialization data
1451
+ * sendViaCustomEvent(MessageEvents.INIT, {
1452
+ * baseUrl: '/api',
1453
+ * token: 'abc123',
1454
+ * gameId: 'game-456'
1455
+ * })
1456
+ * // Creates: CustomEvent('PLAYCADEMY_INIT', { detail: { baseUrl, token, gameId } })
1457
+ *
1458
+ * // Send pause signal
1459
+ * sendViaCustomEvent(MessageEvents.PAUSE, undefined)
1460
+ * // Creates: CustomEvent('PLAYCADEMY_PAUSE', { detail: undefined })
1461
+ *
1462
+ * // Listeners can access the data via event.detail:
1463
+ * window.addEventListener('PLAYCADEMY_INIT', (event) => {
1464
+ * console.log(event.detail.gameId) // 'game-456'
1465
+ * })
1466
+ * ```
1467
+ */
1468
+ private sendViaCustomEvent;
1510
1469
  }
1511
-
1512
1470
  /**
1513
- * SDK-specific API response types
1471
+ * **Playcademy Messaging Singleton**
1472
+ *
1473
+ * This is the main messaging instance used throughout the Playcademy platform.
1474
+ * It's exported as a singleton to ensure consistent communication across all parts
1475
+ * of the application.
1476
+ *
1477
+ * **Why a Singleton?**:
1478
+ * - Ensures all parts of the app use the same messaging instance
1479
+ * - Prevents conflicts between multiple messaging systems
1480
+ * - Simplifies the API - no need to pass instances around
1481
+ * - Maintains consistent event listener management
1482
+ *
1483
+ * **Usage in Different Contexts**:
1484
+ *
1485
+ * **In Games**:
1486
+ * ```typescript
1487
+ * import { messaging, MessageEvents } from '@playcademy/sdk'
1488
+ *
1489
+ * // Tell parent we're ready
1490
+ * messaging.send(MessageEvents.READY, undefined)
1491
+ *
1492
+ * // Listen for pause/resume
1493
+ * messaging.listen(MessageEvents.PAUSE, () => game.pause())
1494
+ * messaging.listen(MessageEvents.RESUME, () => game.resume())
1495
+ * ```
1496
+ *
1497
+ * **In Parent Shell**:
1498
+ * ```typescript
1499
+ * import { messaging, MessageEvents } from '@playcademy/sdk'
1500
+ *
1501
+ * // Send initialization data to game
1502
+ * messaging.send(MessageEvents.INIT, { baseUrl, token, gameId })
1503
+ *
1504
+ * // Listen for game events
1505
+ * messaging.listen(MessageEvents.EXIT, () => closeGame())
1506
+ * messaging.listen(MessageEvents.READY, () => showGame())
1507
+ * ```
1508
+ *
1509
+ * **Automatic Transport Selection**:
1510
+ * The messaging system automatically chooses the right transport method:
1511
+ * - Uses postMessage when game is in iframe sending to parent
1512
+ * - Uses CustomEvent for local development and parent-to-game communication
1513
+ *
1514
+ * **Type Safety**:
1515
+ * All message sending and receiving is fully type-safe with TypeScript.
1514
1516
  */
1515
- interface LoginResponse {
1516
- token: string;
1517
- }
1518
- interface GameTokenResponse {
1519
- token: string;
1520
- exp: number;
1521
- }
1522
- interface InventoryMutationResponse {
1523
- newTotal: number;
1524
- }
1517
+ declare const messaging: PlaycademyMessaging;
1525
1518
 
1526
1519
  /**
1527
- * Realtime namespace types
1528
- */
1529
- /**
1530
- * Response type for the realtime token API
1520
+ * @fileoverview Authentication Strategy Pattern
1521
+ *
1522
+ * Provides different authentication strategies for the Playcademy SDK.
1523
+ * Each strategy knows how to add its authentication headers to requests.
1531
1524
  */
1532
- interface RealtimeTokenResponse {
1533
- token: string;
1534
- }
1535
1525
 
1536
1526
  /**
1537
- * Scores namespace types
1527
+ * Base interface for authentication strategies
1538
1528
  */
1539
- interface ScoreSubmission {
1540
- id: string;
1541
- score: number;
1542
- achievedAt: Date;
1529
+ interface AuthStrategy {
1530
+ /** Get the token value */
1531
+ getToken(): string | null;
1532
+ /** Get the token type */
1533
+ getType(): TokenType;
1534
+ /** Get authentication headers for a request */
1535
+ getHeaders(): Record<string, string>;
1543
1536
  }
1544
1537
 
1545
1538
  /**
1546
- * Authentication namespace types
1539
+ * Base Playcademy SDK client with shared infrastructure.
1540
+ * Provides authentication, HTTP requests, events, connection monitoring,
1541
+ * and fundamental namespaces used by all clients.
1542
+ *
1543
+ * Extended by PlaycademyClient (game SDK) and PlaycademyInternalClient (platform SDK).
1547
1544
  */
1548
-
1549
- type AuthProviderType = (typeof AUTH_PROVIDER_IDS)[keyof typeof AUTH_PROVIDER_IDS];
1550
- interface AuthOptions {
1551
- /** The identity provider to use for authentication */
1552
- provider: AuthProviderType;
1553
- /** The OAuth callback URL where your server handles the callback */
1554
- callbackUrl: string;
1555
- /** Authentication mode - auto detects best option based on context */
1556
- mode?: 'auto' | 'popup' | 'redirect';
1557
- /** Callback for authentication state changes */
1558
- onStateChange?: (state: AuthStateUpdate) => void;
1559
- /** Custom OAuth configuration (for users embedding the SDK outside of the Playcademy platform) */
1560
- oauth?: {
1561
- clientId: string;
1562
- authorizationEndpoint?: string;
1563
- tokenEndpoint?: string;
1564
- scope?: string;
1545
+ declare abstract class PlaycademyBaseClient {
1546
+ baseUrl: string;
1547
+ gameUrl?: string;
1548
+ mode: PlaycademyMode;
1549
+ protected authStrategy: AuthStrategy;
1550
+ protected gameId?: string;
1551
+ protected config: Partial<ClientConfig>;
1552
+ protected listeners: EventListeners;
1553
+ protected internalClientSessionId?: string;
1554
+ protected authContext?: {
1555
+ isInIframe: boolean;
1565
1556
  };
1557
+ protected initPayload?: InitPayload;
1558
+ protected connectionManager?: ConnectionManager;
1559
+ protected launchId?: string;
1566
1560
  /**
1567
- * Optional custom data to encode in OAuth state parameter.
1568
- * By default, the SDK automatically includes playcademy_user_id and game_id.
1569
- * Use this to add additional custom data if needed.
1561
+ * Internal session manager for automatic session lifecycle.
1562
+ * @private
1563
+ * @internal
1570
1564
  */
1571
- stateData?: Record<string, string>;
1572
- }
1573
- interface AuthStateUpdate {
1574
- /** Current status of the authentication flow */
1575
- status: 'opening_popup' | 'exchanging_token' | 'complete' | 'error';
1576
- /** Human-readable message about the current state */
1577
- message: string;
1578
- /** Error details if status is 'error' */
1579
- error?: Error;
1580
- }
1581
- interface AuthResult {
1582
- /** Whether authentication was successful */
1583
- success: boolean;
1584
- /** User information if authentication was successful */
1585
- user?: UserInfo;
1586
- /** Error if authentication failed */
1587
- error?: Error;
1565
+ protected _sessionManager: {
1566
+ startSession: (gameId: string) => Promise<{
1567
+ sessionId: string;
1568
+ }>;
1569
+ endSession: (sessionId: string, gameId: string) => Promise<void>;
1570
+ };
1571
+ constructor(config?: Partial<ClientConfig>);
1572
+ /**
1573
+ * Gets the effective base URL for API requests.
1574
+ */
1575
+ getBaseUrl(): string;
1576
+ /**
1577
+ * Gets the effective game backend URL for integration requests.
1578
+ */
1579
+ protected getGameBackendUrl(): string;
1580
+ /**
1581
+ * Simple ping method for testing connectivity.
1582
+ */
1583
+ ping(): string;
1584
+ /**
1585
+ * Sets the authentication token for API requests.
1586
+ */
1587
+ setToken(token: string | null, tokenType?: TokenType): void;
1588
+ setLaunchId(launchId: string | null | undefined): void;
1589
+ /**
1590
+ * Gets the current token type.
1591
+ */
1592
+ getTokenType(): TokenType;
1593
+ /**
1594
+ * Gets the current authentication token.
1595
+ */
1596
+ getToken(): string | null;
1597
+ /**
1598
+ * Checks if the client has a valid API token.
1599
+ */
1600
+ isAuthenticated(): boolean;
1601
+ /**
1602
+ * Registers a callback to be called when authentication state changes.
1603
+ */
1604
+ onAuthChange(callback: (token: string | null) => void): void;
1605
+ /**
1606
+ * Registers a callback to be called when connection issues are detected.
1607
+ */
1608
+ onDisconnect(callback: (context: DisconnectContext) => void | Promise<void>): () => void;
1609
+ /**
1610
+ * Gets the current connection state.
1611
+ */
1612
+ getConnectionState(): ConnectionState | 'unknown';
1613
+ /**
1614
+ * Manually triggers a connection check immediately.
1615
+ */
1616
+ checkConnection(): Promise<ConnectionState | 'unknown'>;
1617
+ /**
1618
+ * Sets the authentication context for the client.
1619
+ * @internal
1620
+ */
1621
+ _setAuthContext(context: {
1622
+ isInIframe: boolean;
1623
+ }): void;
1624
+ /**
1625
+ * Registers an event listener for client events.
1626
+ */
1627
+ on<E extends keyof ClientEvents>(event: E, callback: (payload: ClientEvents[E]) => void): void;
1628
+ /**
1629
+ * Emits an event to all registered listeners.
1630
+ */
1631
+ protected emit<E extends keyof ClientEvents>(event: E, payload: ClientEvents[E]): void;
1632
+ /**
1633
+ * Makes an authenticated HTTP request to the platform API.
1634
+ */
1635
+ protected request<T>(path: string, method: Method, options?: {
1636
+ body?: unknown;
1637
+ headers?: Record<string, string>;
1638
+ raw?: boolean;
1639
+ retryPolicy?: RetryPolicy;
1640
+ }): Promise<T>;
1641
+ /**
1642
+ * Makes an authenticated HTTP request to the game's backend Worker.
1643
+ */
1644
+ protected requestGameBackend<T>(path: string, method: Method, body?: unknown, headers?: Record<string, string>, options?: {
1645
+ raw?: boolean;
1646
+ retryPolicy?: RetryPolicy;
1647
+ }): Promise<T>;
1648
+ /**
1649
+ * Ensures a gameId is available, throwing an error if not.
1650
+ */
1651
+ protected _ensureGameId(): string;
1652
+ /**
1653
+ * Detects and sets the authentication context (iframe vs standalone).
1654
+ */
1655
+ private _detectAuthContext;
1656
+ /**
1657
+ * Initializes connection monitoring if enabled.
1658
+ */
1659
+ private _initializeConnectionMonitor;
1660
+ private _initializeInternalSession;
1661
+ /**
1662
+ * Current user data and inventory management.
1663
+ * - `me()` - Get authenticated user profile
1664
+ * - `inventory.get()` - List user's items
1665
+ * - `inventory.add(slug, qty)` - Award items to user
1666
+ */
1667
+ users: {
1668
+ me: () => Promise<AuthenticatedUser>;
1669
+ inventory: {
1670
+ get: () => Promise<InventoryItemWithItem[]>;
1671
+ add: (identifier: string, qty: number) => Promise<InventoryMutationResponse>;
1672
+ remove: (identifier: string, qty: number) => Promise<InventoryMutationResponse>;
1673
+ quantity: (identifier: string) => Promise<number>;
1674
+ has: (identifier: string, minQuantity?: number) => Promise<boolean>;
1675
+ };
1676
+ };
1588
1677
  }
1589
1678
 
1590
- type DevUploadEvent = {
1591
- type: 'init';
1592
- } | {
1593
- type: 's3Progress';
1594
- loaded: number;
1595
- total: number;
1596
- percent: number;
1597
- } | {
1598
- type: 'finalizeStart';
1599
- } | {
1600
- type: 'finalizeProgress';
1601
- percent: number;
1602
- currentFileLabel?: string;
1603
- } | {
1604
- type: 'finalizeStatus';
1605
- message: string;
1606
- } | {
1607
- type: 'complete';
1608
- } | {
1609
- type: 'close';
1610
- };
1611
- interface DevUploadHooks {
1612
- onEvent?: (e: DevUploadEvent) => void;
1613
- onClose?: () => void;
1679
+ /**
1680
+ * Options for configuring activity tracking behavior.
1681
+ */
1682
+ interface StartActivityOptions {
1683
+ /**
1684
+ * How long the tab can stay hidden before the timing window resets on return.
1685
+ * Defaults to 10 minutes. Set to `Infinity` to disable.
1686
+ */
1687
+ hiddenTimeoutMs?: number;
1688
+ /**
1689
+ * How often to flush periodic heartbeats with accumulated time data.
1690
+ * Defaults to 15 seconds. Set to `Infinity` to disable the interval;
1691
+ * final unload/endActivity flushes still run.
1692
+ */
1693
+ heartbeatIntervalMs?: number;
1694
+ /**
1695
+ * Stable identifier for this activity run. When provided, it is used on
1696
+ * every heartbeat and on endActivity instead of a freshly-generated UUID.
1697
+ *
1698
+ * Pass the same `runId` across multiple `startActivity()` calls (for
1699
+ * example, after the player closes and reopens a resumable activity) so
1700
+ * downstream systems can correlate related sessions into a single run.
1701
+ *
1702
+ * Must be a UUID (the backend validates it as such) and unique per
1703
+ * logical run. If omitted, the SDK generates a new UUID on each call,
1704
+ * which means every session is treated as its own run.
1705
+ */
1706
+ runId?: string;
1614
1707
  }
1615
1708
 
1616
1709
  /**
1617
- * Connection Manager
1618
- *
1619
- * Manages connection monitoring and integrates it with the Playcademy client.
1620
- * Handles event wiring, state management, and disconnect callbacks.
1621
- *
1622
- * In iframe mode, disables local monitoring and listens to platform connection
1623
- * state broadcasts instead (avoids duplicate heartbeats).
1624
- */
1625
-
1626
- /**
1627
- * Configuration for the ConnectionManager.
1710
+ * Playcademy SDK client for game developers.
1711
+ * Provides namespaced access to platform features for games running inside Cademy.
1628
1712
  */
1629
- interface ConnectionManagerConfig {
1630
- /** Base URL for API requests (used for heartbeat pings) */
1631
- baseUrl: string;
1632
- /** Authentication context (iframe vs standalone) for alert routing */
1633
- authContext?: {
1634
- isInIframe: boolean;
1713
+ declare class PlaycademyClient extends PlaycademyBaseClient {
1714
+ /**
1715
+ * Connect external identity providers to the user's Playcademy account.
1716
+ * - `connect(provider)` - Link Discord, Google, etc. via OAuth popup
1717
+ */
1718
+ identity: {
1719
+ connect: (options: AuthOptions) => Promise<AuthResult>;
1720
+ _getContext: () => {
1721
+ isInIframe: boolean;
1722
+ };
1635
1723
  };
1636
- /** Handler to call when connection issues are detected */
1637
- onDisconnect?: DisconnectHandler;
1638
- /** Callback to emit connection change events to the client */
1639
- onConnectionChange?: (state: ConnectionState, reason: string) => void;
1640
- }
1641
- /**
1642
- * Manages connection monitoring for the Playcademy client.
1643
- *
1644
- * The ConnectionManager serves as an integration layer between the low-level
1645
- * ConnectionMonitor and the PlaycademyClient. It handles:
1646
- * - Event wiring and coordination
1647
- * - Disconnect callbacks with context
1648
- * - Platform-level alert integration
1649
- * - Request success/failure tracking
1650
- *
1651
- * This class is used internally by PlaycademyClient and typically not
1652
- * instantiated directly by game developers.
1653
- *
1654
- * @see {@link ConnectionMonitor} for the underlying monitoring implementation
1655
- * @see {@link PlaycademyClient.onDisconnect} for the public API
1656
- */
1657
- declare class ConnectionManager {
1658
- private monitor?;
1659
- private authContext?;
1660
- private disconnectHandler?;
1661
- private connectionChangeCallback?;
1662
- private currentState;
1663
- private additionalDisconnectHandlers;
1664
1724
  /**
1665
- * Creates a new ConnectionManager instance.
1666
- *
1667
- * @param config - Configuration options for the manager
1668
- *
1669
- * @example
1670
- * ```typescript
1671
- * const manager = new ConnectionManager({
1672
- * baseUrl: 'https://api.playcademy.com',
1673
- * authContext: { isInIframe: false },
1674
- * onDisconnect: (context) => {
1675
- * console.log(`Disconnected: ${context.state}`)
1676
- * },
1677
- * onConnectionChange: (state, reason) => {
1678
- * console.log(`Connection changed: ${state}`)
1679
- * }
1680
- * })
1681
- * ```
1725
+ * Game runtime lifecycle and asset loading.
1726
+ * - `exit()` - Return to Cademy hub
1727
+ * - `getGameToken()` - Get short-lived auth token
1728
+ * - `assets.url()`, `assets.json()`, `assets.fetch()` - Load game assets
1729
+ * - `on('pause')`, `on('resume')` - Handle visibility changes
1682
1730
  */
1683
- constructor(config: ConnectionManagerConfig);
1731
+ runtime: {
1732
+ getGameToken: (gameId: string, options?: {
1733
+ apply?: boolean | undefined;
1734
+ } | undefined) => Promise<GameTokenResponse>;
1735
+ exit: () => Promise<void>;
1736
+ onInit: (handler: (context: GameContextPayload) => void) => void;
1737
+ onTokenRefresh: (handler: (data: {
1738
+ token: string;
1739
+ exp: number;
1740
+ }) => void) => void;
1741
+ onPause: (handler: () => void) => void;
1742
+ onResume: (handler: () => void) => void;
1743
+ onForceExit: (handler: () => void) => void;
1744
+ onOverlay: (handler: (isVisible: boolean) => void) => void;
1745
+ ready: () => void;
1746
+ sendTelemetry: (data: {
1747
+ fps: number;
1748
+ mem: number;
1749
+ }) => void;
1750
+ removeListener: (eventType: MessageEvents, handler: ((context: GameContextPayload) => void) | ((data: {
1751
+ token: string;
1752
+ exp: number;
1753
+ }) => void) | (() => void) | ((isVisible: boolean) => void)) => void;
1754
+ removeAllListeners: () => void;
1755
+ getListenerCounts: () => Record<string, number>;
1756
+ assets: {
1757
+ url(pathOrStrings: string | TemplateStringsArray, ...values: unknown[]): string;
1758
+ fetch: (path: string, options?: RequestInit | undefined) => Promise<Response>;
1759
+ json: <T = unknown>(path: string) => Promise<T>;
1760
+ blob: (path: string) => Promise<Blob>;
1761
+ text: (path: string) => Promise<string>;
1762
+ arrayBuffer: (path: string) => Promise<ArrayBuffer>;
1763
+ };
1764
+ };
1684
1765
  /**
1685
- * Gets the current connection state.
1766
+ * TimeBack integration for activity tracking and user context.
1686
1767
  *
1687
- * @returns The current connection state ('online', 'offline', or 'degraded')
1768
+ * User context (cached from init, refreshable):
1769
+ * - `user.role` - User's role (student, parent, teacher, etc.)
1770
+ * - `user.enrollments` - Courses the player is enrolled in for this game
1771
+ * - `user.organizations` - Schools/districts the player belongs to
1772
+ * - `user.fetch()` - Refresh user context from server
1688
1773
  *
1689
- * @example
1690
- * ```typescript
1691
- * const state = manager.getState()
1692
- * if (state === 'offline') {
1693
- * console.log('No connection')
1694
- * }
1695
- * ```
1774
+ * Activity tracking:
1775
+ * - `startActivity(metadata)` - Begin tracking an activity
1776
+ * - `pauseActivity()` / `resumeActivity()` - Pause/resume timer
1777
+ * - `endActivity(scoreData)` - Submit activity results to TimeBack
1696
1778
  */
1697
- getState(): ConnectionState;
1779
+ timeback: {
1780
+ readonly user: TimebackUser;
1781
+ startActivity: (metadata: ActivityData, options?: StartActivityOptions | undefined) => void;
1782
+ pauseActivity: () => void;
1783
+ resumeActivity: () => void;
1784
+ endActivity: (data: EndActivityScoreData) => Promise<EndActivityResponse>;
1785
+ };
1698
1786
  /**
1699
- * Manually triggers a connection check immediately.
1700
- *
1701
- * Forces a heartbeat ping to verify the current connection status.
1702
- * Useful when you need to check connectivity before a critical operation.
1703
- *
1704
- * In iframe mode, this returns the last known state from platform.
1705
- *
1706
- * @returns Promise resolving to the current connection state
1707
- *
1708
- * @example
1709
- * ```typescript
1710
- * const state = await manager.checkNow()
1711
- * if (state === 'online') {
1712
- * await performCriticalOperation()
1713
- * }
1714
- * ```
1787
+ * Playcademy Credits (platform currency) management.
1788
+ * - `get()` - Get user's credit balance
1789
+ * - `add(amount)` - Award credits to user
1715
1790
  */
1716
- checkNow(): Promise<ConnectionState>;
1791
+ credits: {
1792
+ balance: () => Promise<number>;
1793
+ add: (amount: number) => Promise<number>;
1794
+ spend: (amount: number) => Promise<number>;
1795
+ };
1717
1796
  /**
1718
- * Reports a successful API request to the connection monitor.
1719
- *
1720
- * This resets the consecutive failure counter and transitions from
1721
- * 'degraded' to 'online' state if applicable.
1722
- *
1723
- * Typically called automatically by the SDK's request wrapper.
1724
- * No-op in iframe mode (platform handles monitoring).
1797
+ * Game score submission and leaderboards.
1798
+ * - `submit(score, metadata?)` - Record a game score
1725
1799
  */
1726
- reportRequestSuccess(): void;
1800
+ scores: {
1801
+ submit: (score: number, metadata?: Record<string, unknown> | undefined) => Promise<ScoreSubmission>;
1802
+ };
1727
1803
  /**
1728
- * Reports a failed API request to the connection monitor.
1729
- *
1730
- * Only network errors are tracked (not 4xx/5xx HTTP responses).
1731
- * After consecutive failures exceed the threshold, the state transitions
1732
- * to 'degraded' or 'offline'.
1733
- *
1734
- * Typically called automatically by the SDK's request wrapper.
1735
- * No-op in iframe mode (platform handles monitoring).
1736
- *
1737
- * @param error - The error from the failed request
1804
+ * Read-only leaderboard access for the current game scope.
1805
+ * - `fetch(options?)` - Fetch leaderboard entries
1738
1806
  */
1739
- reportRequestFailure(error: unknown): void;
1807
+ leaderboard: {
1808
+ fetch: (options?: LeaderboardOptions | undefined) => Promise<GameLeaderboardEntry[]>;
1809
+ };
1810
+ /**
1811
+ * Demo-mode helpers. Methods throw when called outside `client.mode === 'demo'`,
1812
+ * so callers should gate on the mode before reaching in.
1813
+ * - `profile.get()` - Read the anonymous demo player's profile
1814
+ * - `profile.update(updates)` - Update the demo player's profile (today: the required `displayName`)
1815
+ * - `end(score, options?)` - Signal to the parent shell that the demo has ended
1816
+ */
1817
+ demo: {
1818
+ profile: {
1819
+ get: () => Promise<DemoProfile>;
1820
+ update: (updates: DemoProfileUpdate) => Promise<DemoProfile>;
1821
+ };
1822
+ end: (score: number, options?: DemoEndOptions | undefined) => void;
1823
+ };
1824
+ /**
1825
+ * Realtime multiplayer authentication.
1826
+ * - `getToken()` - Get token for WebSocket/realtime connections
1827
+ */
1828
+ realtime: {
1829
+ token: {
1830
+ get: () => Promise<RealtimeTokenResponse>;
1831
+ };
1832
+ };
1833
+ /**
1834
+ * Make requests to your game's custom backend API routes.
1835
+ * - `get(path)`, `post(path, body)`, `put()`, `delete()` - HTTP methods
1836
+ * - Routes are relative to your game's deployment (e.g., '/hello' → your-game.playcademy.gg/api/hello)
1837
+ */
1838
+ backend: {
1839
+ get<T = unknown>(path: string, headers?: Record<string, string> | undefined): Promise<T>;
1840
+ post<T = unknown>(path: string, body?: unknown, headers?: Record<string, string> | undefined): Promise<T>;
1841
+ put<T = unknown>(path: string, body?: unknown, headers?: Record<string, string> | undefined): Promise<T>;
1842
+ patch<T = unknown>(path: string, body?: unknown, headers?: Record<string, string> | undefined): Promise<T>;
1843
+ delete<T = unknown>(path: string, headers?: Record<string, string> | undefined): Promise<T>;
1844
+ request<T = unknown>(path: string, method: Method, body?: unknown, headers?: Record<string, string> | undefined): Promise<T>;
1845
+ download(path: string, method?: Method, body?: unknown, headers?: Record<string, string> | undefined): Promise<Response>;
1846
+ url(pathOrStrings: string | TemplateStringsArray, ...values: unknown[]): string;
1847
+ };
1848
+ /** Auto-initializes a PlaycademyClient with context from the environment */
1849
+ static init: typeof init;
1850
+ /** Authenticates a user with email and password */
1851
+ static login: typeof login;
1852
+ /** Static identity utilities for OAuth operations */
1853
+ static identity: {
1854
+ parseOAuthState: typeof parseOAuthState;
1855
+ };
1856
+ }
1857
+
1858
+ /**
1859
+ * Type definitions for the game timeback namespace.
1860
+ *
1861
+ * SDK-specific types like TimebackInitContext that wrap the core types
1862
+ * from @playcademy/types/user (which are re-exported via types/data.ts).
1863
+ */
1864
+
1865
+ /**
1866
+ * A TimeBack enrollment for the current game session.
1867
+ * Alias for UserEnrollment without the optional gameId.
1868
+ */
1869
+ type TimebackEnrollment = Omit<UserEnrollment, 'gameId'>;
1870
+ /**
1871
+ * A TimeBack organization (school/district) for the current user.
1872
+ * Alias for UserOrganization.
1873
+ */
1874
+ type TimebackOrganization = UserOrganization;
1875
+ /**
1876
+ * TimeBack context passed during game initialization.
1877
+ * This is sent from the platform (cademy) to the game iframe via postMessage.
1878
+ */
1879
+ interface TimebackInitContext {
1880
+ /** User's TimeBack ID */
1881
+ id: string;
1882
+ /** User's role in TimeBack (student, parent, teacher, etc.) */
1883
+ role: TimebackUserRole;
1884
+ /** User's enrollments for this game (one per grade/subject combo) */
1885
+ enrollments: TimebackEnrollment[];
1886
+ /** User's organizations (schools/districts) */
1887
+ organizations: TimebackOrganization[];
1888
+ }
1889
+ /**
1890
+ * TimeBack user context with current data (may be stale from init).
1891
+ * Use `fetch()` to get fresh data from the server.
1892
+ */
1893
+ interface TimebackUserContext {
1894
+ /** User's TimeBack ID */
1895
+ id: string | undefined;
1896
+ /** User's role in TimeBack (student, parent, teacher, etc.) */
1897
+ role: TimebackUserRole | undefined;
1898
+ /** User's enrollments for this game */
1899
+ enrollments: TimebackEnrollment[];
1900
+ /** User's organizations (schools/districts) */
1901
+ organizations: TimebackOrganization[];
1902
+ }
1903
+ /**
1904
+ * XP data access for the current user.
1905
+ * Results are cached for 5 seconds to avoid redundant network requests.
1906
+ */
1907
+ interface TimebackUserXp {
1740
1908
  /**
1741
- * Registers a callback to be called when connection issues are detected.
1742
- *
1743
- * The callback only fires for 'offline' and 'degraded' states, not when
1744
- * recovering to 'online'. This provides a clean API for games to handle
1745
- * disconnect scenarios without being notified of every state change.
1746
- *
1747
- * Works in both iframe and standalone modes transparently.
1909
+ * Fetch XP data from the server.
1910
+ * Returns XP for all courses in this game, or filter by grade/subject.
1911
+ * Results are cached for 5 seconds (use `force: true` to bypass).
1748
1912
  *
1749
- * @param callback - Function to call when connection degrades
1750
- * @returns Cleanup function to unregister the callback
1913
+ * @param options - Query options
1914
+ * @param options.grade - Grade level to filter (must be used with subject)
1915
+ * @param options.subject - Subject to filter (must be used with grade)
1916
+ * @param options.include - Additional data to include: 'perCourse', 'today'
1917
+ * @param options.force - Bypass cache and fetch fresh data (default: false)
1918
+ * @returns Promise resolving to XP data
1751
1919
  *
1752
1920
  * @example
1753
1921
  * ```typescript
1754
- * const cleanup = manager.onDisconnect(({ state, reason, displayAlert }) => {
1755
- * if (state === 'offline') {
1756
- * displayAlert?.('Connection lost. Saving your progress...', { type: 'error' })
1757
- * saveGameState()
1758
- * }
1922
+ * // Get total XP for all game courses
1923
+ * const xp = await client.timeback.user.xp.fetch()
1924
+ *
1925
+ * // Get XP for a specific grade/subject
1926
+ * const xp = await client.timeback.user.xp.fetch({
1927
+ * grade: 3,
1928
+ * subject: 'Math'
1759
1929
  * })
1760
1930
  *
1761
- * // Later: cleanup() to unregister
1762
- * ```
1763
- */
1764
- onDisconnect(callback: DisconnectHandler): () => void;
1765
- /**
1766
- * Stops connection monitoring and performs cleanup.
1931
+ * // Get XP with per-course breakdown
1932
+ * const xp = await client.timeback.user.xp.fetch({
1933
+ * include: ['perCourse', 'today']
1934
+ * })
1767
1935
  *
1768
- * Removes event listeners and clears heartbeat intervals.
1769
- * Should be called when the client is being destroyed.
1936
+ * // Force fresh data
1937
+ * const xp = await client.timeback.user.xp.fetch({ force: true })
1938
+ * ```
1770
1939
  */
1771
- stop(): void;
1940
+ fetch(options?: GetXpOptions): Promise<XpResponse>;
1941
+ }
1942
+ /**
1943
+ * TimeBack user object with both cached getters and fetch method.
1944
+ */
1945
+ interface TimebackUser extends TimebackUserContext {
1772
1946
  /**
1773
- * Sets up listener for platform connection state broadcasts (iframe mode only).
1947
+ * Fetch TimeBack data from the server (cached for 5 min).
1948
+ * Updates the cached values so subsequent property access returns fresh data.
1949
+ * @param options - Cache options (pass { force: true } to bypass cache)
1950
+ * @returns Promise resolving to fresh user context
1774
1951
  */
1775
- private _setupPlatformListener;
1952
+ fetch(options?: {
1953
+ force?: boolean;
1954
+ }): Promise<TimebackUserContext>;
1776
1955
  /**
1777
- * Handles connection state changes from the monitor or platform.
1778
- *
1779
- * Coordinates between:
1780
- * 1. Emitting events to the client (for client.on('connectionChange'))
1781
- * 2. Calling the disconnect handler if configured
1782
- * 3. Calling any additional handlers registered via onDisconnect()
1783
- *
1784
- * @param state - The new connection state
1785
- * @param reason - Human-readable reason for the state change
1956
+ * XP data for the current user.
1957
+ * Call `xp.fetch()` to get XP from the server.
1786
1958
  */
1787
- private _handleConnectionChange;
1959
+ xp: TimebackUserXp;
1960
+ }
1961
+ /**
1962
+ * Options for querying student XP.
1963
+ */
1964
+ interface GetXpOptions {
1965
+ /** Grade level to filter (must be used with subject) */
1966
+ grade?: TimebackGrade;
1967
+ /** Subject to filter (must be used with grade) */
1968
+ subject?: TimebackSubject;
1969
+ /** Additional data to include: 'perCourse', 'today' */
1970
+ include?: ('perCourse' | 'today')[];
1971
+ /** Bypass cache and fetch fresh data (default: false) */
1972
+ force?: boolean;
1973
+ }
1974
+ /**
1975
+ * XP data for a single course.
1976
+ */
1977
+ interface CourseXp {
1978
+ grade: TimebackGrade;
1979
+ subject: TimebackSubject;
1980
+ title: string;
1981
+ totalXp: number;
1982
+ todayXp?: number;
1983
+ }
1984
+ /**
1985
+ * Response from XP query.
1986
+ */
1987
+ interface XpResponse {
1988
+ totalXp: number;
1989
+ todayXp?: number;
1990
+ courses?: CourseXp[];
1991
+ }
1992
+
1993
+ /**
1994
+ * Core client configuration and lifecycle types
1995
+ */
1996
+
1997
+ type TokenType = 'session' | 'apiKey' | 'gameJwt';
1998
+ /**
1999
+ * Runtime mode for a Playcademy game client.
2000
+ *
2001
+ * - `'platform'` — game is embedded in the platform (e.g. the cademy hub)
2002
+ * with a real authenticated user and full access to SDK namespaces.
2003
+ * - `'demo'` — game is embedded in the landing page demo shell with an
2004
+ * anonymous user; platform-only namespaces throw, and `client.demo.*`
2005
+ * is the supported surface for signaling demo lifecycle events and
2006
+ * reading/updating the anonymous profile.
2007
+ * - `'standalone'` — game is running outside any iframe (e.g. `bun run dev`
2008
+ * or direct-deploy preview) with a mock token and no real platform
2009
+ * context. API calls will not succeed; use this to branch UX locally.
2010
+ */
2011
+ type PlaycademyMode = 'platform' | 'demo' | 'standalone';
2012
+ interface ClientConfig {
2013
+ baseUrl: string;
2014
+ gameUrl?: string;
2015
+ token?: string;
2016
+ tokenType?: TokenType;
2017
+ gameId?: string;
2018
+ launchId?: string;
2019
+ autoStartSession?: boolean;
2020
+ onDisconnect?: DisconnectHandler;
2021
+ enableConnectionMonitoring?: boolean;
2022
+ mode?: PlaycademyMode;
2023
+ }
2024
+ /**
2025
+ * Handler called when connection state changes to offline or degraded.
2026
+ * Games can implement this to handle disconnects gracefully.
2027
+ */
2028
+ type DisconnectHandler = (context: DisconnectContext) => void | Promise<void>;
2029
+ /**
2030
+ * Context provided to disconnect handlers
2031
+ */
2032
+ interface DisconnectContext {
2033
+ /** Current connection state */
2034
+ state: 'offline' | 'degraded';
2035
+ /** Reason for the disconnect */
2036
+ reason: string;
2037
+ /** Timestamp when disconnect was detected */
2038
+ timestamp: number;
2039
+ /** Utility to display a platform-level alert */
2040
+ displayAlert: (message: string, options?: {
2041
+ type?: 'info' | 'warning' | 'error';
2042
+ duration?: number;
2043
+ }) => void;
2044
+ }
2045
+ interface InitPayload {
2046
+ /** Hub API base URL */
2047
+ baseUrl: string;
2048
+ /** Game deployment URL (serves both frontend assets and backend API) */
2049
+ gameUrl?: string;
2050
+ /** Short-lived game token */
2051
+ token: string;
2052
+ /** Game ID */
2053
+ gameId: string;
2054
+ /** Realtime WebSocket URL */
2055
+ realtimeUrl?: string;
2056
+ /** Timeback context (if user has a Timeback account) */
2057
+ timeback?: TimebackInitContext;
2058
+ /** Runtime mode for the game client */
2059
+ mode?: PlaycademyMode;
2060
+ }
2061
+ interface GameContextPayload {
2062
+ token: string;
2063
+ baseUrl: string;
2064
+ realtimeUrl: string;
2065
+ gameId: string;
2066
+ forwardKeys?: string[];
2067
+ mode?: PlaycademyMode;
2068
+ }
2069
+ type EventListeners = {
2070
+ [E in keyof ClientEvents]?: ((payload: ClientEvents[E]) => void)[];
2071
+ };
2072
+ interface ClientEvents {
2073
+ authChange: {
2074
+ token: string | null;
2075
+ };
2076
+ inventoryChange: {
2077
+ itemId: string;
2078
+ delta: number;
2079
+ newTotal: number;
2080
+ };
2081
+ levelUp: {
2082
+ oldLevel: number;
2083
+ newLevel: number;
2084
+ creditsAwarded: number;
2085
+ };
2086
+ xpGained: {
2087
+ amount: number;
2088
+ totalXP: number;
2089
+ leveledUp: boolean;
2090
+ };
2091
+ connectionChange: {
2092
+ state: 'online' | 'offline' | 'degraded';
2093
+ reason: string;
2094
+ };
2095
+ }
2096
+
2097
+ /**
2098
+ * Event and message payload types for SDK messaging system
2099
+ */
2100
+
2101
+ interface DisplayAlertPayload {
2102
+ message: string;
2103
+ options?: {
2104
+ type?: 'info' | 'warning' | 'error';
2105
+ duration?: number;
2106
+ };
2107
+ }
2108
+ /**
2109
+ * Authentication state change event payload.
2110
+ * Used when authentication state changes in the application.
2111
+ */
2112
+ interface AuthStateChangePayload {
2113
+ /** Whether the user is currently authenticated */
2114
+ authenticated: boolean;
2115
+ /** User information if authenticated, null otherwise */
2116
+ user: UserInfo | null;
2117
+ /** Error information if authentication failed */
2118
+ error: Error | null;
2119
+ }
2120
+ /**
2121
+ * OAuth callback event payload.
2122
+ * Used when OAuth flow completes in popup/new-tab windows.
2123
+ */
2124
+ interface AuthCallbackPayload {
2125
+ /** OAuth authorization code */
2126
+ code: string;
2127
+ /** OAuth state parameter for CSRF protection */
2128
+ state: string;
2129
+ /** Error message if OAuth flow failed */
2130
+ error: string | null;
2131
+ }
2132
+ /**
2133
+ * Token refresh event payload.
2134
+ * Sent when authentication token is updated.
2135
+ */
2136
+ interface TokenRefreshPayload {
2137
+ /** New authentication token */
2138
+ token: string;
2139
+ /** Token expiration timestamp */
2140
+ exp: number;
1788
2141
  }
1789
-
1790
2142
  /**
1791
- * @fileoverview Playcademy Messaging System
1792
- *
1793
- * This file implements a unified messaging system for the Playcademy platform that handles
1794
- * communication between different contexts:
1795
- *
1796
- * 1. **Iframe-to-Parent Communication**: When games run inside iframes (production/development),
1797
- * they need to communicate with the parent window using postMessage API
1798
- *
1799
- * 2. **Local Communication**: When games run in the same context (local development),
1800
- * they use CustomEvents for internal messaging
2143
+ * Telemetry event payload.
2144
+ * Performance metrics sent from the game.
2145
+ */
2146
+ interface TelemetryPayload {
2147
+ /** Frames per second */
2148
+ fps: number;
2149
+ /** Memory usage in MB */
2150
+ mem: number;
2151
+ }
2152
+ /**
2153
+ * Keyboard event payload.
2154
+ * Key events forwarded from the game.
2155
+ */
2156
+ interface KeyEventPayload {
2157
+ /** Key value (e.g., 'Escape', 'F1') */
2158
+ key: string;
2159
+ /** Key code (optional) */
2160
+ code?: string;
2161
+ /** Event type */
2162
+ type: 'keydown' | 'keyup';
2163
+ }
2164
+ /**
2165
+ * Connection state payload.
2166
+ * Broadcast from platform to games when connection changes.
2167
+ */
2168
+ interface ConnectionStatePayload {
2169
+ state: 'online' | 'offline' | 'degraded';
2170
+ reason: string;
2171
+ }
2172
+ /**
2173
+ * Options for `client.demo.end(score, options?)`.
1801
2174
  *
1802
- * The system automatically detects the runtime environment and chooses the appropriate
1803
- * transport method, abstracting this complexity from the developer.
2175
+ * All optional. The landing-page demo shell can render a meaningful CTA
2176
+ * from `score` alone, but `durationMs` enables a nicer summary and
2177
+ * `metadata` lets games pass any extra context they want to surface.
2178
+ */
2179
+ interface DemoEndOptions {
2180
+ durationMs?: number;
2181
+ metadata?: Record<string, unknown>;
2182
+ }
2183
+ /**
2184
+ * Wire payload for the `PLAYCADEMY_DEMO_END` message.
1804
2185
  *
1805
- * **Architecture Overview**:
1806
- * - Games run in iframes for security and isolation
1807
- * - Parent window (Playcademy shell) manages game lifecycle
1808
- * - Messages flow bidirectionally between parent and iframe
1809
- * - Local development mode simulates this architecture without iframes
2186
+ * `score` is required — the demo shell always needs something to display
2187
+ * when the round ends. `durationMs` and `metadata` are optional.
1810
2188
  */
2189
+ interface DemoEndPayload extends DemoEndOptions {
2190
+ score: number;
2191
+ }
1811
2192
 
1812
2193
  /**
1813
- * Enumeration of all message types used in the Playcademy messaging system.
1814
- *
1815
- * **Message Flow Patterns**:
1816
- *
1817
- * **Parent → Game (Overworld → Game)**:
1818
- * - INIT: Provides game with authentication token and configuration
1819
- * - TOKEN_REFRESH: Updates game's authentication token before expiry
1820
- * - PAUSE/RESUME: Controls game execution state
1821
- * - FORCE_EXIT: Immediately terminates the game
1822
- * - OVERLAY: Shows/hides UI overlays over the game
1823
- *
1824
- * **Game → Parent (Game → Overworld)**:
1825
- * - READY: Game has loaded and is ready to receive messages
1826
- * - EXIT: Game requests to be closed (user clicked exit, game ended, etc.)
1827
- * - TELEMETRY: Game reports performance metrics (FPS, memory usage, etc.)
2194
+ * SDK-specific API response types
1828
2195
  */
1829
- declare enum MessageEvents {
1830
- /**
1831
- * Initializes the game with authentication context and configuration.
1832
- * Sent immediately after game iframe loads.
1833
- * Payload:
1834
- * - `baseUrl`: string
1835
- * - `token`: string
1836
- * - `gameId`: string
1837
- */
1838
- INIT = "PLAYCADEMY_INIT",
1839
- /**
1840
- * Updates the game's authentication token before it expires.
1841
- * Sent periodically to maintain valid authentication.
1842
- * Payload:
1843
- * - `token`: string
1844
- * - `exp`: number
1845
- */
1846
- TOKEN_REFRESH = "PLAYCADEMY_TOKEN_REFRESH",
1847
- /**
1848
- * Pauses game execution (e.g., when user switches tabs).
1849
- * Game should pause timers, animations, and user input.
1850
- * Payload: void
1851
- */
1852
- PAUSE = "PLAYCADEMY_PAUSE",
1853
- /**
1854
- * Resumes game execution after being paused.
1855
- * Game should restore timers, animations, and user input.
1856
- * Payload: void
1857
- */
1858
- RESUME = "PLAYCADEMY_RESUME",
1859
- /**
1860
- * Forces immediate game termination (emergency exit).
1861
- * Game should clean up resources and exit immediately.
1862
- * Payload: void
1863
- */
1864
- FORCE_EXIT = "PLAYCADEMY_FORCE_EXIT",
1865
- /**
1866
- * Shows or hides UI overlays over the game.
1867
- * Game may need to pause or adjust rendering accordingly.
1868
- * Payload: boolean (true = show overlay, false = hide overlay)
1869
- */
1870
- OVERLAY = "PLAYCADEMY_OVERLAY",
1871
- /**
1872
- * Broadcasts connection state changes to games.
1873
- * Sent by platform when network connectivity changes.
1874
- * Payload:
1875
- * - `state`: 'online' | 'offline' | 'degraded'
1876
- * - `reason`: string
1877
- */
1878
- CONNECTION_STATE = "PLAYCADEMY_CONNECTION_STATE",
1879
- /**
1880
- * Game has finished loading and is ready to receive messages.
1881
- * Sent once after game initialization is complete.
1882
- * Payload: void
1883
- */
1884
- READY = "PLAYCADEMY_READY",
1885
- /**
1886
- * Game requests to be closed/exited.
1887
- * Sent when user clicks exit button or game naturally ends.
1888
- * Payload: void
1889
- */
1890
- EXIT = "PLAYCADEMY_EXIT",
1891
- /**
1892
- * Game reports performance telemetry data.
1893
- * Sent periodically for monitoring and analytics.
1894
- * Payload:
1895
- * - `fps`: number
1896
- * - `mem`: number
1897
- */
1898
- TELEMETRY = "PLAYCADEMY_TELEMETRY",
1899
- /**
1900
- * Game reports key events to parent.
1901
- * Sent when certain keys are pressed within the game iframe.
1902
- * Payload:
1903
- * - `key`: string
1904
- * - `code?`: string
1905
- * - `type`: 'keydown' | 'keyup'
1906
- */
1907
- KEY_EVENT = "PLAYCADEMY_KEY_EVENT",
1908
- /**
1909
- * Game requests platform to display an alert.
1910
- * Sent when connection issues are detected or other important events occur.
1911
- * Payload:
1912
- * - `message`: string
1913
- * - `options`: `{ type?: 'info' | 'warning' | 'error', duration?: number }`
1914
- */
1915
- DISPLAY_ALERT = "PLAYCADEMY_DISPLAY_ALERT",
1916
- /**
1917
- * Notifies about authentication state changes.
1918
- * Can be sent in both directions depending on auth flow.
1919
- * Payload:
1920
- * - `authenticated`: boolean
1921
- * - `user`: UserInfo | null
1922
- * - `error`: Error | null
1923
- */
1924
- AUTH_STATE_CHANGE = "PLAYCADEMY_AUTH_STATE_CHANGE",
2196
+ interface LoginResponse {
2197
+ token: string;
2198
+ }
2199
+ interface GameTokenResponse {
2200
+ token: string;
2201
+ exp: number;
2202
+ }
2203
+ interface InventoryMutationResponse {
2204
+ newTotal: number;
2205
+ }
2206
+
2207
+ /**
2208
+ * Realtime namespace types
2209
+ */
2210
+ /**
2211
+ * Response type for the realtime token API
2212
+ */
2213
+ interface RealtimeTokenResponse {
2214
+ token: string;
2215
+ }
2216
+
2217
+ /**
2218
+ * Scores namespace types
2219
+ */
2220
+ interface ScoreSubmission {
2221
+ id: string;
2222
+ score: number;
2223
+ achievedAt: Date;
2224
+ }
2225
+
2226
+ /**
2227
+ * Authentication namespace types
2228
+ */
2229
+
2230
+ type AuthProviderType = (typeof AUTH_PROVIDER_IDS)[keyof typeof AUTH_PROVIDER_IDS];
2231
+ interface AuthOptions {
2232
+ /** The identity provider to use for authentication */
2233
+ provider: AuthProviderType;
2234
+ /** The OAuth callback URL where your server handles the callback */
2235
+ callbackUrl: string;
2236
+ /** Authentication mode - auto detects best option based on context */
2237
+ mode?: 'auto' | 'popup' | 'redirect';
2238
+ /** Callback for authentication state changes */
2239
+ onStateChange?: (state: AuthStateUpdate) => void;
2240
+ /** Custom OAuth configuration (for users embedding the SDK outside of the Playcademy platform) */
2241
+ oauth?: {
2242
+ clientId: string;
2243
+ authorizationEndpoint?: string;
2244
+ tokenEndpoint?: string;
2245
+ scope?: string;
2246
+ };
1925
2247
  /**
1926
- * OAuth callback data from popup/new-tab windows.
1927
- * Sent from popup window back to parent after OAuth completes.
1928
- * Payload:
1929
- * - `code`: string (OAuth authorization code)
1930
- * - `state`: string (OAuth state for CSRF protection)
1931
- * - `error`: string | null (OAuth error if any)
2248
+ * Optional custom data to encode in OAuth state parameter.
2249
+ * By default, the SDK automatically includes playcademy_user_id and game_id.
2250
+ * Use this to add additional custom data if needed.
1932
2251
  */
1933
- AUTH_CALLBACK = "PLAYCADEMY_AUTH_CALLBACK"
2252
+ stateData?: Record<string, string>;
2253
+ }
2254
+ interface AuthStateUpdate {
2255
+ /** Current status of the authentication flow */
2256
+ status: 'opening_popup' | 'exchanging_token' | 'complete' | 'error';
2257
+ /** Human-readable message about the current state */
2258
+ message: string;
2259
+ /** Error details if status is 'error' */
2260
+ error?: Error;
2261
+ }
2262
+ interface AuthResult {
2263
+ /** Whether authentication was successful */
2264
+ success: boolean;
2265
+ /** User information if authentication was successful */
2266
+ user?: UserInfo;
2267
+ /** Error if authentication failed */
2268
+ error?: Error;
2269
+ }
2270
+
2271
+ type DevUploadEvent = {
2272
+ type: 'init';
2273
+ } | {
2274
+ type: 's3Progress';
2275
+ loaded: number;
2276
+ total: number;
2277
+ percent: number;
2278
+ } | {
2279
+ type: 'finalizeStart';
2280
+ } | {
2281
+ type: 'finalizeProgress';
2282
+ percent: number;
2283
+ currentFileLabel?: string;
2284
+ } | {
2285
+ type: 'finalizeStatus';
2286
+ message: string;
2287
+ } | {
2288
+ type: 'complete';
2289
+ } | {
2290
+ type: 'close';
2291
+ };
2292
+ interface DevUploadHooks {
2293
+ onEvent?: (e: DevUploadEvent) => void;
2294
+ onClose?: () => void;
1934
2295
  }
2296
+
1935
2297
  /**
1936
- * Type definition for message handler functions.
1937
- * These functions are called when a specific message type is received.
2298
+ * Connection Manager
1938
2299
  *
1939
- * @template T - The type of payload data the handler expects
1940
- * @param payload - The data sent with the message
2300
+ * Manages connection monitoring and integrates it with the Playcademy client.
2301
+ * Handles event wiring, state management, and disconnect callbacks.
2302
+ *
2303
+ * In iframe mode, disables local monitoring and listens to platform connection
2304
+ * state broadcasts instead (avoids duplicate heartbeats).
1941
2305
  */
1942
- type MessageHandler<T = unknown> = (payload: T) => void;
2306
+
1943
2307
  /**
1944
- * Type mapping that defines the payload structure for each message type.
1945
- * This ensures type safety when sending and receiving messages.
1946
- *
1947
- * **Usage Examples**:
1948
- * ```typescript
1949
- * // Type-safe message sending
1950
- * messaging.send(MessageEvents.INIT, { baseUrl: '/api', token: 'abc', gameId: '123' })
1951
- *
1952
- * // Type-safe message handling
1953
- * messaging.listen(MessageEvents.TOKEN_REFRESH, ({ token, exp }) => {
1954
- * console.log(`New token expires at: ${new Date(exp)}`)
1955
- * })
1956
- * ```
2308
+ * Configuration for the ConnectionManager.
1957
2309
  */
1958
- interface MessageEventMap {
1959
- /** Game initialization context with API endpoint, auth token, and game ID */
1960
- [MessageEvents.INIT]: GameContextPayload;
1961
- /** Token refresh data with new token and expiration timestamp */
1962
- [MessageEvents.TOKEN_REFRESH]: TokenRefreshPayload;
1963
- /** Pause message has no payload data */
1964
- [MessageEvents.PAUSE]: void;
1965
- /** Resume message has no payload data */
1966
- [MessageEvents.RESUME]: void;
1967
- /** Force exit message has no payload data */
1968
- [MessageEvents.FORCE_EXIT]: void;
1969
- /** Overlay visibility state (true = show, false = hide) */
1970
- [MessageEvents.OVERLAY]: boolean;
1971
- /** Connection state change from platform */
1972
- [MessageEvents.CONNECTION_STATE]: ConnectionStatePayload;
1973
- /** Ready message has no payload data */
1974
- [MessageEvents.READY]: void;
1975
- /** Exit message has no payload data */
1976
- [MessageEvents.EXIT]: void;
1977
- /** Performance telemetry data */
1978
- [MessageEvents.TELEMETRY]: TelemetryPayload;
1979
- /** Key event data */
1980
- [MessageEvents.KEY_EVENT]: KeyEventPayload;
1981
- /** Display alert request from game */
1982
- [MessageEvents.DISPLAY_ALERT]: DisplayAlertPayload;
1983
- /** Authentication state change notification */
1984
- [MessageEvents.AUTH_STATE_CHANGE]: AuthStateChangePayload;
1985
- /** OAuth callback data from popup/new-tab windows */
1986
- [MessageEvents.AUTH_CALLBACK]: AuthCallbackPayload;
2310
+ interface ConnectionManagerConfig {
2311
+ /** Base URL for API requests (used for heartbeat pings) */
2312
+ baseUrl: string;
2313
+ /** Authentication context (iframe vs standalone) for alert routing */
2314
+ authContext?: {
2315
+ isInIframe: boolean;
2316
+ };
2317
+ /** Handler to call when connection issues are detected */
2318
+ onDisconnect?: DisconnectHandler;
2319
+ /** Callback to emit connection change events to the client */
2320
+ onConnectionChange?: (state: ConnectionState, reason: string) => void;
1987
2321
  }
1988
2322
  /**
1989
- * **PlaycademyMessaging Class**
1990
- *
1991
- * This is the core messaging system that handles all communication in the Playcademy platform.
1992
- * It automatically detects the runtime environment and chooses the appropriate transport method.
1993
- *
1994
- * **Key Features**:
1995
- * 1. **Automatic Transport Selection**: Detects iframe vs local context and uses appropriate method
1996
- * 2. **Type Safety**: Full TypeScript support with payload type checking
1997
- * 3. **Bidirectional Communication**: Handles both parent→game and game→parent messages
1998
- * 4. **Event Cleanup**: Proper listener management to prevent memory leaks
1999
- *
2000
- * **Transport Methods**:
2001
- * - **postMessage**: Used for iframe-to-parent communication (production/development)
2002
- * - **CustomEvent**: Used for local same-context communication (local development)
2003
- *
2004
- * **Runtime Detection Logic**:
2005
- * - If `window.self !== window.top`: We're in an iframe, use postMessage
2006
- * - If `window.self === window.top`: We're in the same context, use CustomEvent
2323
+ * Manages connection monitoring for the Playcademy client.
2007
2324
  *
2008
- * **Example Usage**:
2009
- * ```typescript
2010
- * // Send a message (automatically chooses transport)
2011
- * messaging.send(MessageEvents.READY, undefined)
2325
+ * The ConnectionManager serves as an integration layer between the low-level
2326
+ * ConnectionMonitor and the PlaycademyClient. It handles:
2327
+ * - Event wiring and coordination
2328
+ * - Disconnect callbacks with context
2329
+ * - Platform-level alert integration
2330
+ * - Request success/failure tracking
2012
2331
  *
2013
- * // Listen for messages (handles both transports)
2014
- * messaging.listen(MessageEvents.INIT, (payload) => {
2015
- * console.log('Game initialized with:', payload)
2016
- * })
2332
+ * This class is used internally by PlaycademyClient and typically not
2333
+ * instantiated directly by game developers.
2017
2334
  *
2018
- * // Clean up listeners
2019
- * messaging.unlisten(MessageEvents.INIT, handler)
2020
- * ```
2335
+ * @see {@link ConnectionMonitor} for the underlying monitoring implementation
2336
+ * @see {@link PlaycademyClient.onDisconnect} for the public API
2021
2337
  */
2022
- declare class PlaycademyMessaging {
2023
- /**
2024
- * Internal storage for message listeners.
2025
- *
2026
- * **Structure Explanation**:
2027
- * - Outer Map: MessageEvents → Map of handlers for that event type
2028
- * - Inner Map: MessageHandler → Object containing both listener types
2029
- * - Object: { postMessage: EventListener, customEvent: EventListener }
2030
- *
2031
- * **Why Two Listeners Per Handler?**:
2032
- * Since we don't know at registration time which transport will be used,
2033
- * we register both a postMessage listener and a CustomEvent listener.
2034
- * The appropriate one will be triggered based on the runtime context.
2035
- */
2036
- private listeners;
2037
- /**
2038
- * **Send Message Method**
2039
- *
2040
- * Sends a message using the appropriate transport method based on the runtime context.
2041
- * This is the main public API for sending messages in the Playcademy system.
2042
- *
2043
- * **How It Works**:
2044
- * 1. Analyzes the message type and current runtime context
2045
- * 2. Determines if we're in an iframe and if this message should use postMessage
2046
- * 3. Routes to the appropriate transport method (postMessage or CustomEvent)
2047
- *
2048
- * **Transport Selection Logic**:
2049
- * - **postMessage**: Used when game is in iframe and sending to parent, OR when target window is specified
2050
- * - **CustomEvent**: Used for local/same-context communication
2051
- *
2052
- * **Type Safety**:
2053
- * The generic type parameter `K` ensures that the payload type matches
2054
- * the expected type for the given message event.
2055
- *
2056
- * @template K - The message event type (ensures type safety)
2057
- * @param type - The message event type to send
2058
- * @param payload - The data to send with the message (type-checked)
2059
- * @param options - Optional configuration for message sending
2060
- *
2061
- * @example
2062
- * ```typescript
2063
- * // Send game ready signal (no payload)
2064
- * messaging.send(MessageEvents.READY, undefined)
2065
- *
2066
- * // Send telemetry data (typed payload)
2067
- * messaging.send(MessageEvents.TELEMETRY, { fps: 60, mem: 128 })
2068
- *
2069
- * // Send to specific iframe window (parent to iframe communication)
2070
- * messaging.send(MessageEvents.INIT, { baseUrl, token, gameId }, {
2071
- * target: iframe.contentWindow,
2072
- * origin: '*'
2073
- * })
2074
- *
2075
- * // TypeScript will error if payload type is wrong
2076
- * messaging.send(MessageEvents.INIT, "wrong type") // ❌ Error
2077
- * ```
2078
- */
2079
- send<K extends MessageEvents>(type: K, payload: MessageEventMap[K], options?: {
2080
- target?: Window;
2081
- origin?: string;
2082
- }): void;
2083
- /**
2084
- * **Listen for Messages Method**
2085
- *
2086
- * Registers a message listener that will be called when the specified message type is received.
2087
- * This method automatically handles both postMessage and CustomEvent sources.
2088
- *
2089
- * **Why Register Two Listeners?**:
2090
- * Since we don't know at registration time which transport method will be used to send
2091
- * messages, we register listeners for both possible sources:
2092
- * 1. **postMessage listener**: Handles messages from iframe-to-parent communication
2093
- * 2. **CustomEvent listener**: Handles messages from local same-context communication
2094
- *
2095
- * **Message Processing**:
2096
- * - **postMessage**: Extracts data from `event.data.payload` or `event.data`
2097
- * - **CustomEvent**: Extracts data from `event.detail`
2098
- *
2099
- * **Type Safety**:
2100
- * The handler function receives the correctly typed payload based on the message type.
2101
- *
2102
- * @template K - The message event type (ensures type safety)
2103
- * @param type - The message event type to listen for
2104
- * @param handler - Function to call when the message is received
2105
- *
2106
- * @example
2107
- * ```typescript
2108
- * // Listen for game initialization
2109
- * messaging.listen(MessageEvents.INIT, (payload) => {
2110
- * // payload is automatically typed as GameContextPayload
2111
- * console.log(`Game ${payload.gameId} initialized`)
2112
- * console.log(`API base URL: ${payload.baseUrl}`)
2113
- * })
2114
- *
2115
- * // Listen for token refresh
2116
- * messaging.listen(MessageEvents.TOKEN_REFRESH, ({ token, exp }) => {
2117
- * // payload is automatically typed as { token: string; exp: number }
2118
- * updateAuthToken(token)
2119
- * scheduleTokenRefresh(exp)
2120
- * })
2121
- * ```
2122
- */
2123
- listen<K extends MessageEvents>(type: K, handler: MessageHandler<MessageEventMap[K]>): void;
2338
+ declare class ConnectionManager {
2339
+ private monitor?;
2340
+ private authContext?;
2341
+ private disconnectHandler?;
2342
+ private connectionChangeCallback?;
2343
+ private currentState;
2344
+ private additionalDisconnectHandlers;
2124
2345
  /**
2125
- * **Remove Message Listener Method**
2126
- *
2127
- * Removes a previously registered message listener to prevent memory leaks and unwanted callbacks.
2128
- * This method cleans up both the postMessage and CustomEvent listeners that were registered.
2129
- *
2130
- * **Why Clean Up Both Listeners?**:
2131
- * When we registered the listener with `listen()`, we created two browser event listeners:
2132
- * 1. A 'message' event listener for postMessage communication
2133
- * 2. A custom event listener for local CustomEvent communication
2134
- *
2135
- * Both must be removed to prevent memory leaks and ensure the handler is completely unregistered.
2136
- *
2137
- * **Memory Management**:
2138
- * - Removes both event listeners from the browser
2139
- * - Cleans up internal tracking maps
2140
- * - If no more handlers exist for a message type, removes the entire type entry
2141
- *
2142
- * **Safe to Call Multiple Times**:
2143
- * This method is idempotent - calling it multiple times with the same handler is safe.
2346
+ * Creates a new ConnectionManager instance.
2144
2347
  *
2145
- * @template K - The message event type (ensures type safety)
2146
- * @param type - The message event type to stop listening for
2147
- * @param handler - The exact handler function that was passed to listen()
2348
+ * @param config - Configuration options for the manager
2148
2349
  *
2149
2350
  * @example
2150
2351
  * ```typescript
2151
- * // Register a handler
2152
- * const handleInit = (payload) => console.log('Game initialized')
2153
- * messaging.listen(MessageEvents.INIT, handleInit)
2154
- *
2155
- * // Later, remove the handler
2156
- * messaging.unlisten(MessageEvents.INIT, handleInit)
2157
- *
2158
- * // Safe to call multiple times
2159
- * messaging.unlisten(MessageEvents.INIT, handleInit) // No error
2352
+ * const manager = new ConnectionManager({
2353
+ * baseUrl: 'https://api.playcademy.com',
2354
+ * authContext: { isInIframe: false },
2355
+ * onDisconnect: (context) => {
2356
+ * console.log(`Disconnected: ${context.state}`)
2357
+ * },
2358
+ * onConnectionChange: (state, reason) => {
2359
+ * console.log(`Connection changed: ${state}`)
2360
+ * }
2361
+ * })
2160
2362
  * ```
2161
2363
  */
2162
- unlisten<K extends MessageEvents>(type: K, handler: MessageHandler<MessageEventMap[K]>): void;
2364
+ constructor(config: ConnectionManagerConfig);
2163
2365
  /**
2164
- * **Get Messaging Context Method**
2165
- *
2166
- * Analyzes the current runtime environment and message type to determine the appropriate
2167
- * transport method and configuration for sending messages.
2168
- *
2169
- * **Runtime Environment Detection**:
2170
- * The method detects whether the code is running in an iframe by comparing:
2171
- * - `window.self`: Reference to the current window
2172
- * - `window.top`: Reference to the topmost window in the hierarchy
2173
- *
2174
- * If they're different, we're in an iframe. If they're the same, we're in the top-level window.
2175
- *
2176
- * **Message Direction Analysis**:
2177
- * Different message types flow in different directions:
2178
- * - **Game → Parent**: READY, EXIT, TELEMETRY (use postMessage when in iframe)
2179
- * - **Parent → Game**: INIT, TOKEN_REFRESH, PAUSE, etc. (use CustomEvent in local dev, or postMessage with target)
2180
- *
2181
- * **Cross-Context Communication**:
2182
- * The messaging system supports cross-context targeting through the optional `target` parameter.
2183
- * When a target window is specified, postMessage is used regardless of the current context.
2184
- * This enables parent-to-iframe communication through the unified messaging API.
2185
- *
2186
- * **Transport Selection Logic**:
2187
- * - **postMessage**: Used when game is in iframe AND sending to parent, OR when target window is specified
2188
- * - **CustomEvent**: Used for all other cases (local development, same-context communication)
2189
- *
2190
- * **Security Considerations**:
2191
- * The origin is currently set to '*' for development convenience, but should be
2192
- * configurable for production security.
2366
+ * Gets the current connection state.
2193
2367
  *
2194
- * @param eventType - The message event type being sent
2195
- * @returns Configuration object with transport method and target information
2368
+ * @returns The current connection state ('online', 'offline', or 'degraded')
2196
2369
  *
2197
2370
  * @example
2198
2371
  * ```typescript
2199
- * // In iframe sending READY to parent
2200
- * const context = getMessagingContext(MessageEvents.READY)
2201
- * // Returns: { shouldUsePostMessage: true, target: window.parent, origin: '*' }
2202
- *
2203
- * // In same context sending INIT
2204
- * const context = getMessagingContext(MessageEvents.INIT)
2205
- * // Returns: { shouldUsePostMessage: false, target: undefined, origin: '*' }
2372
+ * const state = manager.getState()
2373
+ * if (state === 'offline') {
2374
+ * console.log('No connection')
2375
+ * }
2206
2376
  * ```
2207
2377
  */
2208
- private getMessagingContext;
2378
+ getState(): ConnectionState;
2209
2379
  /**
2210
- * **Send Via PostMessage Method**
2211
- *
2212
- * Sends a message using the browser's postMessage API for iframe-to-parent communication.
2213
- * This method is used when the game is running in an iframe and needs to communicate
2214
- * with the parent window (the Playcademy shell).
2215
- *
2216
- * **PostMessage Protocol**:
2217
- * The postMessage API is the standard way for iframes to communicate with their parent.
2218
- * It's secure, cross-origin capable, and designed specifically for this use case.
2219
- *
2220
- * **Message Structure**:
2221
- * The method creates a message object with the following structure:
2222
- * - `type`: The message event type (e.g., 'PLAYCADEMY_READY')
2223
- * - `payload`: The message data (if any)
2380
+ * Manually triggers a connection check immediately.
2224
2381
  *
2225
- * **Payload Handling**:
2226
- * - **All payloads**: Wrapped in a `payload` property for consistency (e.g., { type, payload: data })
2227
- * - **Undefined payloads**: Only the type is sent (e.g., { type })
2382
+ * Forces a heartbeat ping to verify the current connection status.
2383
+ * Useful when you need to check connectivity before a critical operation.
2228
2384
  *
2229
- * **Security**:
2230
- * The origin parameter controls which domains can receive the message.
2231
- * Currently set to '*' for development, but should be restricted in production.
2385
+ * In iframe mode, this returns the last known state from platform.
2232
2386
  *
2233
- * @template K - The message event type (ensures type safety)
2234
- * @param type - The message event type to send
2235
- * @param payload - The data to send with the message
2236
- * @param target - The target window (defaults to parent window)
2237
- * @param origin - The allowed origin for the message (defaults to '*')
2387
+ * @returns Promise resolving to the current connection state
2238
2388
  *
2239
2389
  * @example
2240
2390
  * ```typescript
2241
- * // Send ready signal (no payload)
2242
- * sendViaPostMessage(MessageEvents.READY, undefined)
2243
- * // Sends: { type: 'PLAYCADEMY_READY' }
2391
+ * const state = await manager.checkNow()
2392
+ * if (state === 'online') {
2393
+ * await performCriticalOperation()
2394
+ * }
2395
+ * ```
2396
+ */
2397
+ checkNow(): Promise<ConnectionState>;
2398
+ /**
2399
+ * Reports a successful API request to the connection monitor.
2244
2400
  *
2245
- * // Send telemetry data (object payload)
2246
- * sendViaPostMessage(MessageEvents.TELEMETRY, { fps: 60, mem: 128 })
2247
- * // Sends: { type: 'PLAYCADEMY_TELEMETRY', payload: { fps: 60, mem: 128 } }
2401
+ * This resets the consecutive failure counter and transitions from
2402
+ * 'degraded' to 'online' state if applicable.
2248
2403
  *
2249
- * // Send overlay state (primitive payload)
2250
- * sendViaPostMessage(MessageEvents.OVERLAY, true)
2251
- * // Sends: { type: 'PLAYCADEMY_OVERLAY', payload: true }
2252
- * ```
2404
+ * Typically called automatically by the SDK's request wrapper.
2405
+ * No-op in iframe mode (platform handles monitoring).
2253
2406
  */
2254
- private sendViaPostMessage;
2407
+ reportRequestSuccess(): void;
2255
2408
  /**
2256
- * **Send Via CustomEvent Method**
2409
+ * Reports a failed API request to the connection monitor.
2257
2410
  *
2258
- * Sends a message using the browser's CustomEvent API for local same-context communication.
2259
- * This method is used when both the sender and receiver are in the same window context,
2260
- * typically during local development or when the parent needs to send messages to the game.
2411
+ * Only network errors are tracked (not 4xx/5xx HTTP responses).
2412
+ * After consecutive failures exceed the threshold, the state transitions
2413
+ * to 'degraded' or 'offline'.
2261
2414
  *
2262
- * **CustomEvent Protocol**:
2263
- * CustomEvent is a browser API for creating and dispatching custom events within the same
2264
- * window context. It's simpler than postMessage but only works within the same origin/context.
2415
+ * Typically called automatically by the SDK's request wrapper.
2416
+ * No-op in iframe mode (platform handles monitoring).
2265
2417
  *
2266
- * **Event Structure**:
2267
- * - **type**: The event name (e.g., 'PLAYCADEMY_INIT')
2268
- * - **detail**: The payload data attached to the event
2418
+ * @param error - The error from the failed request
2419
+ */
2420
+ reportRequestFailure(error: unknown): void;
2421
+ /**
2422
+ * Registers a callback to be called when connection issues are detected.
2269
2423
  *
2270
- * **When This Is Used**:
2271
- * - Local development when game and shell run in the same context
2272
- * - Parent-to-game communication (INIT, TOKEN_REFRESH, PAUSE, etc.)
2273
- * - Any scenario where postMessage isn't needed
2424
+ * The callback only fires for 'offline' and 'degraded' states, not when
2425
+ * recovering to 'online'. This provides a clean API for games to handle
2426
+ * disconnect scenarios without being notified of every state change.
2274
2427
  *
2275
- * **Event Bubbling**:
2276
- * CustomEvents are dispatched on the window object and can be listened to by any
2277
- * code in the same context using `addEventListener(type, handler)`.
2428
+ * Works in both iframe and standalone modes transparently.
2278
2429
  *
2279
- * @template K - The message event type (ensures type safety)
2280
- * @param type - The message event type to send (becomes the event name)
2281
- * @param payload - The data to send with the message (stored in event.detail)
2430
+ * @param callback - Function to call when connection degrades
2431
+ * @returns Cleanup function to unregister the callback
2282
2432
  *
2283
2433
  * @example
2284
2434
  * ```typescript
2285
- * // Send initialization data
2286
- * sendViaCustomEvent(MessageEvents.INIT, {
2287
- * baseUrl: '/api',
2288
- * token: 'abc123',
2289
- * gameId: 'game-456'
2435
+ * const cleanup = manager.onDisconnect(({ state, reason, displayAlert }) => {
2436
+ * if (state === 'offline') {
2437
+ * displayAlert?.('Connection lost. Saving your progress...', { type: 'error' })
2438
+ * saveGameState()
2439
+ * }
2290
2440
  * })
2291
- * // Creates: CustomEvent('PLAYCADEMY_INIT', { detail: { baseUrl, token, gameId } })
2292
- *
2293
- * // Send pause signal
2294
- * sendViaCustomEvent(MessageEvents.PAUSE, undefined)
2295
- * // Creates: CustomEvent('PLAYCADEMY_PAUSE', { detail: undefined })
2296
2441
  *
2297
- * // Listeners can access the data via event.detail:
2298
- * window.addEventListener('PLAYCADEMY_INIT', (event) => {
2299
- * console.log(event.detail.gameId) // 'game-456'
2300
- * })
2442
+ * // Later: cleanup() to unregister
2301
2443
  * ```
2302
2444
  */
2303
- private sendViaCustomEvent;
2445
+ onDisconnect(callback: DisconnectHandler): () => void;
2446
+ /**
2447
+ * Stops connection monitoring and performs cleanup.
2448
+ *
2449
+ * Removes event listeners and clears heartbeat intervals.
2450
+ * Should be called when the client is being destroyed.
2451
+ */
2452
+ stop(): void;
2453
+ /**
2454
+ * Sets up listener for platform connection state broadcasts (iframe mode only).
2455
+ */
2456
+ private _setupPlatformListener;
2457
+ /**
2458
+ * Handles connection state changes from the monitor or platform.
2459
+ *
2460
+ * Coordinates between:
2461
+ * 1. Emitting events to the client (for client.on('connectionChange'))
2462
+ * 2. Calling the disconnect handler if configured
2463
+ * 3. Calling any additional handlers registered via onDisconnect()
2464
+ *
2465
+ * @param state - The new connection state
2466
+ * @param reason - Human-readable reason for the state change
2467
+ */
2468
+ private _handleConnectionChange;
2304
2469
  }
2305
- /**
2306
- * **Playcademy Messaging Singleton**
2307
- *
2308
- * This is the main messaging instance used throughout the Playcademy platform.
2309
- * It's exported as a singleton to ensure consistent communication across all parts
2310
- * of the application.
2311
- *
2312
- * **Why a Singleton?**:
2313
- * - Ensures all parts of the app use the same messaging instance
2314
- * - Prevents conflicts between multiple messaging systems
2315
- * - Simplifies the API - no need to pass instances around
2316
- * - Maintains consistent event listener management
2317
- *
2318
- * **Usage in Different Contexts**:
2319
- *
2320
- * **In Games**:
2321
- * ```typescript
2322
- * import { messaging, MessageEvents } from '@playcademy/sdk'
2323
- *
2324
- * // Tell parent we're ready
2325
- * messaging.send(MessageEvents.READY, undefined)
2326
- *
2327
- * // Listen for pause/resume
2328
- * messaging.listen(MessageEvents.PAUSE, () => game.pause())
2329
- * messaging.listen(MessageEvents.RESUME, () => game.resume())
2330
- * ```
2331
- *
2332
- * **In Parent Shell**:
2333
- * ```typescript
2334
- * import { messaging, MessageEvents } from '@playcademy/sdk'
2335
- *
2336
- * // Send initialization data to game
2337
- * messaging.send(MessageEvents.INIT, { baseUrl, token, gameId })
2338
- *
2339
- * // Listen for game events
2340
- * messaging.listen(MessageEvents.EXIT, () => closeGame())
2341
- * messaging.listen(MessageEvents.READY, () => showGame())
2342
- * ```
2343
- *
2344
- * **Automatic Transport Selection**:
2345
- * The messaging system automatically chooses the right transport method:
2346
- * - Uses postMessage when game is in iframe sending to parent
2347
- * - Uses CustomEvent for local development and parent-to-game communication
2348
- *
2349
- * **Type Safety**:
2350
- * All message sending and receiving is fully type-safe with TypeScript.
2351
- */
2352
- declare const messaging: PlaycademyMessaging;
2353
2470
 
2354
2471
  export { ApiError, ConnectionManager, ConnectionMonitor, MessageEvents, PlaycademyClient, PlaycademyError, extractApiErrorInfo, messaging };
2355
- export type { ApiErrorCode, ApiErrorInfo, ConnectionMonitorConfig, ConnectionState, ConnectionStatePayload, DevUploadEvent, DevUploadHooks, DisconnectContext, DisconnectHandler, DisplayAlertPayload, ErrorResponseBody };
2472
+ export type { ApiErrorCode, ApiErrorInfo, ConnectionMonitorConfig, ConnectionState, ConnectionStatePayload, DevUploadEvent, DevUploadHooks, DisconnectContext, DisconnectHandler, DisplayAlertPayload, ErrorResponseBody, PlaycademyMode };