@playcademy/sdk 0.5.0 → 0.6.0-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 +1425 -1308
- package/dist/index.js +118 -12
- package/dist/internal.d.ts +68 -1
- package/dist/internal.js +115 -25
- package/dist/types.d.ts +506 -403
- package/package.json +1 -1
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
|
|
1089
|
-
*
|
|
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
|
-
|
|
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
|
-
*
|
|
1094
|
-
*
|
|
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
|
-
|
|
1097
|
-
connect: (options: AuthOptions) => Promise<AuthResult>;
|
|
1098
|
-
_getContext: () => {
|
|
1099
|
-
isInIframe: boolean;
|
|
1100
|
-
};
|
|
1101
|
-
};
|
|
992
|
+
INIT = "PLAYCADEMY_INIT",
|
|
1102
993
|
/**
|
|
1103
|
-
*
|
|
1104
|
-
*
|
|
1105
|
-
*
|
|
1106
|
-
* - `
|
|
1107
|
-
* - `
|
|
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
|
-
|
|
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
|
-
*
|
|
1145
|
-
*
|
|
1146
|
-
*
|
|
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
|
-
|
|
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
|
-
*
|
|
1166
|
-
*
|
|
1167
|
-
*
|
|
1008
|
+
* Resumes game execution after being paused.
|
|
1009
|
+
* Game should restore timers, animations, and user input.
|
|
1010
|
+
* Payload: void
|
|
1168
1011
|
*/
|
|
1169
|
-
|
|
1170
|
-
balance: () => Promise<number>;
|
|
1171
|
-
add: (amount: number) => Promise<number>;
|
|
1172
|
-
spend: (amount: number) => Promise<number>;
|
|
1173
|
-
};
|
|
1012
|
+
RESUME = "PLAYCADEMY_RESUME",
|
|
1174
1013
|
/**
|
|
1175
|
-
*
|
|
1176
|
-
*
|
|
1014
|
+
* Forces immediate game termination (emergency exit).
|
|
1015
|
+
* Game should clean up resources and exit immediately.
|
|
1016
|
+
* Payload: void
|
|
1177
1017
|
*/
|
|
1178
|
-
|
|
1179
|
-
submit: (gameId: string, score: number, metadata?: Record<string, unknown> | undefined) => Promise<ScoreSubmission>;
|
|
1180
|
-
};
|
|
1018
|
+
FORCE_EXIT = "PLAYCADEMY_FORCE_EXIT",
|
|
1181
1019
|
/**
|
|
1182
|
-
*
|
|
1183
|
-
*
|
|
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
|
-
|
|
1186
|
-
token: {
|
|
1187
|
-
get: () => Promise<RealtimeTokenResponse>;
|
|
1188
|
-
};
|
|
1189
|
-
};
|
|
1024
|
+
OVERLAY = "PLAYCADEMY_OVERLAY",
|
|
1190
1025
|
/**
|
|
1191
|
-
*
|
|
1192
|
-
*
|
|
1193
|
-
*
|
|
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
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
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
|
|
1099
|
+
* Type definition for message handler functions.
|
|
1100
|
+
* These functions are called when a specific message type is received.
|
|
1217
1101
|
*
|
|
1218
|
-
*
|
|
1219
|
-
*
|
|
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
|
|
1105
|
+
type MessageHandler<T = unknown> = (payload: T) => void;
|
|
1227
1106
|
/**
|
|
1228
|
-
*
|
|
1229
|
-
*
|
|
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
|
-
|
|
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
|
-
*
|
|
1234
|
-
*
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
*
|
|
1248
|
-
*
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
*
|
|
1262
|
-
*
|
|
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
|
-
|
|
1187
|
+
declare class PlaycademyMessaging {
|
|
1265
1188
|
/**
|
|
1266
|
-
*
|
|
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
|
-
*
|
|
1271
|
-
*
|
|
1272
|
-
*
|
|
1273
|
-
*
|
|
1274
|
-
*
|
|
1275
|
-
*
|
|
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
|
-
* //
|
|
1280
|
-
*
|
|
1228
|
+
* // Send game ready signal (no payload)
|
|
1229
|
+
* messaging.send(MessageEvents.READY, undefined)
|
|
1281
1230
|
*
|
|
1282
|
-
* //
|
|
1283
|
-
*
|
|
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
|
-
* //
|
|
1289
|
-
*
|
|
1290
|
-
*
|
|
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
|
-
* //
|
|
1294
|
-
*
|
|
1240
|
+
* // TypeScript will error if payload type is wrong
|
|
1241
|
+
* messaging.send(MessageEvents.INIT, "wrong type") // ❌ Error
|
|
1295
1242
|
* ```
|
|
1296
1243
|
*/
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
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
|
-
*
|
|
1305
|
-
*
|
|
1306
|
-
*
|
|
1307
|
-
*
|
|
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
|
-
|
|
1310
|
-
force?: boolean;
|
|
1311
|
-
}): Promise<TimebackUserContext>;
|
|
1288
|
+
listen<K extends MessageEvents>(type: K, handler: MessageHandler<MessageEventMap[K]>): void;
|
|
1312
1289
|
/**
|
|
1313
|
-
*
|
|
1314
|
-
*
|
|
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
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
}
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
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
|
-
*
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
*
|
|
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
|
-
*
|
|
1527
|
+
* Base interface for authentication strategies
|
|
1538
1528
|
*/
|
|
1539
|
-
interface
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
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
|
-
*
|
|
1568
|
-
*
|
|
1569
|
-
*
|
|
1561
|
+
* Internal session manager for automatic session lifecycle.
|
|
1562
|
+
* @private
|
|
1563
|
+
* @internal
|
|
1570
1564
|
*/
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
/**
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
/**
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
/**
|
|
1587
|
-
|
|
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
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
1630
|
-
/**
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
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
|
-
*
|
|
1666
|
-
*
|
|
1667
|
-
*
|
|
1668
|
-
*
|
|
1669
|
-
*
|
|
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
|
-
|
|
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
|
-
*
|
|
1766
|
+
* TimeBack integration for activity tracking and user context.
|
|
1686
1767
|
*
|
|
1687
|
-
*
|
|
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
|
-
*
|
|
1690
|
-
*
|
|
1691
|
-
*
|
|
1692
|
-
*
|
|
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
|
-
|
|
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
|
-
*
|
|
1700
|
-
*
|
|
1701
|
-
*
|
|
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
|
-
|
|
1791
|
+
credits: {
|
|
1792
|
+
balance: () => Promise<number>;
|
|
1793
|
+
add: (amount: number) => Promise<number>;
|
|
1794
|
+
spend: (amount: number) => Promise<number>;
|
|
1795
|
+
};
|
|
1717
1796
|
/**
|
|
1718
|
-
*
|
|
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
|
-
|
|
1800
|
+
scores: {
|
|
1801
|
+
submit: (score: number, metadata?: Record<string, unknown> | undefined) => Promise<ScoreSubmission>;
|
|
1802
|
+
};
|
|
1727
1803
|
/**
|
|
1728
|
-
*
|
|
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
|
-
|
|
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
|
-
*
|
|
1742
|
-
*
|
|
1743
|
-
*
|
|
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
|
|
1750
|
-
* @
|
|
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
|
-
*
|
|
1755
|
-
*
|
|
1756
|
-
*
|
|
1757
|
-
*
|
|
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
|
-
* //
|
|
1762
|
-
*
|
|
1763
|
-
|
|
1764
|
-
|
|
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
|
-
*
|
|
1769
|
-
*
|
|
1936
|
+
* // Force fresh data
|
|
1937
|
+
* const xp = await client.timeback.user.xp.fetch({ force: true })
|
|
1938
|
+
* ```
|
|
1770
1939
|
*/
|
|
1771
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
1952
|
+
fetch(options?: {
|
|
1953
|
+
force?: boolean;
|
|
1954
|
+
}): Promise<TimebackUserContext>;
|
|
1776
1955
|
/**
|
|
1777
|
-
*
|
|
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
|
-
|
|
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
|
-
*
|
|
1792
|
-
*
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
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
|
-
*
|
|
1803
|
-
*
|
|
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
|
-
*
|
|
1806
|
-
*
|
|
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
|
-
*
|
|
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
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
/**
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
/**
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
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
|
-
*
|
|
1927
|
-
*
|
|
1928
|
-
*
|
|
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
|
-
|
|
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
|
-
*
|
|
1937
|
-
* These functions are called when a specific message type is received.
|
|
2298
|
+
* Connection Manager
|
|
1938
2299
|
*
|
|
1939
|
-
*
|
|
1940
|
-
*
|
|
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
|
-
|
|
2306
|
+
|
|
1943
2307
|
/**
|
|
1944
|
-
*
|
|
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
|
|
1959
|
-
/**
|
|
1960
|
-
|
|
1961
|
-
/**
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
/**
|
|
1966
|
-
|
|
1967
|
-
/**
|
|
1968
|
-
|
|
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
|
-
*
|
|
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
|
-
*
|
|
2009
|
-
*
|
|
2010
|
-
*
|
|
2011
|
-
*
|
|
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
|
-
*
|
|
2014
|
-
*
|
|
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
|
-
*
|
|
2019
|
-
*
|
|
2020
|
-
* ```
|
|
2335
|
+
* @see {@link ConnectionMonitor} for the underlying monitoring implementation
|
|
2336
|
+
* @see {@link PlaycademyClient.onDisconnect} for the public API
|
|
2021
2337
|
*/
|
|
2022
|
-
declare class
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
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
|
-
*
|
|
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
|
-
* @
|
|
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
|
-
*
|
|
2152
|
-
*
|
|
2153
|
-
*
|
|
2154
|
-
*
|
|
2155
|
-
*
|
|
2156
|
-
*
|
|
2157
|
-
*
|
|
2158
|
-
*
|
|
2159
|
-
*
|
|
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
|
-
|
|
2364
|
+
constructor(config: ConnectionManagerConfig);
|
|
2163
2365
|
/**
|
|
2164
|
-
*
|
|
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
|
-
* @
|
|
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
|
-
*
|
|
2200
|
-
*
|
|
2201
|
-
*
|
|
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
|
-
|
|
2378
|
+
getState(): ConnectionState;
|
|
2209
2379
|
/**
|
|
2210
|
-
*
|
|
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
|
-
*
|
|
2226
|
-
*
|
|
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
|
-
*
|
|
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
|
-
* @
|
|
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
|
-
*
|
|
2242
|
-
*
|
|
2243
|
-
*
|
|
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
|
-
*
|
|
2246
|
-
*
|
|
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
|
-
*
|
|
2250
|
-
*
|
|
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
|
-
|
|
2407
|
+
reportRequestSuccess(): void;
|
|
2255
2408
|
/**
|
|
2256
|
-
*
|
|
2409
|
+
* Reports a failed API request to the connection monitor.
|
|
2257
2410
|
*
|
|
2258
|
-
*
|
|
2259
|
-
*
|
|
2260
|
-
*
|
|
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
|
-
*
|
|
2263
|
-
*
|
|
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
|
-
*
|
|
2267
|
-
|
|
2268
|
-
|
|
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
|
-
*
|
|
2271
|
-
*
|
|
2272
|
-
*
|
|
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
|
-
*
|
|
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
|
-
* @
|
|
2280
|
-
* @
|
|
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
|
-
*
|
|
2286
|
-
*
|
|
2287
|
-
*
|
|
2288
|
-
*
|
|
2289
|
-
*
|
|
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
|
-
* //
|
|
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
|
-
|
|
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 };
|