@playcademy/sdk 0.4.1-beta.3 → 0.4.1-beta.5

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.
@@ -9,370 +9,88 @@ import { AUTH_PROVIDER_IDS } from '@playcademy/constants';
9
9
  type Method = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
10
10
 
11
11
  /**
12
- * User Types
12
+ * TimeBack Enums & Literal Types
13
13
  *
14
- * Enums, DTOs and API response types. Database row types are in @playcademy/data/types.
14
+ * Basic type definitions used throughout the TimeBack integration.
15
15
  *
16
- * @module types/user
16
+ * @module types/timeback/types
17
17
  */
18
- type UserRoleEnumType = 'admin' | 'player' | 'developer';
19
- type DeveloperStatusEnumType = 'none' | 'pending' | 'approved';
20
- type DeveloperStatusValue = DeveloperStatusEnumType;
21
- type TimebackUserRole = 'administrator' | 'aide' | 'guardian' | 'parent' | 'proctor' | 'relative' | 'student' | 'teacher';
22
- type TimebackOrgType = 'department' | 'school' | 'district' | 'local' | 'state' | 'national';
23
- interface UserEnrollment {
24
- gameId?: string;
25
- courseId: string;
26
- grade: number;
27
- subject: string;
28
- orgId?: string;
29
- }
30
- interface UserOrganization {
31
- id: string;
32
- name: string | null;
33
- type: TimebackOrgType | string;
34
- isPrimary: boolean;
35
- }
36
- interface TimebackStudentProfile {
37
- role: TimebackUserRole;
38
- organizations: UserOrganization[];
39
- }
40
- interface UserTimebackData extends TimebackStudentProfile {
41
- id: string;
42
- enrollments: UserEnrollment[];
43
- }
44
18
  /**
45
- * OpenID Connect UserInfo claims (NOT a database row).
19
+ * Valid TimeBack subject values for course configuration.
20
+ * These are the supported subject values for OneRoster courses.
46
21
  */
47
- interface UserInfo {
48
- sub: string;
49
- email: string;
50
- name: string | null;
51
- email_verified?: boolean;
52
- given_name?: string;
53
- family_name?: string;
54
- issuer?: string;
55
- lti_roles?: unknown;
56
- lti_context?: unknown;
57
- lti_resource_link?: unknown;
58
- timeback_id?: string;
59
- }
60
- interface DeveloperStatusResponse {
61
- status: DeveloperStatusEnumType;
62
- }
22
+ type TimebackSubject = 'Reading' | 'Language' | 'Vocabulary' | 'Social Studies' | 'Writing' | 'Science' | 'FastMath' | 'Math' | 'None';
63
23
  /**
64
- * Authenticated user for API responses.
65
- * Differs from UserRow: omits timebackId, adds hasTimebackAccount and timeback.
24
+ * Grade levels per AE OneRoster GradeEnum.
25
+ * -1 = Pre-K, 0 = Kindergarten, 1-12 = Grades 1-12, 13 = AP
66
26
  */
67
- interface AuthenticatedUser {
68
- id: string;
69
- email: string;
70
- emailVerified: boolean;
71
- name: string | null;
72
- image: string | null;
73
- username: string | null;
74
- role: UserRoleEnumType;
75
- developerStatus: DeveloperStatusEnumType;
76
- characterCreated: boolean;
77
- createdAt: Date;
78
- updatedAt: Date;
79
- hasTimebackAccount: boolean;
80
- timeback?: UserTimebackData;
81
- }
82
- interface GameUser {
83
- id: string;
84
- name: string | null;
85
- role: UserRoleEnumType;
86
- username: string | null;
87
- email: string | null;
88
- timeback?: UserTimebackData;
89
- }
90
-
27
+ type TimebackGrade = -1 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13;
91
28
  /**
92
- * Game Types
93
- *
94
- * Literal types and API DTOs. Database row types are in @playcademy/data/types.
95
- *
96
- * @module types/game
29
+ * Valid Caliper subject values.
30
+ * Matches OneRoster subjects, with "None" as a Caliper-specific fallback.
97
31
  */
98
- type GameType = 'hosted' | 'external';
99
- type GamePlatform = 'web' | 'godot' | 'unity' | (string & {});
32
+ type CaliperSubject = 'Reading' | 'Language' | 'Vocabulary' | 'Social Studies' | 'Writing' | 'Science' | 'FastMath' | 'Math' | 'None';
100
33
  /**
101
- * Game manifest file format (manifest.json).
102
- * Note: createdAt is a string here because it's parsed from JSON file.
34
+ * OneRoster organization types.
103
35
  */
104
- interface ManifestV1 {
105
- version: string;
106
- platform: string;
107
- createdAt: string;
108
- }
109
- /** Log entry captured from seed worker console output */
110
- interface SeedLogEntry {
111
- /** Log level (log, warn, error, info) */
112
- level: 'log' | 'warn' | 'error' | 'info';
113
- /** Log message content */
114
- message: string;
115
- /** Milliseconds since seed execution started */
116
- timestamp: number;
117
- }
118
- /** Structured error details from D1/SQLite errors */
119
- interface SeedErrorDetails {
120
- /** Error category code */
121
- code?: 'CONSTRAINT_VIOLATION' | 'SQL_ERROR' | 'DATABASE_BUSY';
122
- /** Table name involved in the error */
123
- table?: string;
124
- /** Constraint name or column that caused the error */
125
- constraint?: string;
126
- /** Specific constraint type */
127
- constraintType?: 'UNIQUE' | 'FOREIGN_KEY' | 'NOT_NULL';
128
- /** Token near syntax error */
129
- nearToken?: string;
130
- /** Specific error type within category */
131
- errorType?: 'TABLE_NOT_FOUND' | 'SYNTAX_ERROR';
132
- }
36
+ type OrganizationType = 'department' | 'school' | 'district' | 'local' | 'state' | 'national';
133
37
  /**
134
- * API response for seed operations (what the API returns to the CLI).
135
- *
136
- * Extends the worker response with deployment metadata.
38
+ * Lesson types for PowerPath integration.
137
39
  */
138
- interface SeedResponse {
139
- /** Whether the seed completed successfully */
140
- success: boolean;
141
- /** Unique identifier for the seed worker deployment */
142
- deploymentId: string;
143
- /** When the seed was executed (ISO 8601 string from JSON serialization) */
144
- executedAt: string;
145
- /** Captured console output from the seed script */
146
- logs?: SeedLogEntry[];
147
- /** Execution duration in milliseconds */
148
- duration?: number;
149
- /** Error message if seed failed */
150
- error?: string;
151
- /** Stack trace if seed failed */
152
- stack?: string;
153
- /** Structured error details if seed failed */
154
- details?: SeedErrorDetails;
155
- }
156
- interface DomainValidationRecords {
157
- ownership?: {
158
- name?: string;
159
- value?: string;
160
- type?: string;
161
- };
162
- ssl?: {
163
- txt_name?: string;
164
- txt_value?: string;
165
- }[];
166
- }
40
+ type LessonType = 'powerpath-100' | 'quiz' | 'test-out' | 'placement' | 'unit-test' | 'alpha-read-article' | null;
167
41
 
168
42
  /**
169
- * Leaderboard Types
43
+ * TimeBack Configuration Types
170
44
  *
171
- * @module types/leaderboard
45
+ * Configuration interfaces for Organization, Course, Component,
46
+ * Resource, and complete TimeBack setup.
47
+ *
48
+ * @module types/timeback/config
172
49
  */
173
- type LeaderboardTimeframe = 'all_time' | 'monthly' | 'weekly' | 'daily';
174
- interface LeaderboardOptions {
175
- timeframe?: LeaderboardTimeframe;
176
- limit?: number;
177
- offset?: number;
178
- gameId?: string;
179
- }
180
- interface LeaderboardEntry {
181
- rank: number;
182
- userId: string;
183
- username: string;
184
- userImage?: string | null;
185
- score: number;
186
- achievedAt: Date;
187
- metadata?: Record<string, unknown>;
188
- gameId?: string;
189
- gameTitle?: string;
190
- gameSlug?: string;
191
- }
192
- interface UserRank {
193
- rank: number;
194
- totalPlayers: number;
195
- score: number;
196
- percentile: number;
197
- }
198
- interface UserRankResponse {
199
- rank: number;
200
- score: number;
201
- userId: string;
50
+
51
+ /**
52
+ * Organization configuration for TimeBack (user input - optionals allowed)
53
+ */
54
+ interface OrganizationConfig {
55
+ /** Display name for your organization */
56
+ name?: string;
57
+ /** Organization type */
58
+ type?: OrganizationType;
59
+ /** Unique identifier (defaults to Playcademy's org) */
60
+ identifier?: string;
202
61
  }
203
- interface UserScore {
204
- id: string;
205
- score: number;
206
- achievedAt: Date;
207
- metadata?: Record<string, unknown>;
208
- gameId: string;
209
- gameTitle: string;
210
- gameSlug: string;
62
+ /**
63
+ * Course goals for daily student targets
64
+ */
65
+ interface CourseGoals {
66
+ /** Target XP students should earn per day */
67
+ dailyXp?: number;
68
+ /** Target lessons per day */
69
+ dailyLessons?: number;
70
+ /** Target active minutes per day */
71
+ dailyActiveMinutes?: number;
72
+ /** Target accuracy percentage */
73
+ dailyAccuracy?: number;
74
+ /** Target mastered units per day */
75
+ dailyMasteredUnits?: number;
211
76
  }
212
77
  /**
213
- * Leaderboard entry with required game context.
214
- * Used when fetching leaderboards for a specific game.
78
+ * Course metrics and totals
215
79
  */
216
- interface GameLeaderboardEntry {
217
- rank: number;
218
- userId: string;
219
- username: string;
220
- userImage?: string | null;
221
- score: number;
222
- achievedAt: Date;
223
- metadata?: Record<string, unknown>;
224
- gameId: string;
225
- gameTitle: string;
226
- gameSlug: string;
80
+ interface CourseMetrics {
81
+ /** Total XP available in the course */
82
+ totalXp?: number;
83
+ /** Total lessons/activities in the course */
84
+ totalLessons?: number;
85
+ /** Total number of grade levels covered by this course */
86
+ totalGrades?: number;
87
+ /** The type of course (e.g. 'optional', 'hole-filling', 'base') */
88
+ courseType?: 'base' | 'hole-filling' | 'optional' | 'Base' | 'Hole-Filling' | 'Optional';
89
+ /** Indicates whether the course is supplemental content */
90
+ isSupplemental?: boolean;
227
91
  }
228
-
229
92
  /**
230
- * Achievement Types
231
- *
232
- * @module types/achievement
233
- */
234
- type AchievementScopeType = 'daily' | 'weekly' | 'monthly' | 'yearly' | 'game' | 'global' | 'map' | 'level' | 'event';
235
- declare enum AchievementCompletionType {
236
- TIME_PLAYED_SESSION = "time_played_session",
237
- INTERACTION = "interaction",
238
- LEADERBOARD_RANK = "leaderboard_rank",
239
- FIRST_SCORE = "first_score",
240
- PERSONAL_BEST = "personal_best"
241
- }
242
- interface AchievementCurrent {
243
- id: string;
244
- title: string;
245
- description?: string | null;
246
- scope: AchievementScopeType;
247
- rewardCredits: number;
248
- limit: number;
249
- completionType: string;
250
- completionConfig: unknown;
251
- target: unknown;
252
- active: boolean;
253
- createdAt?: Date | null;
254
- updatedAt?: Date | null;
255
- status: 'available' | 'completed';
256
- scopeKey: string;
257
- windowStart: string;
258
- windowEnd: string;
259
- }
260
- interface AchievementWithStatus {
261
- id: string;
262
- title: string;
263
- description: string | null;
264
- scope: AchievementScopeType;
265
- rewardCredits: number;
266
- limit: number;
267
- completionType: string;
268
- completionConfig: unknown;
269
- target: unknown;
270
- active: boolean;
271
- createdAt: Date | null;
272
- updatedAt: Date | null;
273
- status: 'available' | 'completed';
274
- scopeKey: string;
275
- windowStart?: string;
276
- windowEnd?: string;
277
- }
278
- interface AchievementHistoryEntry {
279
- achievementId: string;
280
- title: string;
281
- rewardCredits: number;
282
- createdAt: Date;
283
- scopeKey: string;
284
- }
285
- interface AchievementProgressResponse {
286
- achievementId: string;
287
- status: 'completed' | 'already_completed';
288
- rewardCredits: number;
289
- scopeKey: string;
290
- createdAt: Date;
291
- }
292
-
293
- /**
294
- * TimeBack Enums & Literal Types
295
- *
296
- * Basic type definitions used throughout the TimeBack integration.
297
- *
298
- * @module types/timeback/types
299
- */
300
- /**
301
- * Valid TimeBack subject values for course configuration.
302
- * These are the supported subject values for OneRoster courses.
303
- */
304
- type TimebackSubject = 'Reading' | 'Language' | 'Vocabulary' | 'Social Studies' | 'Writing' | 'Science' | 'FastMath' | 'Math' | 'None';
305
- /**
306
- * Grade levels per AE OneRoster GradeEnum.
307
- * -1 = Pre-K, 0 = Kindergarten, 1-12 = Grades 1-12, 13 = AP
308
- */
309
- type TimebackGrade = -1 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13;
310
- /**
311
- * Valid Caliper subject values.
312
- * Matches OneRoster subjects, with "None" as a Caliper-specific fallback.
313
- */
314
- type CaliperSubject = 'Reading' | 'Language' | 'Vocabulary' | 'Social Studies' | 'Writing' | 'Science' | 'FastMath' | 'Math' | 'None';
315
- /**
316
- * OneRoster organization types.
317
- */
318
- type OrganizationType = 'department' | 'school' | 'district' | 'local' | 'state' | 'national';
319
- /**
320
- * Lesson types for PowerPath integration.
321
- */
322
- type LessonType = 'powerpath-100' | 'quiz' | 'test-out' | 'placement' | 'unit-test' | 'alpha-read-article' | null;
323
-
324
- /**
325
- * TimeBack Configuration Types
326
- *
327
- * Configuration interfaces for Organization, Course, Component,
328
- * Resource, and complete TimeBack setup.
329
- *
330
- * @module types/timeback/config
331
- */
332
-
333
- /**
334
- * Organization configuration for TimeBack (user input - optionals allowed)
335
- */
336
- interface OrganizationConfig {
337
- /** Display name for your organization */
338
- name?: string;
339
- /** Organization type */
340
- type?: OrganizationType;
341
- /** Unique identifier (defaults to Playcademy's org) */
342
- identifier?: string;
343
- }
344
- /**
345
- * Course goals for daily student targets
346
- */
347
- interface CourseGoals {
348
- /** Target XP students should earn per day */
349
- dailyXp?: number;
350
- /** Target lessons per day */
351
- dailyLessons?: number;
352
- /** Target active minutes per day */
353
- dailyActiveMinutes?: number;
354
- /** Target accuracy percentage */
355
- dailyAccuracy?: number;
356
- /** Target mastered units per day */
357
- dailyMasteredUnits?: number;
358
- }
359
- /**
360
- * Course metrics and totals
361
- */
362
- interface CourseMetrics {
363
- /** Total XP available in the course */
364
- totalXp?: number;
365
- /** Total lessons/activities in the course */
366
- totalLessons?: number;
367
- /** Total number of grade levels covered by this course */
368
- totalGrades?: number;
369
- /** The type of course (e.g. 'optional', 'hole-filling', 'base') */
370
- courseType?: 'base' | 'hole-filling' | 'optional' | 'Base' | 'Hole-Filling' | 'Optional';
371
- /** Indicates whether the course is supplemental content */
372
- isSupplemental?: boolean;
373
- }
374
- /**
375
- * Complete course metadata structure
93
+ * Complete course metadata structure
376
94
  */
377
95
  interface CourseMetadata {
378
96
  /** Define the type of course and priority for the student */
@@ -796,125 +514,91 @@ interface ToggleCourseCompletionRequest {
796
514
  studentId: string;
797
515
  action: 'complete' | 'resume';
798
516
  }
517
+ interface EnrollStudentRequest {
518
+ gameId: string;
519
+ courseId: string;
520
+ studentId: string;
521
+ }
522
+ interface UnenrollStudentRequest {
523
+ gameId: string;
524
+ courseId: string;
525
+ studentId: string;
526
+ }
527
+ interface SearchStudentResult {
528
+ studentId: string;
529
+ name: string;
530
+ email: string | null;
531
+ alreadyEnrolled: boolean;
532
+ }
533
+ interface SearchStudentsResponse {
534
+ students: SearchStudentResult[];
535
+ }
799
536
  interface TimebackAdminMutationResponse {
800
537
  status: 'ok';
801
538
  }
802
539
 
803
540
  /**
804
- * Inventory & Shop Types
805
- *
806
- * Literal types for inventory system. Database row types are in @playcademy/data/types.
807
- *
808
- * @module types/inventory
809
- */
810
- /**
811
- * Item categories available on the platform.
812
- */
813
- type ItemType = 'currency' | 'badge' | 'trophy' | 'collectible' | 'consumable' | 'unlock' | 'upgrade' | 'accessory' | 'other';
814
-
815
- /**
816
- * Map & Placement Types
817
- *
818
- * Literal types and JSON shapes for maps. Database row types are in @playcademy/data/types.
541
+ * Achievement Types
819
542
  *
820
- * @module types/map
821
- */
822
- /**
823
- * Allowed map interaction types.
824
- */
825
- type InteractionType = 'game_entry' | 'game_registry' | 'info' | 'teleport' | 'door_in' | 'door_out' | 'npc_interaction' | 'quest_trigger';
826
- /**
827
- * Metadata for interactive map elements.
828
- */
829
- interface MapElementMetadata {
830
- description?: string;
831
- title?: string;
832
- targetMapIdentifier?: string;
833
- targetSpawnTileX?: number;
834
- targetSpawnTileY?: number;
835
- sourceTiledObjects?: Record<string, unknown>[];
836
- [key: string]: unknown;
837
- }
838
- /**
839
- * Metadata describing how an item should be placed on the map.
543
+ * @module types/achievement
840
544
  */
841
- interface PlaceableItemMetadata {
842
- tilesWide?: number;
843
- tilesHigh?: number;
844
- flippable?: boolean;
845
- [key: string]: unknown;
545
+ type AchievementScopeType = 'daily' | 'weekly' | 'monthly' | 'yearly' | 'game' | 'global' | 'map' | 'level' | 'event';
546
+ declare enum AchievementCompletionType {
547
+ TIME_PLAYED_SESSION = "time_played_session",
548
+ INTERACTION = "interaction",
549
+ LEADERBOARD_RANK = "leaderboard_rank",
550
+ FIRST_SCORE = "first_score",
551
+ PERSONAL_BEST = "personal_best"
846
552
  }
847
- /**
848
- * Simplified map data for API responses.
849
- */
850
- interface MapData {
553
+ interface AchievementCurrent {
851
554
  id: string;
852
- identifier: string;
853
- displayName: string;
555
+ title: string;
854
556
  description?: string | null;
855
- metadata?: Record<string, unknown> | null;
557
+ scope: AchievementScopeType;
558
+ rewardCredits: number;
559
+ limit: number;
560
+ completionType: string;
561
+ completionConfig: unknown;
562
+ target: unknown;
563
+ active: boolean;
564
+ createdAt?: Date | null;
565
+ updatedAt?: Date | null;
566
+ status: 'available' | 'completed';
567
+ scopeKey: string;
568
+ windowStart: string;
569
+ windowEnd: string;
856
570
  }
857
- /**
858
- * Input for creating a map object.
859
- */
860
- interface CreateMapObjectData {
861
- itemId: string;
862
- worldX: number;
863
- worldY: number;
864
- width?: number;
865
- height?: number;
866
- rotation?: number;
867
- scale?: number;
868
- metadata?: Record<string, unknown>;
571
+ interface AchievementWithStatus {
572
+ id: string;
573
+ title: string;
574
+ description: string | null;
575
+ scope: AchievementScopeType;
576
+ rewardCredits: number;
577
+ limit: number;
578
+ completionType: string;
579
+ completionConfig: unknown;
580
+ target: unknown;
581
+ active: boolean;
582
+ createdAt: Date | null;
583
+ updatedAt: Date | null;
584
+ status: 'available' | 'completed';
585
+ scopeKey: string;
586
+ windowStart?: string;
587
+ windowEnd?: string;
869
588
  }
870
-
871
- /**
872
- * Character & Avatar Types
873
- *
874
- * Literal types for character system. Database row types are in @playcademy/data/types.
875
- *
876
- * @module types/character
877
- */
878
- /**
879
- * Character component categories.
880
- */
881
- type CharacterComponentType = 'body' | 'outfit' | 'hairstyle' | 'eyes' | 'accessory';
882
-
883
- /**
884
- * Level & Progression Types
885
- *
886
- * API response DTOs for level system. Database row types are in @playcademy/data/types.
887
- *
888
- * @module types/level
889
- */
890
- /**
891
- * Result of adding XP to a user.
892
- */
893
- interface XPAddResult {
894
- totalXP: number;
895
- newLevel: number;
896
- leveledUp: boolean;
897
- creditsAwarded: number;
898
- xpToNextLevel: number;
899
- }
900
- /**
901
- * Result of checking whether a level up occurred.
902
- */
903
- interface LevelUpCheckResult {
904
- newLevel: number;
905
- remainingXp: number;
906
- leveledUp: boolean;
907
- creditsAwarded: number;
908
- xpToNextLevel: number;
589
+ interface AchievementHistoryEntry {
590
+ achievementId: string;
591
+ title: string;
592
+ rewardCredits: number;
593
+ createdAt: Date;
594
+ scopeKey: string;
909
595
  }
910
- /**
911
- * Level progress API response.
912
- */
913
- interface LevelProgressResponse {
914
- level: number;
915
- currentXp: number;
916
- xpToNextLevel: number;
917
- totalXP: number;
596
+ interface AchievementProgressResponse {
597
+ achievementId: string;
598
+ status: 'completed' | 'already_completed';
599
+ rewardCredits: number;
600
+ scopeKey: string;
601
+ createdAt: Date;
918
602
  }
919
603
 
920
604
  /**
@@ -952,1202 +636,437 @@ interface NotificationStats {
952
636
  }
953
637
 
954
638
  /**
955
- * Shop Types
639
+ * User Types
956
640
  *
957
- * @module types/shop
958
- */
959
- /**
960
- * Currency display info for shop UI.
641
+ * Enums, DTOs and API response types. Database row types are in @playcademy/data/types.
642
+ *
643
+ * @module types/user
961
644
  */
962
- interface ShopCurrency {
645
+ type UserRoleEnumType = 'admin' | 'player' | 'developer' | 'teacher';
646
+ type DeveloperStatusEnumType = 'none' | 'pending' | 'approved';
647
+ type DeveloperStatusValue = DeveloperStatusEnumType;
648
+ type TimebackUserRole = 'administrator' | 'aide' | 'guardian' | 'parent' | 'proctor' | 'relative' | 'student' | 'teacher';
649
+ type TimebackOrgType = 'department' | 'school' | 'district' | 'local' | 'state' | 'national';
650
+ interface UserEnrollment {
651
+ gameId?: string;
652
+ courseId: string;
653
+ grade: number;
654
+ subject: string;
655
+ orgId?: string;
656
+ }
657
+ interface UserOrganization {
963
658
  id: string;
964
- symbol: string | null;
659
+ name: string | null;
660
+ type: TimebackOrgType | string;
965
661
  isPrimary: boolean;
966
- displayName?: string | null;
967
- imageUrl?: string | null;
662
+ }
663
+ interface TimebackStudentProfile {
664
+ role: TimebackUserRole;
665
+ organizations: UserOrganization[];
666
+ }
667
+ interface UserTimebackData extends TimebackStudentProfile {
668
+ id: string;
669
+ enrollments: UserEnrollment[];
968
670
  }
969
671
  /**
970
- * Shop item for display.
971
- * Combines Item fields (excluding createdAt) with shop listing data.
672
+ * OpenID Connect UserInfo claims (NOT a database row).
972
673
  */
973
- interface ShopDisplayItem {
974
- id: string;
975
- slug: string;
976
- gameId?: string | null;
977
- displayName: string;
978
- description?: string | null;
979
- type: string;
980
- isPlaceable: boolean;
981
- imageUrl?: string | null;
982
- metadata?: unknown;
983
- listingId: string;
984
- shopPrice: number;
985
- currencyId: string;
986
- currencySymbol?: string | null;
987
- currencyDisplayName?: string | null;
988
- currencyImageUrl?: string | null;
989
- stock?: number | null;
990
- sellBackPercentage?: number | null;
674
+ interface UserInfo {
675
+ sub: string;
676
+ email: string;
677
+ name: string | null;
678
+ email_verified?: boolean;
679
+ given_name?: string;
680
+ family_name?: string;
681
+ issuer?: string;
682
+ lti_roles?: unknown;
683
+ lti_context?: unknown;
684
+ lti_resource_link?: unknown;
685
+ timeback_id?: string;
686
+ }
687
+ interface DeveloperStatusResponse {
688
+ status: DeveloperStatusEnumType;
991
689
  }
992
690
  /**
993
- * Complete shop view response.
691
+ * Authenticated user for API responses.
692
+ * Differs from UserRow: omits timebackId, adds hasTimebackAccount and timeback.
994
693
  */
995
- interface ShopViewResponse {
996
- shopItems: ShopDisplayItem[];
997
- currencies: ShopCurrency[];
694
+ interface AuthenticatedUser {
695
+ id: string;
696
+ email: string;
697
+ emailVerified: boolean;
698
+ name: string | null;
699
+ image: string | null;
700
+ username: string | null;
701
+ role: UserRoleEnumType;
702
+ developerStatus: DeveloperStatusEnumType;
703
+ characterCreated: boolean;
704
+ createdAt: Date;
705
+ updatedAt: Date;
706
+ hasTimebackAccount: boolean;
707
+ timeback?: UserTimebackData;
708
+ }
709
+ interface GameUser {
710
+ id: string;
711
+ name: string | null;
712
+ role: UserRoleEnumType;
713
+ username: string | null;
714
+ email: string | null;
715
+ timeback?: UserTimebackData;
998
716
  }
999
717
 
1000
718
  /**
1001
- * Sprite Types
719
+ * Game Types
1002
720
  *
1003
- * JSON shapes for sprite configuration. Database row types are in @playcademy/data/types.
721
+ * Literal types and API DTOs. Database row types are in @playcademy/data/types.
1004
722
  *
1005
- * @module types/sprite
723
+ * @module types/game
1006
724
  */
725
+ type GameType = 'hosted' | 'external';
726
+ type GamePlatform = 'web' | 'godot' | 'unity' | (string & {});
1007
727
  /**
1008
- * Animation frame configuration.
728
+ * Game manifest file format (manifest.json).
729
+ * Note: createdAt is a string here because it's parsed from JSON file.
1009
730
  */
1010
- interface SpriteAnimationFrame {
1011
- row: number;
1012
- frameStart: number;
1013
- numFrames: number;
1014
- fps: number;
731
+ interface ManifestV1 {
732
+ version: string;
733
+ platform: string;
734
+ createdAt: string;
735
+ }
736
+ /** Log entry captured from seed worker console output */
737
+ interface SeedLogEntry {
738
+ /** Log level (log, warn, error, info) */
739
+ level: 'log' | 'warn' | 'error' | 'info';
740
+ /** Log message content */
741
+ message: string;
742
+ /** Milliseconds since seed execution started */
743
+ timestamp: number;
744
+ }
745
+ /** Structured error details from D1/SQLite errors */
746
+ interface SeedErrorDetails {
747
+ /** Error category code */
748
+ code?: 'CONSTRAINT_VIOLATION' | 'SQL_ERROR' | 'DATABASE_BUSY';
749
+ /** Table name involved in the error */
750
+ table?: string;
751
+ /** Constraint name or column that caused the error */
752
+ constraint?: string;
753
+ /** Specific constraint type */
754
+ constraintType?: 'UNIQUE' | 'FOREIGN_KEY' | 'NOT_NULL';
755
+ /** Token near syntax error */
756
+ nearToken?: string;
757
+ /** Specific error type within category */
758
+ errorType?: 'TABLE_NOT_FOUND' | 'SYNTAX_ERROR';
1015
759
  }
1016
760
  /**
1017
- * Sprite template data structure (stored in JSONB).
761
+ * API response for seed operations (what the API returns to the CLI).
762
+ *
763
+ * Extends the worker response with deployment metadata.
1018
764
  */
1019
- interface SpriteTemplateData {
1020
- tileSize: number;
1021
- tileHeight: number;
1022
- columns: number;
1023
- rows: number;
1024
- spacing: number;
1025
- animations: {
1026
- base_right: SpriteAnimationFrame;
1027
- base_up: SpriteAnimationFrame;
1028
- base_left: SpriteAnimationFrame;
1029
- base_down: SpriteAnimationFrame;
1030
- idle_right: SpriteAnimationFrame;
1031
- walk_right: SpriteAnimationFrame;
1032
- idle_up: SpriteAnimationFrame;
1033
- walk_up: SpriteAnimationFrame;
1034
- idle_left: SpriteAnimationFrame;
1035
- walk_left: SpriteAnimationFrame;
1036
- idle_down: SpriteAnimationFrame;
1037
- walk_down: SpriteAnimationFrame;
765
+ interface SeedResponse {
766
+ /** Whether the seed completed successfully */
767
+ success: boolean;
768
+ /** Unique identifier for the seed worker deployment */
769
+ deploymentId: string;
770
+ /** When the seed was executed (ISO 8601 string from JSON serialization) */
771
+ executedAt: string;
772
+ /** Captured console output from the seed script */
773
+ logs?: SeedLogEntry[];
774
+ /** Execution duration in milliseconds */
775
+ duration?: number;
776
+ /** Error message if seed failed */
777
+ error?: string;
778
+ /** Stack trace if seed failed */
779
+ stack?: string;
780
+ /** Structured error details if seed failed */
781
+ details?: SeedErrorDetails;
782
+ }
783
+ interface DomainValidationRecords {
784
+ ownership?: {
785
+ name?: string;
786
+ value?: string;
787
+ type?: string;
1038
788
  };
789
+ ssl?: {
790
+ txt_name?: string;
791
+ txt_value?: string;
792
+ }[];
1039
793
  }
794
+
1040
795
  /**
1041
- * Sprite sheet configuration with precomputed dimensions.
1042
- */
1043
- interface SpriteConfigWithDimensions {
1044
- textureUrl: string;
1045
- columns: number;
1046
- rows: number;
1047
- spriteWidth: number;
1048
- spriteHeight: number;
1049
- animations: Record<string, SpriteAnimationFrame>;
1050
- }
1051
-
1052
- /**
1053
- * Base error class for Cademy SDK specific errors.
796
+ * Leaderboard Types
797
+ *
798
+ * @module types/leaderboard
1054
799
  */
1055
- declare class PlaycademyError extends Error {
1056
- constructor(message: string);
800
+ type LeaderboardTimeframe = 'all_time' | 'monthly' | 'weekly' | 'daily';
801
+ interface LeaderboardOptions {
802
+ timeframe?: LeaderboardTimeframe;
803
+ limit?: number;
804
+ offset?: number;
805
+ gameId?: string;
806
+ }
807
+ interface LeaderboardEntry {
808
+ rank: number;
809
+ userId: string;
810
+ username: string;
811
+ userImage?: string | null;
812
+ score: number;
813
+ achievedAt: Date;
814
+ metadata?: Record<string, unknown>;
815
+ gameId?: string;
816
+ gameTitle?: string;
817
+ gameSlug?: string;
818
+ }
819
+ interface UserRank {
820
+ rank: number;
821
+ totalPlayers: number;
822
+ score: number;
823
+ percentile: number;
824
+ }
825
+ interface UserRankResponse {
826
+ rank: number;
827
+ score: number;
828
+ userId: string;
829
+ }
830
+ interface UserScore {
831
+ id: string;
832
+ score: number;
833
+ achievedAt: Date;
834
+ metadata?: Record<string, unknown>;
835
+ gameId: string;
836
+ gameTitle: string;
837
+ gameSlug: string;
1057
838
  }
1058
839
  /**
1059
- * Error codes returned by the API.
1060
- * These map to specific error types and HTTP status codes.
1061
- */
1062
- type ApiErrorCode = 'BAD_REQUEST' | 'UNAUTHORIZED' | 'FORBIDDEN' | 'ACCESS_DENIED' | 'NOT_FOUND' | 'METHOD_NOT_ALLOWED' | 'CONFLICT' | 'ALREADY_EXISTS' | 'GONE' | 'PRECONDITION_FAILED' | 'PAYLOAD_TOO_LARGE' | 'VALIDATION_FAILED' | 'TOO_MANY_REQUESTS' | 'RATE_LIMITED' | 'EXPIRED' | 'INTERNAL' | 'INTERNAL_ERROR' | 'NOT_IMPLEMENTED' | 'SERVICE_UNAVAILABLE' | 'TIMEOUT' | string;
1063
- /**
1064
- * Structure of error response bodies returned by API endpoints.
1065
- *
1066
- * @example
1067
- * ```json
1068
- * {
1069
- * "error": {
1070
- * "code": "NOT_FOUND",
1071
- * "message": "Item not found",
1072
- * "details": { "identifier": "abc123" }
1073
- * }
1074
- * }
1075
- * ```
840
+ * Leaderboard entry with required game context.
841
+ * Used when fetching leaderboards for a specific game.
1076
842
  */
1077
- interface ErrorResponseBody {
1078
- error?: {
1079
- code?: string;
1080
- message?: string;
1081
- details?: unknown;
1082
- };
843
+ interface GameLeaderboardEntry {
844
+ rank: number;
845
+ userId: string;
846
+ username: string;
847
+ userImage?: string | null;
848
+ score: number;
849
+ achievedAt: Date;
850
+ metadata?: Record<string, unknown>;
851
+ gameId: string;
852
+ gameTitle: string;
853
+ gameSlug: string;
1083
854
  }
855
+
1084
856
  /**
1085
- * API error thrown when a request fails.
857
+ * Inventory & Shop Types
1086
858
  *
1087
- * Contains structured error information from the API response:
1088
- * - `status` - HTTP status code (e.g., 404)
1089
- * - `code` - API error code (e.g., "NOT_FOUND")
1090
- * - `message` - Human-readable error message
1091
- * - `details` - Optional additional error context
859
+ * Literal types for inventory system. Database row types are in @playcademy/data/types.
1092
860
  *
1093
- * @example
1094
- * ```typescript
1095
- * try {
1096
- * await client.games.get('nonexistent')
1097
- * } catch (error) {
1098
- * if (error instanceof ApiError) {
1099
- * console.log(error.status) // 404
1100
- * console.log(error.code) // "NOT_FOUND"
1101
- * console.log(error.message) // "Game not found"
1102
- * console.log(error.details) // { identifier: "nonexistent" }
1103
- * }
1104
- * }
1105
- * ```
1106
- */
1107
- declare class ApiError extends Error {
1108
- /**
1109
- * API error code (e.g., "NOT_FOUND", "VALIDATION_FAILED").
1110
- * Use this for programmatic error handling.
1111
- */
1112
- readonly code: ApiErrorCode;
1113
- /**
1114
- * Additional error context from the API.
1115
- * Structure varies by error type (e.g., validation errors include field details).
1116
- */
1117
- readonly details: unknown;
1118
- /**
1119
- * Raw response body for debugging.
1120
- * @internal
1121
- */
1122
- readonly rawBody: unknown;
1123
- readonly status: number;
1124
- constructor(
1125
- /** HTTP status code */
1126
- status: number,
1127
- /** API error code */
1128
- code: ApiErrorCode,
1129
- /** Human-readable error message */
1130
- message: string,
1131
- /** Additional error context */
1132
- details?: unknown,
1133
- /** Raw response body */
1134
- rawBody?: unknown);
1135
- /**
1136
- * Create an ApiError from an HTTP response.
1137
- * Parses the structured error response from the API.
1138
- *
1139
- * @internal
1140
- */
1141
- static fromResponse(status: number, statusText: string, body: unknown): ApiError;
1142
- /**
1143
- * Check if this is a specific error type.
1144
- *
1145
- * @example
1146
- * ```typescript
1147
- * if (error.is('NOT_FOUND')) {
1148
- * // Handle not found
1149
- * } else if (error.is('VALIDATION_FAILED')) {
1150
- * // Handle validation error
1151
- * }
1152
- * ```
1153
- */
1154
- is(code: ApiErrorCode): boolean;
1155
- /**
1156
- * Check if this is a client error (4xx).
1157
- */
1158
- isClientError(): boolean;
1159
- /**
1160
- * Check if this is a server error (5xx).
1161
- */
1162
- isServerError(): boolean;
1163
- /**
1164
- * Check if this error is retryable.
1165
- * Server errors and rate limits are typically retryable.
1166
- */
1167
- isRetryable(): boolean;
1168
- }
1169
- /**
1170
- * Extracted error information for display purposes.
861
+ * @module types/inventory
1171
862
  */
1172
- interface ApiErrorInfo {
1173
- /** HTTP status code */
1174
- status: number;
1175
- /** API error code */
1176
- code: ApiErrorCode;
1177
- /** Human-readable error message */
1178
- message: string;
1179
- /** Additional error context */
1180
- details?: unknown;
1181
- }
1182
863
  /**
1183
- * Extract useful error information from an API error.
1184
- * Useful for displaying errors to users in a friendly way.
1185
- *
1186
- * @example
1187
- * ```typescript
1188
- * try {
1189
- * await client.shop.purchase(itemId)
1190
- * } catch (error) {
1191
- * const info = extractApiErrorInfo(error)
1192
- * if (info) {
1193
- * showToast(`Error: ${info.message}`)
1194
- * }
1195
- * }
1196
- * ```
864
+ * Item categories available on the platform.
1197
865
  */
1198
- declare function extractApiErrorInfo(error: unknown): ApiErrorInfo | null;
866
+ type ItemType = 'currency' | 'badge' | 'trophy' | 'collectible' | 'consumable' | 'unlock' | 'upgrade' | 'accessory' | 'other';
1199
867
 
1200
868
  /**
1201
- * Connection monitoring types
869
+ * Map & Placement Types
1202
870
  *
1203
- * Type definitions for connection state, configuration, and callbacks.
871
+ * Literal types and JSON shapes for maps. Database row types are in @playcademy/data/types.
872
+ *
873
+ * @module types/map
1204
874
  */
1205
875
  /**
1206
- * Possible connection states.
1207
- *
1208
- * - **online**: Connection is stable and healthy
1209
- * - **offline**: Complete loss of network connectivity
1210
- * - **degraded**: Connection is slow or experiencing intermittent issues
876
+ * Allowed map interaction types.
1211
877
  */
1212
- type ConnectionState = 'online' | 'offline' | 'degraded';
878
+ type InteractionType = 'game_entry' | 'game_registry' | 'info' | 'teleport' | 'door_in' | 'door_out' | 'npc_interaction' | 'quest_trigger';
1213
879
  /**
1214
- * Configuration options for ConnectionMonitor.
1215
- *
1216
- * @see {@link ConnectionMonitor} for usage
880
+ * Metadata for interactive map elements.
1217
881
  */
1218
- interface ConnectionMonitorConfig {
1219
- /** Base URL for heartbeat pings (e.g., 'https://api.playcademy.com') */
1220
- baseUrl: string;
1221
- /** How often to send heartbeat pings in milliseconds (default: 10000) */
1222
- heartbeatInterval?: number;
1223
- /** How long to wait for heartbeat response in milliseconds (default: 5000) */
1224
- heartbeatTimeout?: number;
1225
- /** Number of consecutive failures before triggering disconnect (default: 2) */
1226
- failureThreshold?: number;
1227
- /** Enable periodic heartbeat monitoring (default: true) */
1228
- enableHeartbeat?: boolean;
1229
- /** Enable browser online/offline event listeners (default: true) */
1230
- enableOfflineEvents?: boolean;
882
+ interface MapElementMetadata {
883
+ description?: string;
884
+ title?: string;
885
+ targetMapIdentifier?: string;
886
+ targetSpawnTileX?: number;
887
+ targetSpawnTileY?: number;
888
+ sourceTiledObjects?: Record<string, unknown>[];
889
+ [key: string]: unknown;
1231
890
  }
1232
891
  /**
1233
- * Callback function signature for connection state changes.
1234
- *
1235
- * @param state - The new connection state
1236
- * @param reason - Human-readable reason for the state change
892
+ * Metadata describing how an item should be placed on the map.
1237
893
  */
1238
- type ConnectionChangeCallback = (state: ConnectionState, reason: string) => void;
1239
-
894
+ interface PlaceableItemMetadata {
895
+ tilesWide?: number;
896
+ tilesHigh?: number;
897
+ flippable?: boolean;
898
+ [key: string]: unknown;
899
+ }
1240
900
  /**
1241
- * Connection Monitor
1242
- *
1243
- * Monitors network connectivity using multiple signals:
1244
- * 1. navigator.onLine - Instant offline detection
1245
- * 2. Periodic heartbeat - Detects slow/degraded connections
1246
- * 3. Request failure tracking - Piggybacks on actual API calls
1247
- *
1248
- * Designed for school WiFi environments where connections may be
1249
- * unstable or degraded without fully disconnecting.
901
+ * Simplified map data for API responses.
902
+ */
903
+ interface MapData {
904
+ id: string;
905
+ identifier: string;
906
+ displayName: string;
907
+ description?: string | null;
908
+ metadata?: Record<string, unknown> | null;
909
+ }
910
+ /**
911
+ * Input for creating a map object.
1250
912
  */
913
+ interface CreateMapObjectData {
914
+ itemId: string;
915
+ worldX: number;
916
+ worldY: number;
917
+ width?: number;
918
+ height?: number;
919
+ rotation?: number;
920
+ scale?: number;
921
+ metadata?: Record<string, unknown>;
922
+ }
1251
923
 
1252
924
  /**
1253
- * Monitors network connectivity using multiple signals and notifies callbacks of state changes.
1254
- *
1255
- * The ConnectionMonitor uses a multi-signal approach to detect connection issues:
1256
- *
1257
- * 1. **navigator.onLine events** - Instant detection of hard disconnects
1258
- * 2. **Heartbeat pings** - Periodic checks to detect slow/degraded connections
1259
- * 3. **Request failure tracking** - Piggybacks on actual API calls
1260
- *
1261
- * This comprehensive approach ensures reliable detection across different network
1262
- * failure modes common in school WiFi environments (hard disconnect, slow connection,
1263
- * intermittent failures).
925
+ * Character & Avatar Types
1264
926
  *
1265
- * @example
1266
- * ```typescript
1267
- * const monitor = new ConnectionMonitor({
1268
- * baseUrl: 'https://api.playcademy.com',
1269
- * heartbeatInterval: 10000, // Check every 10s
1270
- * failureThreshold: 2 // Trigger after 2 failures
1271
- * })
927
+ * Literal types for character system. Database row types are in @playcademy/data/types.
1272
928
  *
1273
- * monitor.onChange((state, reason) => {
1274
- * console.log(`Connection: ${state} - ${reason}`)
1275
- * })
929
+ * @module types/character
930
+ */
931
+ /**
932
+ * Character component categories.
933
+ */
934
+ type CharacterComponentType = 'body' | 'outfit' | 'hairstyle' | 'eyes' | 'accessory';
935
+
936
+ /**
937
+ * Level & Progression Types
1276
938
  *
1277
- * monitor.start()
1278
- * ```
939
+ * API response DTOs for level system. Database row types are in @playcademy/data/types.
1279
940
  *
1280
- * @see {@link ConnectionManagerConfig} for configuration options
941
+ * @module types/level
1281
942
  */
1282
- declare class ConnectionMonitor {
1283
- private state;
1284
- private callbacks;
1285
- private heartbeatInterval?;
1286
- private consecutiveFailures;
1287
- private isMonitoring;
1288
- private config;
1289
- /**
1290
- * Creates a new ConnectionMonitor instance.
1291
- *
1292
- * The monitor starts in a stopped state. Call `start()` to begin monitoring.
1293
- *
1294
- * @param config - Configuration options
1295
- * @param config.baseUrl - Base URL for heartbeat pings
1296
- * @param config.heartbeatInterval - How often to check (default: 10000ms)
1297
- * @param config.heartbeatTimeout - Request timeout (default: 5000ms)
1298
- * @param config.failureThreshold - Failures before triggering disconnect (default: 2)
1299
- * @param config.enableHeartbeat - Enable periodic checks (default: true)
1300
- * @param config.enableOfflineEvents - Listen to browser events (default: true)
1301
- */
1302
- constructor(config: ConnectionMonitorConfig);
1303
- /**
1304
- * Starts monitoring the connection state.
1305
- *
1306
- * Sets up event listeners and begins heartbeat checks based on configuration.
1307
- * Idempotent - safe to call multiple times.
1308
- */
1309
- start(): void;
1310
- /**
1311
- * Stops monitoring the connection state and cleans up resources.
1312
- *
1313
- * Removes event listeners and clears heartbeat intervals.
1314
- * Idempotent - safe to call multiple times.
1315
- */
1316
- stop(): void;
1317
- /**
1318
- * Registers a callback to be notified of all connection state changes.
1319
- *
1320
- * The callback fires for all state transitions: online → offline,
1321
- * offline → degraded, degraded → online, etc.
1322
- *
1323
- * @param callback - Function called with (state, reason) when connection changes
1324
- * @returns Cleanup function to unregister the callback
1325
- *
1326
- * @example
1327
- * ```typescript
1328
- * const cleanup = monitor.onChange((state, reason) => {
1329
- * console.log(`Connection: ${state}`)
1330
- * if (state === 'offline') {
1331
- * showReconnectingUI()
1332
- * }
1333
- * })
1334
- *
1335
- * // Later: cleanup() to unregister
1336
- * ```
1337
- */
1338
- onChange(callback: ConnectionChangeCallback): () => void;
1339
- /**
1340
- * Gets the current connection state.
1341
- *
1342
- * @returns The current state ('online', 'offline', or 'degraded')
1343
- */
1344
- getState(): ConnectionState;
1345
- /**
1346
- * Manually triggers an immediate connection check.
1347
- *
1348
- * Forces a heartbeat ping to verify connectivity right now, bypassing
1349
- * the normal interval. Useful before critical operations.
1350
- *
1351
- * @returns Promise resolving to the current connection state after the check
1352
- *
1353
- * @example
1354
- * ```typescript
1355
- * const state = await monitor.checkNow()
1356
- * if (state !== 'online') {
1357
- * alert('Please check your internet connection')
1358
- * }
1359
- * ```
1360
- */
1361
- checkNow(): Promise<ConnectionState>;
1362
- /**
1363
- * Reports a request failure for tracking.
1364
- *
1365
- * This should be called from your request wrapper whenever an API call fails.
1366
- * Only network errors are tracked (TypeError, fetch failures) - HTTP error
1367
- * responses (4xx, 5xx) are ignored.
1368
- *
1369
- * After consecutive failures exceed the threshold, the monitor transitions
1370
- * to 'degraded' or 'offline' state.
1371
- *
1372
- * @param error - The error from the failed request
1373
- *
1374
- * @example
1375
- * ```typescript
1376
- * try {
1377
- * await fetch('/api/data')
1378
- * } catch (error) {
1379
- * monitor.reportRequestFailure(error)
1380
- * throw error
1381
- * }
1382
- * ```
1383
- */
1384
- reportRequestFailure(error: unknown): void;
1385
- /**
1386
- * Reports a successful request.
1387
- *
1388
- * This should be called from your request wrapper whenever an API call succeeds.
1389
- * Resets the consecutive failure counter and transitions from 'degraded' to
1390
- * 'online' if the connection has recovered.
1391
- *
1392
- * @example
1393
- * ```typescript
1394
- * try {
1395
- * const result = await fetch('/api/data')
1396
- * monitor.reportRequestSuccess()
1397
- * return result
1398
- * } catch (error) {
1399
- * monitor.reportRequestFailure(error)
1400
- * throw error
1401
- * }
1402
- * ```
1403
- */
1404
- reportRequestSuccess(): void;
1405
- private _detectInitialState;
1406
- private _handleOnline;
1407
- private _handleOffline;
1408
- private _startHeartbeat;
1409
- private _performHeartbeat;
1410
- private _handleHeartbeatFailure;
1411
- private _setState;
943
+ /**
944
+ * Result of adding XP to a user.
945
+ */
946
+ interface XPAddResult {
947
+ totalXP: number;
948
+ newLevel: number;
949
+ leveledUp: boolean;
950
+ creditsAwarded: number;
951
+ xpToNextLevel: number;
952
+ }
953
+ /**
954
+ * Result of checking whether a level up occurred.
955
+ */
956
+ interface LevelUpCheckResult {
957
+ newLevel: number;
958
+ remainingXp: number;
959
+ leveledUp: boolean;
960
+ creditsAwarded: number;
961
+ xpToNextLevel: number;
962
+ }
963
+ /**
964
+ * Level progress API response.
965
+ */
966
+ interface LevelProgressResponse {
967
+ level: number;
968
+ currentXp: number;
969
+ xpToNextLevel: number;
970
+ totalXP: number;
1412
971
  }
1413
972
 
1414
973
  /**
1415
- * Connection Manager
1416
- *
1417
- * Manages connection monitoring and integrates it with the Playcademy client.
1418
- * Handles event wiring, state management, and disconnect callbacks.
974
+ * Shop Types
1419
975
  *
1420
- * In iframe mode, disables local monitoring and listens to platform connection
1421
- * state broadcasts instead (avoids duplicate heartbeats).
976
+ * @module types/shop
1422
977
  */
1423
-
1424
978
  /**
1425
- * Configuration for the ConnectionManager.
979
+ * Currency display info for shop UI.
1426
980
  */
1427
- interface ConnectionManagerConfig {
1428
- /** Base URL for API requests (used for heartbeat pings) */
1429
- baseUrl: string;
1430
- /** Authentication context (iframe vs standalone) for alert routing */
1431
- authContext?: {
1432
- isInIframe: boolean;
1433
- };
1434
- /** Handler to call when connection issues are detected */
1435
- onDisconnect?: DisconnectHandler;
1436
- /** Callback to emit connection change events to the client */
1437
- onConnectionChange?: (state: ConnectionState, reason: string) => void;
981
+ interface ShopCurrency {
982
+ id: string;
983
+ symbol: string | null;
984
+ isPrimary: boolean;
985
+ displayName?: string | null;
986
+ imageUrl?: string | null;
1438
987
  }
1439
988
  /**
1440
- * Manages connection monitoring for the Playcademy client.
1441
- *
1442
- * The ConnectionManager serves as an integration layer between the low-level
1443
- * ConnectionMonitor and the PlaycademyClient. It handles:
1444
- * - Event wiring and coordination
1445
- * - Disconnect callbacks with context
1446
- * - Platform-level alert integration
1447
- * - Request success/failure tracking
1448
- *
1449
- * This class is used internally by PlaycademyClient and typically not
1450
- * instantiated directly by game developers.
1451
- *
1452
- * @see {@link ConnectionMonitor} for the underlying monitoring implementation
1453
- * @see {@link PlaycademyClient.onDisconnect} for the public API
989
+ * Shop item for display.
990
+ * Combines Item fields (excluding createdAt) with shop listing data.
1454
991
  */
1455
- declare class ConnectionManager {
1456
- private monitor?;
1457
- private authContext?;
1458
- private disconnectHandler?;
1459
- private connectionChangeCallback?;
1460
- private currentState;
1461
- private additionalDisconnectHandlers;
1462
- /**
1463
- * Creates a new ConnectionManager instance.
1464
- *
1465
- * @param config - Configuration options for the manager
1466
- *
1467
- * @example
1468
- * ```typescript
1469
- * const manager = new ConnectionManager({
1470
- * baseUrl: 'https://api.playcademy.com',
1471
- * authContext: { isInIframe: false },
1472
- * onDisconnect: (context) => {
1473
- * console.log(`Disconnected: ${context.state}`)
1474
- * },
1475
- * onConnectionChange: (state, reason) => {
1476
- * console.log(`Connection changed: ${state}`)
1477
- * }
1478
- * })
1479
- * ```
1480
- */
1481
- constructor(config: ConnectionManagerConfig);
1482
- /**
1483
- * Gets the current connection state.
1484
- *
1485
- * @returns The current connection state ('online', 'offline', or 'degraded')
1486
- *
1487
- * @example
1488
- * ```typescript
1489
- * const state = manager.getState()
1490
- * if (state === 'offline') {
1491
- * console.log('No connection')
1492
- * }
1493
- * ```
1494
- */
1495
- getState(): ConnectionState;
1496
- /**
1497
- * Manually triggers a connection check immediately.
1498
- *
1499
- * Forces a heartbeat ping to verify the current connection status.
1500
- * Useful when you need to check connectivity before a critical operation.
1501
- *
1502
- * In iframe mode, this returns the last known state from platform.
1503
- *
1504
- * @returns Promise resolving to the current connection state
1505
- *
1506
- * @example
1507
- * ```typescript
1508
- * const state = await manager.checkNow()
1509
- * if (state === 'online') {
1510
- * await performCriticalOperation()
1511
- * }
1512
- * ```
1513
- */
1514
- checkNow(): Promise<ConnectionState>;
1515
- /**
1516
- * Reports a successful API request to the connection monitor.
1517
- *
1518
- * This resets the consecutive failure counter and transitions from
1519
- * 'degraded' to 'online' state if applicable.
1520
- *
1521
- * Typically called automatically by the SDK's request wrapper.
1522
- * No-op in iframe mode (platform handles monitoring).
1523
- */
1524
- reportRequestSuccess(): void;
1525
- /**
1526
- * Reports a failed API request to the connection monitor.
1527
- *
1528
- * Only network errors are tracked (not 4xx/5xx HTTP responses).
1529
- * After consecutive failures exceed the threshold, the state transitions
1530
- * to 'degraded' or 'offline'.
1531
- *
1532
- * Typically called automatically by the SDK's request wrapper.
1533
- * No-op in iframe mode (platform handles monitoring).
1534
- *
1535
- * @param error - The error from the failed request
1536
- */
1537
- reportRequestFailure(error: unknown): void;
1538
- /**
1539
- * Registers a callback to be called when connection issues are detected.
1540
- *
1541
- * The callback only fires for 'offline' and 'degraded' states, not when
1542
- * recovering to 'online'. This provides a clean API for games to handle
1543
- * disconnect scenarios without being notified of every state change.
1544
- *
1545
- * Works in both iframe and standalone modes transparently.
1546
- *
1547
- * @param callback - Function to call when connection degrades
1548
- * @returns Cleanup function to unregister the callback
1549
- *
1550
- * @example
1551
- * ```typescript
1552
- * const cleanup = manager.onDisconnect(({ state, reason, displayAlert }) => {
1553
- * if (state === 'offline') {
1554
- * displayAlert?.('Connection lost. Saving your progress...', { type: 'error' })
1555
- * saveGameState()
1556
- * }
1557
- * })
1558
- *
1559
- * // Later: cleanup() to unregister
1560
- * ```
1561
- */
1562
- onDisconnect(callback: DisconnectHandler): () => void;
1563
- /**
1564
- * Stops connection monitoring and performs cleanup.
1565
- *
1566
- * Removes event listeners and clears heartbeat intervals.
1567
- * Should be called when the client is being destroyed.
1568
- */
1569
- stop(): void;
1570
- /**
1571
- * Sets up listener for platform connection state broadcasts (iframe mode only).
1572
- */
1573
- private _setupPlatformListener;
1574
- /**
1575
- * Handles connection state changes from the monitor or platform.
1576
- *
1577
- * Coordinates between:
1578
- * 1. Emitting events to the client (for client.on('connectionChange'))
1579
- * 2. Calling the disconnect handler if configured
1580
- * 3. Calling any additional handlers registered via onDisconnect()
1581
- *
1582
- * @param state - The new connection state
1583
- * @param reason - Human-readable reason for the state change
1584
- */
1585
- private _handleConnectionChange;
992
+ interface ShopDisplayItem {
993
+ id: string;
994
+ slug: string;
995
+ gameId?: string | null;
996
+ displayName: string;
997
+ description?: string | null;
998
+ type: string;
999
+ isPlaceable: boolean;
1000
+ imageUrl?: string | null;
1001
+ metadata?: unknown;
1002
+ listingId: string;
1003
+ shopPrice: number;
1004
+ currencyId: string;
1005
+ currencySymbol?: string | null;
1006
+ currencyDisplayName?: string | null;
1007
+ currencyImageUrl?: string | null;
1008
+ stock?: number | null;
1009
+ sellBackPercentage?: number | null;
1586
1010
  }
1587
-
1588
1011
  /**
1589
- * @fileoverview Playcademy Messaging System
1590
- *
1591
- * This file implements a unified messaging system for the Playcademy platform that handles
1592
- * communication between different contexts:
1593
- *
1594
- * 1. **Iframe-to-Parent Communication**: When games run inside iframes (production/development),
1595
- * they need to communicate with the parent window using postMessage API
1596
- *
1597
- * 2. **Local Communication**: When games run in the same context (local development),
1598
- * they use CustomEvents for internal messaging
1599
- *
1600
- * The system automatically detects the runtime environment and chooses the appropriate
1601
- * transport method, abstracting this complexity from the developer.
1602
- *
1603
- * **Architecture Overview**:
1604
- * - Games run in iframes for security and isolation
1605
- * - Parent window (Playcademy shell) manages game lifecycle
1606
- * - Messages flow bidirectionally between parent and iframe
1607
- * - Local development mode simulates this architecture without iframes
1012
+ * Complete shop view response.
1608
1013
  */
1014
+ interface ShopViewResponse {
1015
+ shopItems: ShopDisplayItem[];
1016
+ currencies: ShopCurrency[];
1017
+ }
1609
1018
 
1610
1019
  /**
1611
- * Enumeration of all message types used in the Playcademy messaging system.
1612
- *
1613
- * **Message Flow Patterns**:
1020
+ * Sprite Types
1614
1021
  *
1615
- * **Parent Game (Overworld Game)**:
1616
- * - INIT: Provides game with authentication token and configuration
1617
- * - TOKEN_REFRESH: Updates game's authentication token before expiry
1618
- * - PAUSE/RESUME: Controls game execution state
1619
- * - FORCE_EXIT: Immediately terminates the game
1620
- * - OVERLAY: Shows/hides UI overlays over the game
1022
+ * JSON shapes for sprite configuration. Database row types are in @playcademy/data/types.
1621
1023
  *
1622
- * **Game → Parent (Game → Overworld)**:
1623
- * - READY: Game has loaded and is ready to receive messages
1624
- * - EXIT: Game requests to be closed (user clicked exit, game ended, etc.)
1625
- * - TELEMETRY: Game reports performance metrics (FPS, memory usage, etc.)
1024
+ * @module types/sprite
1626
1025
  */
1627
- declare enum MessageEvents {
1628
- /**
1629
- * Initializes the game with authentication context and configuration.
1630
- * Sent immediately after game iframe loads.
1631
- * Payload:
1632
- * - `baseUrl`: string
1633
- * - `token`: string
1634
- * - `gameId`: string
1635
- */
1636
- INIT = "PLAYCADEMY_INIT",
1637
- /**
1638
- * Updates the game's authentication token before it expires.
1639
- * Sent periodically to maintain valid authentication.
1640
- * Payload:
1641
- * - `token`: string
1642
- * - `exp`: number
1643
- */
1644
- TOKEN_REFRESH = "PLAYCADEMY_TOKEN_REFRESH",
1645
- /**
1646
- * Pauses game execution (e.g., when user switches tabs).
1647
- * Game should pause timers, animations, and user input.
1648
- * Payload: void
1649
- */
1650
- PAUSE = "PLAYCADEMY_PAUSE",
1651
- /**
1652
- * Resumes game execution after being paused.
1653
- * Game should restore timers, animations, and user input.
1654
- * Payload: void
1655
- */
1656
- RESUME = "PLAYCADEMY_RESUME",
1657
- /**
1658
- * Forces immediate game termination (emergency exit).
1659
- * Game should clean up resources and exit immediately.
1660
- * Payload: void
1661
- */
1662
- FORCE_EXIT = "PLAYCADEMY_FORCE_EXIT",
1663
- /**
1664
- * Shows or hides UI overlays over the game.
1665
- * Game may need to pause or adjust rendering accordingly.
1666
- * Payload: boolean (true = show overlay, false = hide overlay)
1667
- */
1668
- OVERLAY = "PLAYCADEMY_OVERLAY",
1669
- /**
1670
- * Broadcasts connection state changes to games.
1671
- * Sent by platform when network connectivity changes.
1672
- * Payload:
1673
- * - `state`: 'online' | 'offline' | 'degraded'
1674
- * - `reason`: string
1675
- */
1676
- CONNECTION_STATE = "PLAYCADEMY_CONNECTION_STATE",
1677
- /**
1678
- * Game has finished loading and is ready to receive messages.
1679
- * Sent once after game initialization is complete.
1680
- * Payload: void
1681
- */
1682
- READY = "PLAYCADEMY_READY",
1683
- /**
1684
- * Game requests to be closed/exited.
1685
- * Sent when user clicks exit button or game naturally ends.
1686
- * Payload: void
1687
- */
1688
- EXIT = "PLAYCADEMY_EXIT",
1689
- /**
1690
- * Game reports performance telemetry data.
1691
- * Sent periodically for monitoring and analytics.
1692
- * Payload:
1693
- * - `fps`: number
1694
- * - `mem`: number
1695
- */
1696
- TELEMETRY = "PLAYCADEMY_TELEMETRY",
1697
- /**
1698
- * Game reports key events to parent.
1699
- * Sent when certain keys are pressed within the game iframe.
1700
- * Payload:
1701
- * - `key`: string
1702
- * - `code?`: string
1703
- * - `type`: 'keydown' | 'keyup'
1704
- */
1705
- KEY_EVENT = "PLAYCADEMY_KEY_EVENT",
1706
- /**
1707
- * Game requests platform to display an alert.
1708
- * Sent when connection issues are detected or other important events occur.
1709
- * Payload:
1710
- * - `message`: string
1711
- * - `options`: `{ type?: 'info' | 'warning' | 'error', duration?: number }`
1712
- */
1713
- DISPLAY_ALERT = "PLAYCADEMY_DISPLAY_ALERT",
1714
- /**
1715
- * Notifies about authentication state changes.
1716
- * Can be sent in both directions depending on auth flow.
1717
- * Payload:
1718
- * - `authenticated`: boolean
1719
- * - `user`: UserInfo | null
1720
- * - `error`: Error | null
1721
- */
1722
- AUTH_STATE_CHANGE = "PLAYCADEMY_AUTH_STATE_CHANGE",
1723
- /**
1724
- * OAuth callback data from popup/new-tab windows.
1725
- * Sent from popup window back to parent after OAuth completes.
1726
- * Payload:
1727
- * - `code`: string (OAuth authorization code)
1728
- * - `state`: string (OAuth state for CSRF protection)
1729
- * - `error`: string | null (OAuth error if any)
1730
- */
1731
- AUTH_CALLBACK = "PLAYCADEMY_AUTH_CALLBACK"
1732
- }
1733
1026
  /**
1734
- * Type definition for message handler functions.
1735
- * These functions are called when a specific message type is received.
1736
- *
1737
- * @template T - The type of payload data the handler expects
1738
- * @param payload - The data sent with the message
1027
+ * Animation frame configuration.
1739
1028
  */
1740
- type MessageHandler<T = unknown> = (payload: T) => void;
1029
+ interface SpriteAnimationFrame {
1030
+ row: number;
1031
+ frameStart: number;
1032
+ numFrames: number;
1033
+ fps: number;
1034
+ }
1741
1035
  /**
1742
- * Type mapping that defines the payload structure for each message type.
1743
- * This ensures type safety when sending and receiving messages.
1744
- *
1745
- * **Usage Examples**:
1746
- * ```typescript
1747
- * // Type-safe message sending
1748
- * messaging.send(MessageEvents.INIT, { baseUrl: '/api', token: 'abc', gameId: '123' })
1749
- *
1750
- * // Type-safe message handling
1751
- * messaging.listen(MessageEvents.TOKEN_REFRESH, ({ token, exp }) => {
1752
- * console.log(`New token expires at: ${new Date(exp)}`)
1753
- * })
1754
- * ```
1036
+ * Sprite template data structure (stored in JSONB).
1755
1037
  */
1756
- interface MessageEventMap {
1757
- /** Game initialization context with API endpoint, auth token, and game ID */
1758
- [MessageEvents.INIT]: GameContextPayload;
1759
- /** Token refresh data with new token and expiration timestamp */
1760
- [MessageEvents.TOKEN_REFRESH]: TokenRefreshPayload;
1761
- /** Pause message has no payload data */
1762
- [MessageEvents.PAUSE]: void;
1763
- /** Resume message has no payload data */
1764
- [MessageEvents.RESUME]: void;
1765
- /** Force exit message has no payload data */
1766
- [MessageEvents.FORCE_EXIT]: void;
1767
- /** Overlay visibility state (true = show, false = hide) */
1768
- [MessageEvents.OVERLAY]: boolean;
1769
- /** Connection state change from platform */
1770
- [MessageEvents.CONNECTION_STATE]: ConnectionStatePayload;
1771
- /** Ready message has no payload data */
1772
- [MessageEvents.READY]: void;
1773
- /** Exit message has no payload data */
1774
- [MessageEvents.EXIT]: void;
1775
- /** Performance telemetry data */
1776
- [MessageEvents.TELEMETRY]: TelemetryPayload;
1777
- /** Key event data */
1778
- [MessageEvents.KEY_EVENT]: KeyEventPayload;
1779
- /** Display alert request from game */
1780
- [MessageEvents.DISPLAY_ALERT]: DisplayAlertPayload;
1781
- /** Authentication state change notification */
1782
- [MessageEvents.AUTH_STATE_CHANGE]: AuthStateChangePayload;
1783
- /** OAuth callback data from popup/new-tab windows */
1784
- [MessageEvents.AUTH_CALLBACK]: AuthCallbackPayload;
1038
+ interface SpriteTemplateData {
1039
+ tileSize: number;
1040
+ tileHeight: number;
1041
+ columns: number;
1042
+ rows: number;
1043
+ spacing: number;
1044
+ animations: {
1045
+ base_right: SpriteAnimationFrame;
1046
+ base_up: SpriteAnimationFrame;
1047
+ base_left: SpriteAnimationFrame;
1048
+ base_down: SpriteAnimationFrame;
1049
+ idle_right: SpriteAnimationFrame;
1050
+ walk_right: SpriteAnimationFrame;
1051
+ idle_up: SpriteAnimationFrame;
1052
+ walk_up: SpriteAnimationFrame;
1053
+ idle_left: SpriteAnimationFrame;
1054
+ walk_left: SpriteAnimationFrame;
1055
+ idle_down: SpriteAnimationFrame;
1056
+ walk_down: SpriteAnimationFrame;
1057
+ };
1785
1058
  }
1786
1059
  /**
1787
- * **PlaycademyMessaging Class**
1788
- *
1789
- * This is the core messaging system that handles all communication in the Playcademy platform.
1790
- * It automatically detects the runtime environment and chooses the appropriate transport method.
1791
- *
1792
- * **Key Features**:
1793
- * 1. **Automatic Transport Selection**: Detects iframe vs local context and uses appropriate method
1794
- * 2. **Type Safety**: Full TypeScript support with payload type checking
1795
- * 3. **Bidirectional Communication**: Handles both parent→game and game→parent messages
1796
- * 4. **Event Cleanup**: Proper listener management to prevent memory leaks
1797
- *
1798
- * **Transport Methods**:
1799
- * - **postMessage**: Used for iframe-to-parent communication (production/development)
1800
- * - **CustomEvent**: Used for local same-context communication (local development)
1801
- *
1802
- * **Runtime Detection Logic**:
1803
- * - If `window.self !== window.top`: We're in an iframe, use postMessage
1804
- * - If `window.self === window.top`: We're in the same context, use CustomEvent
1805
- *
1806
- * **Example Usage**:
1807
- * ```typescript
1808
- * // Send a message (automatically chooses transport)
1809
- * messaging.send(MessageEvents.READY, undefined)
1810
- *
1811
- * // Listen for messages (handles both transports)
1812
- * messaging.listen(MessageEvents.INIT, (payload) => {
1813
- * console.log('Game initialized with:', payload)
1814
- * })
1815
- *
1816
- * // Clean up listeners
1817
- * messaging.unlisten(MessageEvents.INIT, handler)
1818
- * ```
1060
+ * Sprite sheet configuration with precomputed dimensions.
1819
1061
  */
1820
- declare class PlaycademyMessaging {
1821
- /**
1822
- * Internal storage for message listeners.
1823
- *
1824
- * **Structure Explanation**:
1825
- * - Outer Map: MessageEvents → Map of handlers for that event type
1826
- * - Inner Map: MessageHandler → Object containing both listener types
1827
- * - Object: { postMessage: EventListener, customEvent: EventListener }
1828
- *
1829
- * **Why Two Listeners Per Handler?**:
1830
- * Since we don't know at registration time which transport will be used,
1831
- * we register both a postMessage listener and a CustomEvent listener.
1832
- * The appropriate one will be triggered based on the runtime context.
1833
- */
1834
- private listeners;
1835
- /**
1836
- * **Send Message Method**
1837
- *
1838
- * Sends a message using the appropriate transport method based on the runtime context.
1839
- * This is the main public API for sending messages in the Playcademy system.
1840
- *
1841
- * **How It Works**:
1842
- * 1. Analyzes the message type and current runtime context
1843
- * 2. Determines if we're in an iframe and if this message should use postMessage
1844
- * 3. Routes to the appropriate transport method (postMessage or CustomEvent)
1845
- *
1846
- * **Transport Selection Logic**:
1847
- * - **postMessage**: Used when game is in iframe and sending to parent, OR when target window is specified
1848
- * - **CustomEvent**: Used for local/same-context communication
1849
- *
1850
- * **Type Safety**:
1851
- * The generic type parameter `K` ensures that the payload type matches
1852
- * the expected type for the given message event.
1853
- *
1854
- * @template K - The message event type (ensures type safety)
1855
- * @param type - The message event type to send
1856
- * @param payload - The data to send with the message (type-checked)
1857
- * @param options - Optional configuration for message sending
1858
- *
1859
- * @example
1860
- * ```typescript
1861
- * // Send game ready signal (no payload)
1862
- * messaging.send(MessageEvents.READY, undefined)
1863
- *
1864
- * // Send telemetry data (typed payload)
1865
- * messaging.send(MessageEvents.TELEMETRY, { fps: 60, mem: 128 })
1866
- *
1867
- * // Send to specific iframe window (parent to iframe communication)
1868
- * messaging.send(MessageEvents.INIT, { baseUrl, token, gameId }, {
1869
- * target: iframe.contentWindow,
1870
- * origin: '*'
1871
- * })
1872
- *
1873
- * // TypeScript will error if payload type is wrong
1874
- * messaging.send(MessageEvents.INIT, "wrong type") // ❌ Error
1875
- * ```
1876
- */
1877
- send<K extends MessageEvents>(type: K, payload: MessageEventMap[K], options?: {
1878
- target?: Window;
1879
- origin?: string;
1880
- }): void;
1881
- /**
1882
- * **Listen for Messages Method**
1883
- *
1884
- * Registers a message listener that will be called when the specified message type is received.
1885
- * This method automatically handles both postMessage and CustomEvent sources.
1886
- *
1887
- * **Why Register Two Listeners?**:
1888
- * Since we don't know at registration time which transport method will be used to send
1889
- * messages, we register listeners for both possible sources:
1890
- * 1. **postMessage listener**: Handles messages from iframe-to-parent communication
1891
- * 2. **CustomEvent listener**: Handles messages from local same-context communication
1892
- *
1893
- * **Message Processing**:
1894
- * - **postMessage**: Extracts data from `event.data.payload` or `event.data`
1895
- * - **CustomEvent**: Extracts data from `event.detail`
1896
- *
1897
- * **Type Safety**:
1898
- * The handler function receives the correctly typed payload based on the message type.
1899
- *
1900
- * @template K - The message event type (ensures type safety)
1901
- * @param type - The message event type to listen for
1902
- * @param handler - Function to call when the message is received
1903
- *
1904
- * @example
1905
- * ```typescript
1906
- * // Listen for game initialization
1907
- * messaging.listen(MessageEvents.INIT, (payload) => {
1908
- * // payload is automatically typed as GameContextPayload
1909
- * console.log(`Game ${payload.gameId} initialized`)
1910
- * console.log(`API base URL: ${payload.baseUrl}`)
1911
- * })
1912
- *
1913
- * // Listen for token refresh
1914
- * messaging.listen(MessageEvents.TOKEN_REFRESH, ({ token, exp }) => {
1915
- * // payload is automatically typed as { token: string; exp: number }
1916
- * updateAuthToken(token)
1917
- * scheduleTokenRefresh(exp)
1918
- * })
1919
- * ```
1920
- */
1921
- listen<K extends MessageEvents>(type: K, handler: MessageHandler<MessageEventMap[K]>): void;
1922
- /**
1923
- * **Remove Message Listener Method**
1924
- *
1925
- * Removes a previously registered message listener to prevent memory leaks and unwanted callbacks.
1926
- * This method cleans up both the postMessage and CustomEvent listeners that were registered.
1927
- *
1928
- * **Why Clean Up Both Listeners?**:
1929
- * When we registered the listener with `listen()`, we created two browser event listeners:
1930
- * 1. A 'message' event listener for postMessage communication
1931
- * 2. A custom event listener for local CustomEvent communication
1932
- *
1933
- * Both must be removed to prevent memory leaks and ensure the handler is completely unregistered.
1934
- *
1935
- * **Memory Management**:
1936
- * - Removes both event listeners from the browser
1937
- * - Cleans up internal tracking maps
1938
- * - If no more handlers exist for a message type, removes the entire type entry
1939
- *
1940
- * **Safe to Call Multiple Times**:
1941
- * This method is idempotent - calling it multiple times with the same handler is safe.
1942
- *
1943
- * @template K - The message event type (ensures type safety)
1944
- * @param type - The message event type to stop listening for
1945
- * @param handler - The exact handler function that was passed to listen()
1946
- *
1947
- * @example
1948
- * ```typescript
1949
- * // Register a handler
1950
- * const handleInit = (payload) => console.log('Game initialized')
1951
- * messaging.listen(MessageEvents.INIT, handleInit)
1952
- *
1953
- * // Later, remove the handler
1954
- * messaging.unlisten(MessageEvents.INIT, handleInit)
1955
- *
1956
- * // Safe to call multiple times
1957
- * messaging.unlisten(MessageEvents.INIT, handleInit) // No error
1958
- * ```
1959
- */
1960
- unlisten<K extends MessageEvents>(type: K, handler: MessageHandler<MessageEventMap[K]>): void;
1961
- /**
1962
- * **Get Messaging Context Method**
1963
- *
1964
- * Analyzes the current runtime environment and message type to determine the appropriate
1965
- * transport method and configuration for sending messages.
1966
- *
1967
- * **Runtime Environment Detection**:
1968
- * The method detects whether the code is running in an iframe by comparing:
1969
- * - `window.self`: Reference to the current window
1970
- * - `window.top`: Reference to the topmost window in the hierarchy
1971
- *
1972
- * If they're different, we're in an iframe. If they're the same, we're in the top-level window.
1973
- *
1974
- * **Message Direction Analysis**:
1975
- * Different message types flow in different directions:
1976
- * - **Game → Parent**: READY, EXIT, TELEMETRY (use postMessage when in iframe)
1977
- * - **Parent → Game**: INIT, TOKEN_REFRESH, PAUSE, etc. (use CustomEvent in local dev, or postMessage with target)
1978
- *
1979
- * **Cross-Context Communication**:
1980
- * The messaging system supports cross-context targeting through the optional `target` parameter.
1981
- * When a target window is specified, postMessage is used regardless of the current context.
1982
- * This enables parent-to-iframe communication through the unified messaging API.
1983
- *
1984
- * **Transport Selection Logic**:
1985
- * - **postMessage**: Used when game is in iframe AND sending to parent, OR when target window is specified
1986
- * - **CustomEvent**: Used for all other cases (local development, same-context communication)
1987
- *
1988
- * **Security Considerations**:
1989
- * The origin is currently set to '*' for development convenience, but should be
1990
- * configurable for production security.
1991
- *
1992
- * @param eventType - The message event type being sent
1993
- * @returns Configuration object with transport method and target information
1994
- *
1995
- * @example
1996
- * ```typescript
1997
- * // In iframe sending READY to parent
1998
- * const context = getMessagingContext(MessageEvents.READY)
1999
- * // Returns: { shouldUsePostMessage: true, target: window.parent, origin: '*' }
2000
- *
2001
- * // In same context sending INIT
2002
- * const context = getMessagingContext(MessageEvents.INIT)
2003
- * // Returns: { shouldUsePostMessage: false, target: undefined, origin: '*' }
2004
- * ```
2005
- */
2006
- private getMessagingContext;
2007
- /**
2008
- * **Send Via PostMessage Method**
2009
- *
2010
- * Sends a message using the browser's postMessage API for iframe-to-parent communication.
2011
- * This method is used when the game is running in an iframe and needs to communicate
2012
- * with the parent window (the Playcademy shell).
2013
- *
2014
- * **PostMessage Protocol**:
2015
- * The postMessage API is the standard way for iframes to communicate with their parent.
2016
- * It's secure, cross-origin capable, and designed specifically for this use case.
2017
- *
2018
- * **Message Structure**:
2019
- * The method creates a message object with the following structure:
2020
- * - `type`: The message event type (e.g., 'PLAYCADEMY_READY')
2021
- * - `payload`: The message data (if any)
2022
- *
2023
- * **Payload Handling**:
2024
- * - **All payloads**: Wrapped in a `payload` property for consistency (e.g., { type, payload: data })
2025
- * - **Undefined payloads**: Only the type is sent (e.g., { type })
2026
- *
2027
- * **Security**:
2028
- * The origin parameter controls which domains can receive the message.
2029
- * Currently set to '*' for development, but should be restricted in production.
2030
- *
2031
- * @template K - The message event type (ensures type safety)
2032
- * @param type - The message event type to send
2033
- * @param payload - The data to send with the message
2034
- * @param target - The target window (defaults to parent window)
2035
- * @param origin - The allowed origin for the message (defaults to '*')
2036
- *
2037
- * @example
2038
- * ```typescript
2039
- * // Send ready signal (no payload)
2040
- * sendViaPostMessage(MessageEvents.READY, undefined)
2041
- * // Sends: { type: 'PLAYCADEMY_READY' }
2042
- *
2043
- * // Send telemetry data (object payload)
2044
- * sendViaPostMessage(MessageEvents.TELEMETRY, { fps: 60, mem: 128 })
2045
- * // Sends: { type: 'PLAYCADEMY_TELEMETRY', payload: { fps: 60, mem: 128 } }
2046
- *
2047
- * // Send overlay state (primitive payload)
2048
- * sendViaPostMessage(MessageEvents.OVERLAY, true)
2049
- * // Sends: { type: 'PLAYCADEMY_OVERLAY', payload: true }
2050
- * ```
2051
- */
2052
- private sendViaPostMessage;
2053
- /**
2054
- * **Send Via CustomEvent Method**
2055
- *
2056
- * Sends a message using the browser's CustomEvent API for local same-context communication.
2057
- * This method is used when both the sender and receiver are in the same window context,
2058
- * typically during local development or when the parent needs to send messages to the game.
2059
- *
2060
- * **CustomEvent Protocol**:
2061
- * CustomEvent is a browser API for creating and dispatching custom events within the same
2062
- * window context. It's simpler than postMessage but only works within the same origin/context.
2063
- *
2064
- * **Event Structure**:
2065
- * - **type**: The event name (e.g., 'PLAYCADEMY_INIT')
2066
- * - **detail**: The payload data attached to the event
2067
- *
2068
- * **When This Is Used**:
2069
- * - Local development when game and shell run in the same context
2070
- * - Parent-to-game communication (INIT, TOKEN_REFRESH, PAUSE, etc.)
2071
- * - Any scenario where postMessage isn't needed
2072
- *
2073
- * **Event Bubbling**:
2074
- * CustomEvents are dispatched on the window object and can be listened to by any
2075
- * code in the same context using `addEventListener(type, handler)`.
2076
- *
2077
- * @template K - The message event type (ensures type safety)
2078
- * @param type - The message event type to send (becomes the event name)
2079
- * @param payload - The data to send with the message (stored in event.detail)
2080
- *
2081
- * @example
2082
- * ```typescript
2083
- * // Send initialization data
2084
- * sendViaCustomEvent(MessageEvents.INIT, {
2085
- * baseUrl: '/api',
2086
- * token: 'abc123',
2087
- * gameId: 'game-456'
2088
- * })
2089
- * // Creates: CustomEvent('PLAYCADEMY_INIT', { detail: { baseUrl, token, gameId } })
2090
- *
2091
- * // Send pause signal
2092
- * sendViaCustomEvent(MessageEvents.PAUSE, undefined)
2093
- * // Creates: CustomEvent('PLAYCADEMY_PAUSE', { detail: undefined })
2094
- *
2095
- * // Listeners can access the data via event.detail:
2096
- * window.addEventListener('PLAYCADEMY_INIT', (event) => {
2097
- * console.log(event.detail.gameId) // 'game-456'
2098
- * })
2099
- * ```
2100
- */
2101
- private sendViaCustomEvent;
1062
+ interface SpriteConfigWithDimensions {
1063
+ textureUrl: string;
1064
+ columns: number;
1065
+ rows: number;
1066
+ spriteWidth: number;
1067
+ spriteHeight: number;
1068
+ animations: Record<string, SpriteAnimationFrame>;
2102
1069
  }
2103
- /**
2104
- * **Playcademy Messaging Singleton**
2105
- *
2106
- * This is the main messaging instance used throughout the Playcademy platform.
2107
- * It's exported as a singleton to ensure consistent communication across all parts
2108
- * of the application.
2109
- *
2110
- * **Why a Singleton?**:
2111
- * - Ensures all parts of the app use the same messaging instance
2112
- * - Prevents conflicts between multiple messaging systems
2113
- * - Simplifies the API - no need to pass instances around
2114
- * - Maintains consistent event listener management
2115
- *
2116
- * **Usage in Different Contexts**:
2117
- *
2118
- * **In Games**:
2119
- * ```typescript
2120
- * import { messaging, MessageEvents } from '@playcademy/sdk'
2121
- *
2122
- * // Tell parent we're ready
2123
- * messaging.send(MessageEvents.READY, undefined)
2124
- *
2125
- * // Listen for pause/resume
2126
- * messaging.listen(MessageEvents.PAUSE, () => game.pause())
2127
- * messaging.listen(MessageEvents.RESUME, () => game.resume())
2128
- * ```
2129
- *
2130
- * **In Parent Shell**:
2131
- * ```typescript
2132
- * import { messaging, MessageEvents } from '@playcademy/sdk'
2133
- *
2134
- * // Send initialization data to game
2135
- * messaging.send(MessageEvents.INIT, { baseUrl, token, gameId })
2136
- *
2137
- * // Listen for game events
2138
- * messaging.listen(MessageEvents.EXIT, () => closeGame())
2139
- * messaging.listen(MessageEvents.READY, () => showGame())
2140
- * ```
2141
- *
2142
- * **Automatic Transport Selection**:
2143
- * The messaging system automatically chooses the right transport method:
2144
- * - Uses postMessage when game is in iframe sending to parent
2145
- * - Uses CustomEvent for local development and parent-to-game communication
2146
- *
2147
- * **Type Safety**:
2148
- * All message sending and receiving is fully type-safe with TypeScript.
2149
- */
2150
- declare const messaging: PlaycademyMessaging;
2151
1070
 
2152
1071
  declare const users: drizzle_orm_pg_core.PgTableWithColumns<{
2153
1072
  name: "user";
@@ -2277,14 +1196,14 @@ declare const users: drizzle_orm_pg_core.PgTableWithColumns<{
2277
1196
  tableName: "user";
2278
1197
  dataType: "string";
2279
1198
  columnType: "PgEnumColumn";
2280
- data: "admin" | "developer" | "player";
1199
+ data: "admin" | "developer" | "player" | "teacher";
2281
1200
  driverParam: string;
2282
1201
  notNull: true;
2283
1202
  hasDefault: true;
2284
1203
  isPrimaryKey: false;
2285
1204
  isAutoincrement: false;
2286
1205
  hasRuntimeDefault: false;
2287
- enumValues: ["admin", "player", "developer"];
1206
+ enumValues: ["admin", "player", "developer", "teacher"];
2288
1207
  baseColumn: never;
2289
1208
  identity: undefined;
2290
1209
  generated: undefined;
@@ -5604,260 +4523,1360 @@ type MapObjectRow = typeof mapObjects.$inferSelect;
5604
4523
  type UserRow = typeof users.$inferSelect;
5605
4524
 
5606
4525
  /**
5607
- * Cross-Domain Composite Types
5608
- * Types that combine data from multiple domains
4526
+ * Cross-Domain Composite Types
4527
+ * Types that combine data from multiple domains
4528
+ */
4529
+ type CharacterComponentWithSpriteUrl = CharacterComponentRow & {
4530
+ spriteSheetUrl: string;
4531
+ };
4532
+ type InventoryItemWithItem = InventoryItemRow & {
4533
+ item: ItemRow;
4534
+ };
4535
+ /**
4536
+ * Game with optional manifest metadata from build tools
4537
+ */
4538
+ type FetchedGame = (HostedGame | ExternalGame | GameRow) & {
4539
+ manifest?: ManifestV1;
4540
+ };
4541
+ /**
4542
+ * Session Bootstrap Payload Types
4543
+ * Complex types that combine user, inventory, game, and map data for client initialization
4544
+ */
4545
+ interface PlayerProfile {
4546
+ userId: UserRow['id'];
4547
+ username: UserRow['username'];
4548
+ name: UserRow['name'];
4549
+ image?: UserRow['image'];
4550
+ role?: UserRow['role'];
4551
+ characterCreated?: UserRow['characterCreated'];
4552
+ hasTimebackAccount?: boolean;
4553
+ }
4554
+ interface PlayerCurrency {
4555
+ currencySystemId: CurrencyRow['id'];
4556
+ itemId: ItemRow['id'];
4557
+ name: ItemRow['displayName'];
4558
+ symbol?: CurrencyRow['symbol'];
4559
+ imageUrl?: ItemRow['imageUrl'];
4560
+ isPrimary: CurrencyRow['isPrimary'];
4561
+ balance: number;
4562
+ }
4563
+ interface PlayerInventoryItem {
4564
+ inventoryItemEntryId: InventoryItemRow['id'];
4565
+ item: ItemRow;
4566
+ quantity: InventoryItemRow['quantity'];
4567
+ updatedAt: InventoryItemRow['updatedAt'];
4568
+ }
4569
+ interface PlayerLocation {
4570
+ mapIdentifier: MapRow['identifier'];
4571
+ mapDisplayName: MapRow['displayName'];
4572
+ tileX?: number;
4573
+ tileY?: number;
4574
+ worldX?: number;
4575
+ worldY?: number;
4576
+ direction?: 'up' | 'down' | 'left' | 'right';
4577
+ }
4578
+ interface PlayerSessionPayload {
4579
+ profile: PlayerProfile;
4580
+ currencies: PlayerCurrency[];
4581
+ inventory: PlayerInventoryItem[];
4582
+ ownedGameIds: Game['id'][];
4583
+ currentLocation?: PlayerLocation;
4584
+ characterCreated?: UserRow['characterCreated'];
4585
+ playerCharacter?: PlayerCharacterRow | null;
4586
+ }
4587
+ /**
4588
+ * Map-related Composite Types
4589
+ * DB row + joined game/item data
4590
+ */
4591
+ type MapElementWithGame = MapElementRow & {
4592
+ game: {
4593
+ id: string;
4594
+ displayName: string;
4595
+ } | null;
4596
+ };
4597
+ type MapObjectWithItem = MapObjectRow & {
4598
+ item: {
4599
+ id: string;
4600
+ slug: string;
4601
+ displayName: string;
4602
+ description?: string | null;
4603
+ imageUrl?: string | null;
4604
+ isPlaceable: boolean;
4605
+ metadata?: PlaceableItemMetadata | null;
4606
+ };
4607
+ };
4608
+ /**
4609
+ * Game custom hostname with validation records
4610
+ */
4611
+ type GameCustomHostname = GameCustomHostnameRow & {
4612
+ validationRecords?: DomainValidationRecords;
4613
+ };
4614
+
4615
+ type UserLevelRow = typeof userLevels.$inferSelect;
4616
+ type LevelConfigRow = typeof levelConfigs.$inferSelect;
4617
+ type UserLevelWithConfig = UserLevelRow & {
4618
+ xpToNextLevel: number;
4619
+ nextLevelConfig?: LevelConfigRow;
4620
+ };
4621
+
4622
+ type SpriteTemplateRow = typeof spriteTemplates.$inferSelect;
4623
+
4624
+ type NotificationRow = InferSelectModel<typeof notifications>;
4625
+
4626
+ /**
4627
+ * Connection monitoring types
4628
+ *
4629
+ * Type definitions for connection state, configuration, and callbacks.
4630
+ */
4631
+ /**
4632
+ * Possible connection states.
4633
+ *
4634
+ * - **online**: Connection is stable and healthy
4635
+ * - **offline**: Complete loss of network connectivity
4636
+ * - **degraded**: Connection is slow or experiencing intermittent issues
4637
+ */
4638
+ type ConnectionState = 'online' | 'offline' | 'degraded';
4639
+ /**
4640
+ * Configuration options for ConnectionMonitor.
4641
+ *
4642
+ * @see {@link ConnectionMonitor} for usage
4643
+ */
4644
+ interface ConnectionMonitorConfig {
4645
+ /** Base URL for heartbeat pings (e.g., 'https://api.playcademy.com') */
4646
+ baseUrl: string;
4647
+ /** How often to send heartbeat pings in milliseconds (default: 10000) */
4648
+ heartbeatInterval?: number;
4649
+ /** How long to wait for heartbeat response in milliseconds (default: 5000) */
4650
+ heartbeatTimeout?: number;
4651
+ /** Number of consecutive failures before triggering disconnect (default: 2) */
4652
+ failureThreshold?: number;
4653
+ /** Enable periodic heartbeat monitoring (default: true) */
4654
+ enableHeartbeat?: boolean;
4655
+ /** Enable browser online/offline event listeners (default: true) */
4656
+ enableOfflineEvents?: boolean;
4657
+ }
4658
+ /**
4659
+ * Callback function signature for connection state changes.
4660
+ *
4661
+ * @param state - The new connection state
4662
+ * @param reason - Human-readable reason for the state change
4663
+ */
4664
+ type ConnectionChangeCallback = (state: ConnectionState, reason: string) => void;
4665
+
4666
+ /**
4667
+ * Connection Monitor
4668
+ *
4669
+ * Monitors network connectivity using multiple signals:
4670
+ * 1. navigator.onLine - Instant offline detection
4671
+ * 2. Periodic heartbeat - Detects slow/degraded connections
4672
+ * 3. Request failure tracking - Piggybacks on actual API calls
4673
+ *
4674
+ * Designed for school WiFi environments where connections may be
4675
+ * unstable or degraded without fully disconnecting.
4676
+ */
4677
+
4678
+ /**
4679
+ * Monitors network connectivity using multiple signals and notifies callbacks of state changes.
4680
+ *
4681
+ * The ConnectionMonitor uses a multi-signal approach to detect connection issues:
4682
+ *
4683
+ * 1. **navigator.onLine events** - Instant detection of hard disconnects
4684
+ * 2. **Heartbeat pings** - Periodic checks to detect slow/degraded connections
4685
+ * 3. **Request failure tracking** - Piggybacks on actual API calls
4686
+ *
4687
+ * This comprehensive approach ensures reliable detection across different network
4688
+ * failure modes common in school WiFi environments (hard disconnect, slow connection,
4689
+ * intermittent failures).
4690
+ *
4691
+ * @example
4692
+ * ```typescript
4693
+ * const monitor = new ConnectionMonitor({
4694
+ * baseUrl: 'https://api.playcademy.com',
4695
+ * heartbeatInterval: 10000, // Check every 10s
4696
+ * failureThreshold: 2 // Trigger after 2 failures
4697
+ * })
4698
+ *
4699
+ * monitor.onChange((state, reason) => {
4700
+ * console.log(`Connection: ${state} - ${reason}`)
4701
+ * })
4702
+ *
4703
+ * monitor.start()
4704
+ * ```
4705
+ *
4706
+ * @see {@link ConnectionManagerConfig} for configuration options
4707
+ */
4708
+ declare class ConnectionMonitor {
4709
+ private state;
4710
+ private callbacks;
4711
+ private heartbeatInterval?;
4712
+ private consecutiveFailures;
4713
+ private isMonitoring;
4714
+ private config;
4715
+ /**
4716
+ * Creates a new ConnectionMonitor instance.
4717
+ *
4718
+ * The monitor starts in a stopped state. Call `start()` to begin monitoring.
4719
+ *
4720
+ * @param config - Configuration options
4721
+ * @param config.baseUrl - Base URL for heartbeat pings
4722
+ * @param config.heartbeatInterval - How often to check (default: 10000ms)
4723
+ * @param config.heartbeatTimeout - Request timeout (default: 5000ms)
4724
+ * @param config.failureThreshold - Failures before triggering disconnect (default: 2)
4725
+ * @param config.enableHeartbeat - Enable periodic checks (default: true)
4726
+ * @param config.enableOfflineEvents - Listen to browser events (default: true)
4727
+ */
4728
+ constructor(config: ConnectionMonitorConfig);
4729
+ /**
4730
+ * Starts monitoring the connection state.
4731
+ *
4732
+ * Sets up event listeners and begins heartbeat checks based on configuration.
4733
+ * Idempotent - safe to call multiple times.
4734
+ */
4735
+ start(): void;
4736
+ /**
4737
+ * Stops monitoring the connection state and cleans up resources.
4738
+ *
4739
+ * Removes event listeners and clears heartbeat intervals.
4740
+ * Idempotent - safe to call multiple times.
4741
+ */
4742
+ stop(): void;
4743
+ /**
4744
+ * Registers a callback to be notified of all connection state changes.
4745
+ *
4746
+ * The callback fires for all state transitions: online → offline,
4747
+ * offline → degraded, degraded → online, etc.
4748
+ *
4749
+ * @param callback - Function called with (state, reason) when connection changes
4750
+ * @returns Cleanup function to unregister the callback
4751
+ *
4752
+ * @example
4753
+ * ```typescript
4754
+ * const cleanup = monitor.onChange((state, reason) => {
4755
+ * console.log(`Connection: ${state}`)
4756
+ * if (state === 'offline') {
4757
+ * showReconnectingUI()
4758
+ * }
4759
+ * })
4760
+ *
4761
+ * // Later: cleanup() to unregister
4762
+ * ```
4763
+ */
4764
+ onChange(callback: ConnectionChangeCallback): () => void;
4765
+ /**
4766
+ * Gets the current connection state.
4767
+ *
4768
+ * @returns The current state ('online', 'offline', or 'degraded')
4769
+ */
4770
+ getState(): ConnectionState;
4771
+ /**
4772
+ * Manually triggers an immediate connection check.
4773
+ *
4774
+ * Forces a heartbeat ping to verify connectivity right now, bypassing
4775
+ * the normal interval. Useful before critical operations.
4776
+ *
4777
+ * @returns Promise resolving to the current connection state after the check
4778
+ *
4779
+ * @example
4780
+ * ```typescript
4781
+ * const state = await monitor.checkNow()
4782
+ * if (state !== 'online') {
4783
+ * alert('Please check your internet connection')
4784
+ * }
4785
+ * ```
4786
+ */
4787
+ checkNow(): Promise<ConnectionState>;
4788
+ /**
4789
+ * Reports a request failure for tracking.
4790
+ *
4791
+ * This should be called from your request wrapper whenever an API call fails.
4792
+ * Only network errors are tracked (TypeError, fetch failures) - HTTP error
4793
+ * responses (4xx, 5xx) are ignored.
4794
+ *
4795
+ * After consecutive failures exceed the threshold, the monitor transitions
4796
+ * to 'degraded' or 'offline' state.
4797
+ *
4798
+ * @param error - The error from the failed request
4799
+ *
4800
+ * @example
4801
+ * ```typescript
4802
+ * try {
4803
+ * await fetch('/api/data')
4804
+ * } catch (error) {
4805
+ * monitor.reportRequestFailure(error)
4806
+ * throw error
4807
+ * }
4808
+ * ```
4809
+ */
4810
+ reportRequestFailure(error: unknown): void;
4811
+ /**
4812
+ * Reports a successful request.
4813
+ *
4814
+ * This should be called from your request wrapper whenever an API call succeeds.
4815
+ * Resets the consecutive failure counter and transitions from 'degraded' to
4816
+ * 'online' if the connection has recovered.
4817
+ *
4818
+ * @example
4819
+ * ```typescript
4820
+ * try {
4821
+ * const result = await fetch('/api/data')
4822
+ * monitor.reportRequestSuccess()
4823
+ * return result
4824
+ * } catch (error) {
4825
+ * monitor.reportRequestFailure(error)
4826
+ * throw error
4827
+ * }
4828
+ * ```
4829
+ */
4830
+ reportRequestSuccess(): void;
4831
+ private _detectInitialState;
4832
+ private _handleOnline;
4833
+ private _handleOffline;
4834
+ private _startHeartbeat;
4835
+ private _performHeartbeat;
4836
+ private _handleHeartbeatFailure;
4837
+ private _setState;
4838
+ }
4839
+
4840
+ /**
4841
+ * Connection Manager
4842
+ *
4843
+ * Manages connection monitoring and integrates it with the Playcademy client.
4844
+ * Handles event wiring, state management, and disconnect callbacks.
4845
+ *
4846
+ * In iframe mode, disables local monitoring and listens to platform connection
4847
+ * state broadcasts instead (avoids duplicate heartbeats).
4848
+ */
4849
+
4850
+ /**
4851
+ * Configuration for the ConnectionManager.
4852
+ */
4853
+ interface ConnectionManagerConfig {
4854
+ /** Base URL for API requests (used for heartbeat pings) */
4855
+ baseUrl: string;
4856
+ /** Authentication context (iframe vs standalone) for alert routing */
4857
+ authContext?: {
4858
+ isInIframe: boolean;
4859
+ };
4860
+ /** Handler to call when connection issues are detected */
4861
+ onDisconnect?: DisconnectHandler;
4862
+ /** Callback to emit connection change events to the client */
4863
+ onConnectionChange?: (state: ConnectionState, reason: string) => void;
4864
+ }
4865
+ /**
4866
+ * Manages connection monitoring for the Playcademy client.
4867
+ *
4868
+ * The ConnectionManager serves as an integration layer between the low-level
4869
+ * ConnectionMonitor and the PlaycademyClient. It handles:
4870
+ * - Event wiring and coordination
4871
+ * - Disconnect callbacks with context
4872
+ * - Platform-level alert integration
4873
+ * - Request success/failure tracking
4874
+ *
4875
+ * This class is used internally by PlaycademyClient and typically not
4876
+ * instantiated directly by game developers.
4877
+ *
4878
+ * @see {@link ConnectionMonitor} for the underlying monitoring implementation
4879
+ * @see {@link PlaycademyClient.onDisconnect} for the public API
4880
+ */
4881
+ declare class ConnectionManager {
4882
+ private monitor?;
4883
+ private authContext?;
4884
+ private disconnectHandler?;
4885
+ private connectionChangeCallback?;
4886
+ private currentState;
4887
+ private additionalDisconnectHandlers;
4888
+ /**
4889
+ * Creates a new ConnectionManager instance.
4890
+ *
4891
+ * @param config - Configuration options for the manager
4892
+ *
4893
+ * @example
4894
+ * ```typescript
4895
+ * const manager = new ConnectionManager({
4896
+ * baseUrl: 'https://api.playcademy.com',
4897
+ * authContext: { isInIframe: false },
4898
+ * onDisconnect: (context) => {
4899
+ * console.log(`Disconnected: ${context.state}`)
4900
+ * },
4901
+ * onConnectionChange: (state, reason) => {
4902
+ * console.log(`Connection changed: ${state}`)
4903
+ * }
4904
+ * })
4905
+ * ```
4906
+ */
4907
+ constructor(config: ConnectionManagerConfig);
4908
+ /**
4909
+ * Gets the current connection state.
4910
+ *
4911
+ * @returns The current connection state ('online', 'offline', or 'degraded')
4912
+ *
4913
+ * @example
4914
+ * ```typescript
4915
+ * const state = manager.getState()
4916
+ * if (state === 'offline') {
4917
+ * console.log('No connection')
4918
+ * }
4919
+ * ```
4920
+ */
4921
+ getState(): ConnectionState;
4922
+ /**
4923
+ * Manually triggers a connection check immediately.
4924
+ *
4925
+ * Forces a heartbeat ping to verify the current connection status.
4926
+ * Useful when you need to check connectivity before a critical operation.
4927
+ *
4928
+ * In iframe mode, this returns the last known state from platform.
4929
+ *
4930
+ * @returns Promise resolving to the current connection state
4931
+ *
4932
+ * @example
4933
+ * ```typescript
4934
+ * const state = await manager.checkNow()
4935
+ * if (state === 'online') {
4936
+ * await performCriticalOperation()
4937
+ * }
4938
+ * ```
4939
+ */
4940
+ checkNow(): Promise<ConnectionState>;
4941
+ /**
4942
+ * Reports a successful API request to the connection monitor.
4943
+ *
4944
+ * This resets the consecutive failure counter and transitions from
4945
+ * 'degraded' to 'online' state if applicable.
4946
+ *
4947
+ * Typically called automatically by the SDK's request wrapper.
4948
+ * No-op in iframe mode (platform handles monitoring).
4949
+ */
4950
+ reportRequestSuccess(): void;
4951
+ /**
4952
+ * Reports a failed API request to the connection monitor.
4953
+ *
4954
+ * Only network errors are tracked (not 4xx/5xx HTTP responses).
4955
+ * After consecutive failures exceed the threshold, the state transitions
4956
+ * to 'degraded' or 'offline'.
4957
+ *
4958
+ * Typically called automatically by the SDK's request wrapper.
4959
+ * No-op in iframe mode (platform handles monitoring).
4960
+ *
4961
+ * @param error - The error from the failed request
4962
+ */
4963
+ reportRequestFailure(error: unknown): void;
4964
+ /**
4965
+ * Registers a callback to be called when connection issues are detected.
4966
+ *
4967
+ * The callback only fires for 'offline' and 'degraded' states, not when
4968
+ * recovering to 'online'. This provides a clean API for games to handle
4969
+ * disconnect scenarios without being notified of every state change.
4970
+ *
4971
+ * Works in both iframe and standalone modes transparently.
4972
+ *
4973
+ * @param callback - Function to call when connection degrades
4974
+ * @returns Cleanup function to unregister the callback
4975
+ *
4976
+ * @example
4977
+ * ```typescript
4978
+ * const cleanup = manager.onDisconnect(({ state, reason, displayAlert }) => {
4979
+ * if (state === 'offline') {
4980
+ * displayAlert?.('Connection lost. Saving your progress...', { type: 'error' })
4981
+ * saveGameState()
4982
+ * }
4983
+ * })
4984
+ *
4985
+ * // Later: cleanup() to unregister
4986
+ * ```
4987
+ */
4988
+ onDisconnect(callback: DisconnectHandler): () => void;
4989
+ /**
4990
+ * Stops connection monitoring and performs cleanup.
4991
+ *
4992
+ * Removes event listeners and clears heartbeat intervals.
4993
+ * Should be called when the client is being destroyed.
4994
+ */
4995
+ stop(): void;
4996
+ /**
4997
+ * Sets up listener for platform connection state broadcasts (iframe mode only).
4998
+ */
4999
+ private _setupPlatformListener;
5000
+ /**
5001
+ * Handles connection state changes from the monitor or platform.
5002
+ *
5003
+ * Coordinates between:
5004
+ * 1. Emitting events to the client (for client.on('connectionChange'))
5005
+ * 2. Calling the disconnect handler if configured
5006
+ * 3. Calling any additional handlers registered via onDisconnect()
5007
+ *
5008
+ * @param state - The new connection state
5009
+ * @param reason - Human-readable reason for the state change
5010
+ */
5011
+ private _handleConnectionChange;
5012
+ }
5013
+
5014
+ /**
5015
+ * @fileoverview Authentication Strategy Pattern
5016
+ *
5017
+ * Provides different authentication strategies for the Playcademy SDK.
5018
+ * Each strategy knows how to add its authentication headers to requests.
5609
5019
  */
5610
- type CharacterComponentWithSpriteUrl = CharacterComponentRow & {
5611
- spriteSheetUrl: string;
5612
- };
5613
- type InventoryItemWithItem = InventoryItemRow & {
5614
- item: ItemRow;
5615
- };
5020
+
5616
5021
  /**
5617
- * Game with optional manifest metadata from build tools
5022
+ * Base interface for authentication strategies
5618
5023
  */
5619
- type FetchedGame = (HostedGame | ExternalGame | GameRow) & {
5620
- manifest?: ManifestV1;
5621
- };
5024
+ interface AuthStrategy {
5025
+ /** Get the token value */
5026
+ getToken(): string | null;
5027
+ /** Get the token type */
5028
+ getType(): TokenType;
5029
+ /** Get authentication headers for a request */
5030
+ getHeaders(): Record<string, string>;
5031
+ }
5032
+
5622
5033
  /**
5623
- * Session Bootstrap Payload Types
5624
- * Complex types that combine user, inventory, game, and map data for client initialization
5034
+ * Base Playcademy SDK client with shared infrastructure.
5035
+ * Provides authentication, HTTP requests, events, connection monitoring,
5036
+ * and fundamental namespaces used by all clients.
5037
+ *
5038
+ * Extended by PlaycademyClient (game SDK) and PlaycademyInternalClient (platform SDK).
5625
5039
  */
5626
- interface PlayerProfile {
5627
- userId: UserRow['id'];
5628
- username: UserRow['username'];
5629
- name: UserRow['name'];
5630
- image?: UserRow['image'];
5631
- role?: UserRow['role'];
5632
- characterCreated?: UserRow['characterCreated'];
5633
- hasTimebackAccount?: boolean;
5634
- }
5635
- interface PlayerCurrency {
5636
- currencySystemId: CurrencyRow['id'];
5637
- itemId: ItemRow['id'];
5638
- name: ItemRow['displayName'];
5639
- symbol?: CurrencyRow['symbol'];
5640
- imageUrl?: ItemRow['imageUrl'];
5641
- isPrimary: CurrencyRow['isPrimary'];
5642
- balance: number;
5643
- }
5644
- interface PlayerInventoryItem {
5645
- inventoryItemEntryId: InventoryItemRow['id'];
5646
- item: ItemRow;
5647
- quantity: InventoryItemRow['quantity'];
5648
- updatedAt: InventoryItemRow['updatedAt'];
5040
+ declare abstract class PlaycademyBaseClient {
5041
+ baseUrl: string;
5042
+ gameUrl?: string;
5043
+ protected authStrategy: AuthStrategy;
5044
+ protected gameId?: string;
5045
+ protected config: Partial<ClientConfig>;
5046
+ protected listeners: EventListeners;
5047
+ protected internalClientSessionId?: string;
5048
+ protected authContext?: {
5049
+ isInIframe: boolean;
5050
+ };
5051
+ protected initPayload?: InitPayload;
5052
+ protected connectionManager?: ConnectionManager;
5053
+ protected launchId?: string;
5054
+ /**
5055
+ * Internal session manager for automatic session lifecycle.
5056
+ * @private
5057
+ * @internal
5058
+ */
5059
+ protected _sessionManager: {
5060
+ startSession: (gameId: string) => Promise<{
5061
+ sessionId: string;
5062
+ }>;
5063
+ endSession: (sessionId: string, gameId: string) => Promise<void>;
5064
+ };
5065
+ constructor(config?: Partial<ClientConfig>);
5066
+ /**
5067
+ * Gets the effective base URL for API requests.
5068
+ */
5069
+ getBaseUrl(): string;
5070
+ /**
5071
+ * Gets the effective game backend URL for integration requests.
5072
+ */
5073
+ protected getGameBackendUrl(): string;
5074
+ /**
5075
+ * Simple ping method for testing connectivity.
5076
+ */
5077
+ ping(): string;
5078
+ /**
5079
+ * Sets the authentication token for API requests.
5080
+ */
5081
+ setToken(token: string | null, tokenType?: TokenType): void;
5082
+ setLaunchId(launchId: string | null | undefined): void;
5083
+ /**
5084
+ * Gets the current token type.
5085
+ */
5086
+ getTokenType(): TokenType;
5087
+ /**
5088
+ * Gets the current authentication token.
5089
+ */
5090
+ getToken(): string | null;
5091
+ /**
5092
+ * Checks if the client has a valid API token.
5093
+ */
5094
+ isAuthenticated(): boolean;
5095
+ /**
5096
+ * Registers a callback to be called when authentication state changes.
5097
+ */
5098
+ onAuthChange(callback: (token: string | null) => void): void;
5099
+ /**
5100
+ * Registers a callback to be called when connection issues are detected.
5101
+ */
5102
+ onDisconnect(callback: (context: DisconnectContext) => void | Promise<void>): () => void;
5103
+ /**
5104
+ * Gets the current connection state.
5105
+ */
5106
+ getConnectionState(): ConnectionState | 'unknown';
5107
+ /**
5108
+ * Manually triggers a connection check immediately.
5109
+ */
5110
+ checkConnection(): Promise<ConnectionState | 'unknown'>;
5111
+ /**
5112
+ * Sets the authentication context for the client.
5113
+ * @internal
5114
+ */
5115
+ _setAuthContext(context: {
5116
+ isInIframe: boolean;
5117
+ }): void;
5118
+ /**
5119
+ * Registers an event listener for client events.
5120
+ */
5121
+ on<E extends keyof ClientEvents>(event: E, callback: (payload: ClientEvents[E]) => void): void;
5122
+ /**
5123
+ * Emits an event to all registered listeners.
5124
+ */
5125
+ protected emit<E extends keyof ClientEvents>(event: E, payload: ClientEvents[E]): void;
5126
+ /**
5127
+ * Makes an authenticated HTTP request to the platform API.
5128
+ */
5129
+ protected request<T>(path: string, method: Method, options?: {
5130
+ body?: unknown;
5131
+ headers?: Record<string, string>;
5132
+ raw?: boolean;
5133
+ }): Promise<T>;
5134
+ /**
5135
+ * Makes an authenticated HTTP request to the game's backend Worker.
5136
+ */
5137
+ protected requestGameBackend<T>(path: string, method: Method, body?: unknown, headers?: Record<string, string>, raw?: boolean): Promise<T>;
5138
+ /**
5139
+ * Ensures a gameId is available, throwing an error if not.
5140
+ */
5141
+ protected _ensureGameId(): string;
5142
+ /**
5143
+ * Detects and sets the authentication context (iframe vs standalone).
5144
+ */
5145
+ private _detectAuthContext;
5146
+ /**
5147
+ * Initializes connection monitoring if enabled.
5148
+ */
5149
+ private _initializeConnectionMonitor;
5150
+ private _initializeInternalSession;
5151
+ /**
5152
+ * Current user data and inventory management.
5153
+ * - `me()` - Get authenticated user profile
5154
+ * - `inventory.get()` - List user's items
5155
+ * - `inventory.add(slug, qty)` - Award items to user
5156
+ */
5157
+ users: {
5158
+ me: () => Promise<AuthenticatedUser>;
5159
+ inventory: {
5160
+ get: () => Promise<InventoryItemWithItem[]>;
5161
+ add: (identifier: string, qty: number) => Promise<InventoryMutationResponse>;
5162
+ remove: (identifier: string, qty: number) => Promise<InventoryMutationResponse>;
5163
+ quantity: (identifier: string) => Promise<number>;
5164
+ has: (identifier: string, minQuantity?: number) => Promise<boolean>;
5165
+ };
5166
+ };
5649
5167
  }
5650
- interface PlayerLocation {
5651
- mapIdentifier: MapRow['identifier'];
5652
- mapDisplayName: MapRow['displayName'];
5653
- tileX?: number;
5654
- tileY?: number;
5655
- worldX?: number;
5656
- worldY?: number;
5657
- direction?: 'up' | 'down' | 'left' | 'right';
5168
+
5169
+ /**
5170
+ * Base error class for Cademy SDK specific errors.
5171
+ */
5172
+ declare class PlaycademyError extends Error {
5173
+ constructor(message: string);
5658
5174
  }
5659
- interface PlayerSessionPayload {
5660
- profile: PlayerProfile;
5661
- currencies: PlayerCurrency[];
5662
- inventory: PlayerInventoryItem[];
5663
- ownedGameIds: Game['id'][];
5664
- currentLocation?: PlayerLocation;
5665
- characterCreated?: UserRow['characterCreated'];
5666
- playerCharacter?: PlayerCharacterRow | null;
5175
+ /**
5176
+ * Error codes returned by the API.
5177
+ * These map to specific error types and HTTP status codes.
5178
+ */
5179
+ type ApiErrorCode = 'BAD_REQUEST' | 'UNAUTHORIZED' | 'FORBIDDEN' | 'ACCESS_DENIED' | 'NOT_FOUND' | 'METHOD_NOT_ALLOWED' | 'CONFLICT' | 'ALREADY_EXISTS' | 'GONE' | 'PRECONDITION_FAILED' | 'PAYLOAD_TOO_LARGE' | 'VALIDATION_FAILED' | 'TOO_MANY_REQUESTS' | 'RATE_LIMITED' | 'EXPIRED' | 'INTERNAL' | 'INTERNAL_ERROR' | 'NOT_IMPLEMENTED' | 'SERVICE_UNAVAILABLE' | 'TIMEOUT' | string;
5180
+ /**
5181
+ * Structure of error response bodies returned by API endpoints.
5182
+ *
5183
+ * @example
5184
+ * ```json
5185
+ * {
5186
+ * "error": {
5187
+ * "code": "NOT_FOUND",
5188
+ * "message": "Item not found",
5189
+ * "details": { "identifier": "abc123" }
5190
+ * }
5191
+ * }
5192
+ * ```
5193
+ */
5194
+ interface ErrorResponseBody {
5195
+ error?: {
5196
+ code?: string;
5197
+ message?: string;
5198
+ details?: unknown;
5199
+ };
5667
5200
  }
5668
5201
  /**
5669
- * Map-related Composite Types
5670
- * DB row + joined game/item data
5202
+ * API error thrown when a request fails.
5203
+ *
5204
+ * Contains structured error information from the API response:
5205
+ * - `status` - HTTP status code (e.g., 404)
5206
+ * - `code` - API error code (e.g., "NOT_FOUND")
5207
+ * - `message` - Human-readable error message
5208
+ * - `details` - Optional additional error context
5209
+ *
5210
+ * @example
5211
+ * ```typescript
5212
+ * try {
5213
+ * await client.games.get('nonexistent')
5214
+ * } catch (error) {
5215
+ * if (error instanceof ApiError) {
5216
+ * console.log(error.status) // 404
5217
+ * console.log(error.code) // "NOT_FOUND"
5218
+ * console.log(error.message) // "Game not found"
5219
+ * console.log(error.details) // { identifier: "nonexistent" }
5220
+ * }
5221
+ * }
5222
+ * ```
5671
5223
  */
5672
- type MapElementWithGame = MapElementRow & {
5673
- game: {
5674
- id: string;
5675
- displayName: string;
5676
- } | null;
5677
- };
5678
- type MapObjectWithItem = MapObjectRow & {
5679
- item: {
5680
- id: string;
5681
- slug: string;
5682
- displayName: string;
5683
- description?: string | null;
5684
- imageUrl?: string | null;
5685
- isPlaceable: boolean;
5686
- metadata?: PlaceableItemMetadata | null;
5687
- };
5688
- };
5224
+ declare class ApiError extends Error {
5225
+ /**
5226
+ * API error code (e.g., "NOT_FOUND", "VALIDATION_FAILED").
5227
+ * Use this for programmatic error handling.
5228
+ */
5229
+ readonly code: ApiErrorCode;
5230
+ /**
5231
+ * Additional error context from the API.
5232
+ * Structure varies by error type (e.g., validation errors include field details).
5233
+ */
5234
+ readonly details: unknown;
5235
+ /**
5236
+ * Raw response body for debugging.
5237
+ * @internal
5238
+ */
5239
+ readonly rawBody: unknown;
5240
+ readonly status: number;
5241
+ constructor(
5242
+ /** HTTP status code */
5243
+ status: number,
5244
+ /** API error code */
5245
+ code: ApiErrorCode,
5246
+ /** Human-readable error message */
5247
+ message: string,
5248
+ /** Additional error context */
5249
+ details?: unknown,
5250
+ /** Raw response body */
5251
+ rawBody?: unknown);
5252
+ /**
5253
+ * Create an ApiError from an HTTP response.
5254
+ * Parses the structured error response from the API.
5255
+ *
5256
+ * @internal
5257
+ */
5258
+ static fromResponse(status: number, statusText: string, body: unknown): ApiError;
5259
+ /**
5260
+ * Check if this is a specific error type.
5261
+ *
5262
+ * @example
5263
+ * ```typescript
5264
+ * if (error.is('NOT_FOUND')) {
5265
+ * // Handle not found
5266
+ * } else if (error.is('VALIDATION_FAILED')) {
5267
+ * // Handle validation error
5268
+ * }
5269
+ * ```
5270
+ */
5271
+ is(code: ApiErrorCode): boolean;
5272
+ /**
5273
+ * Check if this is a client error (4xx).
5274
+ */
5275
+ isClientError(): boolean;
5276
+ /**
5277
+ * Check if this is a server error (5xx).
5278
+ */
5279
+ isServerError(): boolean;
5280
+ /**
5281
+ * Check if this error is retryable.
5282
+ * Server errors and rate limits are typically retryable.
5283
+ */
5284
+ isRetryable(): boolean;
5285
+ }
5689
5286
  /**
5690
- * Game custom hostname with validation records
5287
+ * Extracted error information for display purposes.
5691
5288
  */
5692
- type GameCustomHostname = GameCustomHostnameRow & {
5693
- validationRecords?: DomainValidationRecords;
5694
- };
5695
-
5696
- type UserLevelRow = typeof userLevels.$inferSelect;
5697
- type LevelConfigRow = typeof levelConfigs.$inferSelect;
5698
- type UserLevelWithConfig = UserLevelRow & {
5699
- xpToNextLevel: number;
5700
- nextLevelConfig?: LevelConfigRow;
5701
- };
5702
-
5703
- type SpriteTemplateRow = typeof spriteTemplates.$inferSelect;
5704
-
5705
- type NotificationRow = InferSelectModel<typeof notifications>;
5706
-
5289
+ interface ApiErrorInfo {
5290
+ /** HTTP status code */
5291
+ status: number;
5292
+ /** API error code */
5293
+ code: ApiErrorCode;
5294
+ /** Human-readable error message */
5295
+ message: string;
5296
+ /** Additional error context */
5297
+ details?: unknown;
5298
+ }
5707
5299
  /**
5708
- * @fileoverview Authentication Strategy Pattern
5300
+ * Extract useful error information from an API error.
5301
+ * Useful for displaying errors to users in a friendly way.
5709
5302
  *
5710
- * Provides different authentication strategies for the Playcademy SDK.
5711
- * Each strategy knows how to add its authentication headers to requests.
5303
+ * @example
5304
+ * ```typescript
5305
+ * try {
5306
+ * await client.shop.purchase(itemId)
5307
+ * } catch (error) {
5308
+ * const info = extractApiErrorInfo(error)
5309
+ * if (info) {
5310
+ * showToast(`Error: ${info.message}`)
5311
+ * }
5312
+ * }
5313
+ * ```
5712
5314
  */
5315
+ declare function extractApiErrorInfo(error: unknown): ApiErrorInfo | null;
5713
5316
 
5714
5317
  /**
5715
- * Base interface for authentication strategies
5318
+ * @fileoverview Playcademy Messaging System
5319
+ *
5320
+ * This file implements a unified messaging system for the Playcademy platform that handles
5321
+ * communication between different contexts:
5322
+ *
5323
+ * 1. **Iframe-to-Parent Communication**: When games run inside iframes (production/development),
5324
+ * they need to communicate with the parent window using postMessage API
5325
+ *
5326
+ * 2. **Local Communication**: When games run in the same context (local development),
5327
+ * they use CustomEvents for internal messaging
5328
+ *
5329
+ * The system automatically detects the runtime environment and chooses the appropriate
5330
+ * transport method, abstracting this complexity from the developer.
5331
+ *
5332
+ * **Architecture Overview**:
5333
+ * - Games run in iframes for security and isolation
5334
+ * - Parent window (Playcademy shell) manages game lifecycle
5335
+ * - Messages flow bidirectionally between parent and iframe
5336
+ * - Local development mode simulates this architecture without iframes
5716
5337
  */
5717
- interface AuthStrategy {
5718
- /** Get the token value */
5719
- getToken(): string | null;
5720
- /** Get the token type */
5721
- getType(): TokenType;
5722
- /** Get authentication headers for a request */
5723
- getHeaders(): Record<string, string>;
5724
- }
5725
5338
 
5726
5339
  /**
5727
- * Base Playcademy SDK client with shared infrastructure.
5728
- * Provides authentication, HTTP requests, events, connection monitoring,
5729
- * and fundamental namespaces used by all clients.
5340
+ * Enumeration of all message types used in the Playcademy messaging system.
5730
5341
  *
5731
- * Extended by PlaycademyClient (game SDK) and PlaycademyInternalClient (platform SDK).
5342
+ * **Message Flow Patterns**:
5343
+ *
5344
+ * **Parent → Game (Overworld → Game)**:
5345
+ * - INIT: Provides game with authentication token and configuration
5346
+ * - TOKEN_REFRESH: Updates game's authentication token before expiry
5347
+ * - PAUSE/RESUME: Controls game execution state
5348
+ * - FORCE_EXIT: Immediately terminates the game
5349
+ * - OVERLAY: Shows/hides UI overlays over the game
5350
+ *
5351
+ * **Game → Parent (Game → Overworld)**:
5352
+ * - READY: Game has loaded and is ready to receive messages
5353
+ * - EXIT: Game requests to be closed (user clicked exit, game ended, etc.)
5354
+ * - TELEMETRY: Game reports performance metrics (FPS, memory usage, etc.)
5732
5355
  */
5733
- declare abstract class PlaycademyBaseClient {
5734
- baseUrl: string;
5735
- gameUrl?: string;
5736
- protected authStrategy: AuthStrategy;
5737
- protected gameId?: string;
5738
- protected config: Partial<ClientConfig>;
5739
- protected listeners: EventListeners;
5740
- protected internalClientSessionId?: string;
5741
- protected authContext?: {
5742
- isInIframe: boolean;
5743
- };
5744
- protected initPayload?: InitPayload;
5745
- protected connectionManager?: ConnectionManager;
5746
- protected launchId?: string;
5356
+ declare enum MessageEvents {
5747
5357
  /**
5748
- * Internal session manager for automatic session lifecycle.
5749
- * @private
5750
- * @internal
5358
+ * Initializes the game with authentication context and configuration.
5359
+ * Sent immediately after game iframe loads.
5360
+ * Payload:
5361
+ * - `baseUrl`: string
5362
+ * - `token`: string
5363
+ * - `gameId`: string
5751
5364
  */
5752
- protected _sessionManager: {
5753
- startSession: (gameId: string) => Promise<{
5754
- sessionId: string;
5755
- }>;
5756
- endSession: (sessionId: string, gameId: string) => Promise<void>;
5757
- };
5758
- constructor(config?: Partial<ClientConfig>);
5365
+ INIT = "PLAYCADEMY_INIT",
5759
5366
  /**
5760
- * Gets the effective base URL for API requests.
5367
+ * Updates the game's authentication token before it expires.
5368
+ * Sent periodically to maintain valid authentication.
5369
+ * Payload:
5370
+ * - `token`: string
5371
+ * - `exp`: number
5761
5372
  */
5762
- getBaseUrl(): string;
5373
+ TOKEN_REFRESH = "PLAYCADEMY_TOKEN_REFRESH",
5763
5374
  /**
5764
- * Gets the effective game backend URL for integration requests.
5375
+ * Pauses game execution (e.g., when user switches tabs).
5376
+ * Game should pause timers, animations, and user input.
5377
+ * Payload: void
5765
5378
  */
5766
- protected getGameBackendUrl(): string;
5379
+ PAUSE = "PLAYCADEMY_PAUSE",
5767
5380
  /**
5768
- * Simple ping method for testing connectivity.
5381
+ * Resumes game execution after being paused.
5382
+ * Game should restore timers, animations, and user input.
5383
+ * Payload: void
5769
5384
  */
5770
- ping(): string;
5385
+ RESUME = "PLAYCADEMY_RESUME",
5771
5386
  /**
5772
- * Sets the authentication token for API requests.
5387
+ * Forces immediate game termination (emergency exit).
5388
+ * Game should clean up resources and exit immediately.
5389
+ * Payload: void
5773
5390
  */
5774
- setToken(token: string | null, tokenType?: TokenType): void;
5775
- setLaunchId(launchId: string | null | undefined): void;
5391
+ FORCE_EXIT = "PLAYCADEMY_FORCE_EXIT",
5776
5392
  /**
5777
- * Gets the current token type.
5393
+ * Shows or hides UI overlays over the game.
5394
+ * Game may need to pause or adjust rendering accordingly.
5395
+ * Payload: boolean (true = show overlay, false = hide overlay)
5778
5396
  */
5779
- getTokenType(): TokenType;
5397
+ OVERLAY = "PLAYCADEMY_OVERLAY",
5780
5398
  /**
5781
- * Gets the current authentication token.
5399
+ * Broadcasts connection state changes to games.
5400
+ * Sent by platform when network connectivity changes.
5401
+ * Payload:
5402
+ * - `state`: 'online' | 'offline' | 'degraded'
5403
+ * - `reason`: string
5782
5404
  */
5783
- getToken(): string | null;
5405
+ CONNECTION_STATE = "PLAYCADEMY_CONNECTION_STATE",
5784
5406
  /**
5785
- * Checks if the client has a valid API token.
5407
+ * Game has finished loading and is ready to receive messages.
5408
+ * Sent once after game initialization is complete.
5409
+ * Payload: void
5786
5410
  */
5787
- isAuthenticated(): boolean;
5411
+ READY = "PLAYCADEMY_READY",
5788
5412
  /**
5789
- * Registers a callback to be called when authentication state changes.
5413
+ * Game requests to be closed/exited.
5414
+ * Sent when user clicks exit button or game naturally ends.
5415
+ * Payload: void
5790
5416
  */
5791
- onAuthChange(callback: (token: string | null) => void): void;
5417
+ EXIT = "PLAYCADEMY_EXIT",
5792
5418
  /**
5793
- * Registers a callback to be called when connection issues are detected.
5419
+ * Game reports performance telemetry data.
5420
+ * Sent periodically for monitoring and analytics.
5421
+ * Payload:
5422
+ * - `fps`: number
5423
+ * - `mem`: number
5794
5424
  */
5795
- onDisconnect(callback: (context: DisconnectContext) => void | Promise<void>): () => void;
5425
+ TELEMETRY = "PLAYCADEMY_TELEMETRY",
5796
5426
  /**
5797
- * Gets the current connection state.
5427
+ * Game reports key events to parent.
5428
+ * Sent when certain keys are pressed within the game iframe.
5429
+ * Payload:
5430
+ * - `key`: string
5431
+ * - `code?`: string
5432
+ * - `type`: 'keydown' | 'keyup'
5798
5433
  */
5799
- getConnectionState(): ConnectionState | 'unknown';
5434
+ KEY_EVENT = "PLAYCADEMY_KEY_EVENT",
5800
5435
  /**
5801
- * Manually triggers a connection check immediately.
5436
+ * Game requests platform to display an alert.
5437
+ * Sent when connection issues are detected or other important events occur.
5438
+ * Payload:
5439
+ * - `message`: string
5440
+ * - `options`: `{ type?: 'info' | 'warning' | 'error', duration?: number }`
5802
5441
  */
5803
- checkConnection(): Promise<ConnectionState | 'unknown'>;
5442
+ DISPLAY_ALERT = "PLAYCADEMY_DISPLAY_ALERT",
5804
5443
  /**
5805
- * Sets the authentication context for the client.
5806
- * @internal
5444
+ * Notifies about authentication state changes.
5445
+ * Can be sent in both directions depending on auth flow.
5446
+ * Payload:
5447
+ * - `authenticated`: boolean
5448
+ * - `user`: UserInfo | null
5449
+ * - `error`: Error | null
5807
5450
  */
5808
- _setAuthContext(context: {
5809
- isInIframe: boolean;
5810
- }): void;
5451
+ AUTH_STATE_CHANGE = "PLAYCADEMY_AUTH_STATE_CHANGE",
5811
5452
  /**
5812
- * Registers an event listener for client events.
5453
+ * OAuth callback data from popup/new-tab windows.
5454
+ * Sent from popup window back to parent after OAuth completes.
5455
+ * Payload:
5456
+ * - `code`: string (OAuth authorization code)
5457
+ * - `state`: string (OAuth state for CSRF protection)
5458
+ * - `error`: string | null (OAuth error if any)
5813
5459
  */
5814
- on<E extends keyof ClientEvents>(event: E, callback: (payload: ClientEvents[E]) => void): void;
5460
+ AUTH_CALLBACK = "PLAYCADEMY_AUTH_CALLBACK"
5461
+ }
5462
+ /**
5463
+ * Type definition for message handler functions.
5464
+ * These functions are called when a specific message type is received.
5465
+ *
5466
+ * @template T - The type of payload data the handler expects
5467
+ * @param payload - The data sent with the message
5468
+ */
5469
+ type MessageHandler<T = unknown> = (payload: T) => void;
5470
+ /**
5471
+ * Type mapping that defines the payload structure for each message type.
5472
+ * This ensures type safety when sending and receiving messages.
5473
+ *
5474
+ * **Usage Examples**:
5475
+ * ```typescript
5476
+ * // Type-safe message sending
5477
+ * messaging.send(MessageEvents.INIT, { baseUrl: '/api', token: 'abc', gameId: '123' })
5478
+ *
5479
+ * // Type-safe message handling
5480
+ * messaging.listen(MessageEvents.TOKEN_REFRESH, ({ token, exp }) => {
5481
+ * console.log(`New token expires at: ${new Date(exp)}`)
5482
+ * })
5483
+ * ```
5484
+ */
5485
+ interface MessageEventMap {
5486
+ /** Game initialization context with API endpoint, auth token, and game ID */
5487
+ [MessageEvents.INIT]: GameContextPayload;
5488
+ /** Token refresh data with new token and expiration timestamp */
5489
+ [MessageEvents.TOKEN_REFRESH]: TokenRefreshPayload;
5490
+ /** Pause message has no payload data */
5491
+ [MessageEvents.PAUSE]: void;
5492
+ /** Resume message has no payload data */
5493
+ [MessageEvents.RESUME]: void;
5494
+ /** Force exit message has no payload data */
5495
+ [MessageEvents.FORCE_EXIT]: void;
5496
+ /** Overlay visibility state (true = show, false = hide) */
5497
+ [MessageEvents.OVERLAY]: boolean;
5498
+ /** Connection state change from platform */
5499
+ [MessageEvents.CONNECTION_STATE]: ConnectionStatePayload;
5500
+ /** Ready message has no payload data */
5501
+ [MessageEvents.READY]: void;
5502
+ /** Exit message has no payload data */
5503
+ [MessageEvents.EXIT]: void;
5504
+ /** Performance telemetry data */
5505
+ [MessageEvents.TELEMETRY]: TelemetryPayload;
5506
+ /** Key event data */
5507
+ [MessageEvents.KEY_EVENT]: KeyEventPayload;
5508
+ /** Display alert request from game */
5509
+ [MessageEvents.DISPLAY_ALERT]: DisplayAlertPayload;
5510
+ /** Authentication state change notification */
5511
+ [MessageEvents.AUTH_STATE_CHANGE]: AuthStateChangePayload;
5512
+ /** OAuth callback data from popup/new-tab windows */
5513
+ [MessageEvents.AUTH_CALLBACK]: AuthCallbackPayload;
5514
+ }
5515
+ /**
5516
+ * **PlaycademyMessaging Class**
5517
+ *
5518
+ * This is the core messaging system that handles all communication in the Playcademy platform.
5519
+ * It automatically detects the runtime environment and chooses the appropriate transport method.
5520
+ *
5521
+ * **Key Features**:
5522
+ * 1. **Automatic Transport Selection**: Detects iframe vs local context and uses appropriate method
5523
+ * 2. **Type Safety**: Full TypeScript support with payload type checking
5524
+ * 3. **Bidirectional Communication**: Handles both parent→game and game→parent messages
5525
+ * 4. **Event Cleanup**: Proper listener management to prevent memory leaks
5526
+ *
5527
+ * **Transport Methods**:
5528
+ * - **postMessage**: Used for iframe-to-parent communication (production/development)
5529
+ * - **CustomEvent**: Used for local same-context communication (local development)
5530
+ *
5531
+ * **Runtime Detection Logic**:
5532
+ * - If `window.self !== window.top`: We're in an iframe, use postMessage
5533
+ * - If `window.self === window.top`: We're in the same context, use CustomEvent
5534
+ *
5535
+ * **Example Usage**:
5536
+ * ```typescript
5537
+ * // Send a message (automatically chooses transport)
5538
+ * messaging.send(MessageEvents.READY, undefined)
5539
+ *
5540
+ * // Listen for messages (handles both transports)
5541
+ * messaging.listen(MessageEvents.INIT, (payload) => {
5542
+ * console.log('Game initialized with:', payload)
5543
+ * })
5544
+ *
5545
+ * // Clean up listeners
5546
+ * messaging.unlisten(MessageEvents.INIT, handler)
5547
+ * ```
5548
+ */
5549
+ declare class PlaycademyMessaging {
5815
5550
  /**
5816
- * Emits an event to all registered listeners.
5551
+ * Internal storage for message listeners.
5552
+ *
5553
+ * **Structure Explanation**:
5554
+ * - Outer Map: MessageEvents → Map of handlers for that event type
5555
+ * - Inner Map: MessageHandler → Object containing both listener types
5556
+ * - Object: { postMessage: EventListener, customEvent: EventListener }
5557
+ *
5558
+ * **Why Two Listeners Per Handler?**:
5559
+ * Since we don't know at registration time which transport will be used,
5560
+ * we register both a postMessage listener and a CustomEvent listener.
5561
+ * The appropriate one will be triggered based on the runtime context.
5817
5562
  */
5818
- protected emit<E extends keyof ClientEvents>(event: E, payload: ClientEvents[E]): void;
5563
+ private listeners;
5819
5564
  /**
5820
- * Makes an authenticated HTTP request to the platform API.
5565
+ * **Send Message Method**
5566
+ *
5567
+ * Sends a message using the appropriate transport method based on the runtime context.
5568
+ * This is the main public API for sending messages in the Playcademy system.
5569
+ *
5570
+ * **How It Works**:
5571
+ * 1. Analyzes the message type and current runtime context
5572
+ * 2. Determines if we're in an iframe and if this message should use postMessage
5573
+ * 3. Routes to the appropriate transport method (postMessage or CustomEvent)
5574
+ *
5575
+ * **Transport Selection Logic**:
5576
+ * - **postMessage**: Used when game is in iframe and sending to parent, OR when target window is specified
5577
+ * - **CustomEvent**: Used for local/same-context communication
5578
+ *
5579
+ * **Type Safety**:
5580
+ * The generic type parameter `K` ensures that the payload type matches
5581
+ * the expected type for the given message event.
5582
+ *
5583
+ * @template K - The message event type (ensures type safety)
5584
+ * @param type - The message event type to send
5585
+ * @param payload - The data to send with the message (type-checked)
5586
+ * @param options - Optional configuration for message sending
5587
+ *
5588
+ * @example
5589
+ * ```typescript
5590
+ * // Send game ready signal (no payload)
5591
+ * messaging.send(MessageEvents.READY, undefined)
5592
+ *
5593
+ * // Send telemetry data (typed payload)
5594
+ * messaging.send(MessageEvents.TELEMETRY, { fps: 60, mem: 128 })
5595
+ *
5596
+ * // Send to specific iframe window (parent to iframe communication)
5597
+ * messaging.send(MessageEvents.INIT, { baseUrl, token, gameId }, {
5598
+ * target: iframe.contentWindow,
5599
+ * origin: '*'
5600
+ * })
5601
+ *
5602
+ * // TypeScript will error if payload type is wrong
5603
+ * messaging.send(MessageEvents.INIT, "wrong type") // ❌ Error
5604
+ * ```
5821
5605
  */
5822
- protected request<T>(path: string, method: Method, options?: {
5823
- body?: unknown;
5824
- headers?: Record<string, string>;
5825
- raw?: boolean;
5826
- }): Promise<T>;
5606
+ send<K extends MessageEvents>(type: K, payload: MessageEventMap[K], options?: {
5607
+ target?: Window;
5608
+ origin?: string;
5609
+ }): void;
5827
5610
  /**
5828
- * Makes an authenticated HTTP request to the game's backend Worker.
5611
+ * **Listen for Messages Method**
5612
+ *
5613
+ * Registers a message listener that will be called when the specified message type is received.
5614
+ * This method automatically handles both postMessage and CustomEvent sources.
5615
+ *
5616
+ * **Why Register Two Listeners?**:
5617
+ * Since we don't know at registration time which transport method will be used to send
5618
+ * messages, we register listeners for both possible sources:
5619
+ * 1. **postMessage listener**: Handles messages from iframe-to-parent communication
5620
+ * 2. **CustomEvent listener**: Handles messages from local same-context communication
5621
+ *
5622
+ * **Message Processing**:
5623
+ * - **postMessage**: Extracts data from `event.data.payload` or `event.data`
5624
+ * - **CustomEvent**: Extracts data from `event.detail`
5625
+ *
5626
+ * **Type Safety**:
5627
+ * The handler function receives the correctly typed payload based on the message type.
5628
+ *
5629
+ * @template K - The message event type (ensures type safety)
5630
+ * @param type - The message event type to listen for
5631
+ * @param handler - Function to call when the message is received
5632
+ *
5633
+ * @example
5634
+ * ```typescript
5635
+ * // Listen for game initialization
5636
+ * messaging.listen(MessageEvents.INIT, (payload) => {
5637
+ * // payload is automatically typed as GameContextPayload
5638
+ * console.log(`Game ${payload.gameId} initialized`)
5639
+ * console.log(`API base URL: ${payload.baseUrl}`)
5640
+ * })
5641
+ *
5642
+ * // Listen for token refresh
5643
+ * messaging.listen(MessageEvents.TOKEN_REFRESH, ({ token, exp }) => {
5644
+ * // payload is automatically typed as { token: string; exp: number }
5645
+ * updateAuthToken(token)
5646
+ * scheduleTokenRefresh(exp)
5647
+ * })
5648
+ * ```
5829
5649
  */
5830
- protected requestGameBackend<T>(path: string, method: Method, body?: unknown, headers?: Record<string, string>, raw?: boolean): Promise<T>;
5650
+ listen<K extends MessageEvents>(type: K, handler: MessageHandler<MessageEventMap[K]>): void;
5831
5651
  /**
5832
- * Ensures a gameId is available, throwing an error if not.
5652
+ * **Remove Message Listener Method**
5653
+ *
5654
+ * Removes a previously registered message listener to prevent memory leaks and unwanted callbacks.
5655
+ * This method cleans up both the postMessage and CustomEvent listeners that were registered.
5656
+ *
5657
+ * **Why Clean Up Both Listeners?**:
5658
+ * When we registered the listener with `listen()`, we created two browser event listeners:
5659
+ * 1. A 'message' event listener for postMessage communication
5660
+ * 2. A custom event listener for local CustomEvent communication
5661
+ *
5662
+ * Both must be removed to prevent memory leaks and ensure the handler is completely unregistered.
5663
+ *
5664
+ * **Memory Management**:
5665
+ * - Removes both event listeners from the browser
5666
+ * - Cleans up internal tracking maps
5667
+ * - If no more handlers exist for a message type, removes the entire type entry
5668
+ *
5669
+ * **Safe to Call Multiple Times**:
5670
+ * This method is idempotent - calling it multiple times with the same handler is safe.
5671
+ *
5672
+ * @template K - The message event type (ensures type safety)
5673
+ * @param type - The message event type to stop listening for
5674
+ * @param handler - The exact handler function that was passed to listen()
5675
+ *
5676
+ * @example
5677
+ * ```typescript
5678
+ * // Register a handler
5679
+ * const handleInit = (payload) => console.log('Game initialized')
5680
+ * messaging.listen(MessageEvents.INIT, handleInit)
5681
+ *
5682
+ * // Later, remove the handler
5683
+ * messaging.unlisten(MessageEvents.INIT, handleInit)
5684
+ *
5685
+ * // Safe to call multiple times
5686
+ * messaging.unlisten(MessageEvents.INIT, handleInit) // No error
5687
+ * ```
5833
5688
  */
5834
- protected _ensureGameId(): string;
5689
+ unlisten<K extends MessageEvents>(type: K, handler: MessageHandler<MessageEventMap[K]>): void;
5835
5690
  /**
5836
- * Detects and sets the authentication context (iframe vs standalone).
5691
+ * **Get Messaging Context Method**
5692
+ *
5693
+ * Analyzes the current runtime environment and message type to determine the appropriate
5694
+ * transport method and configuration for sending messages.
5695
+ *
5696
+ * **Runtime Environment Detection**:
5697
+ * The method detects whether the code is running in an iframe by comparing:
5698
+ * - `window.self`: Reference to the current window
5699
+ * - `window.top`: Reference to the topmost window in the hierarchy
5700
+ *
5701
+ * If they're different, we're in an iframe. If they're the same, we're in the top-level window.
5702
+ *
5703
+ * **Message Direction Analysis**:
5704
+ * Different message types flow in different directions:
5705
+ * - **Game → Parent**: READY, EXIT, TELEMETRY (use postMessage when in iframe)
5706
+ * - **Parent → Game**: INIT, TOKEN_REFRESH, PAUSE, etc. (use CustomEvent in local dev, or postMessage with target)
5707
+ *
5708
+ * **Cross-Context Communication**:
5709
+ * The messaging system supports cross-context targeting through the optional `target` parameter.
5710
+ * When a target window is specified, postMessage is used regardless of the current context.
5711
+ * This enables parent-to-iframe communication through the unified messaging API.
5712
+ *
5713
+ * **Transport Selection Logic**:
5714
+ * - **postMessage**: Used when game is in iframe AND sending to parent, OR when target window is specified
5715
+ * - **CustomEvent**: Used for all other cases (local development, same-context communication)
5716
+ *
5717
+ * **Security Considerations**:
5718
+ * The origin is currently set to '*' for development convenience, but should be
5719
+ * configurable for production security.
5720
+ *
5721
+ * @param eventType - The message event type being sent
5722
+ * @returns Configuration object with transport method and target information
5723
+ *
5724
+ * @example
5725
+ * ```typescript
5726
+ * // In iframe sending READY to parent
5727
+ * const context = getMessagingContext(MessageEvents.READY)
5728
+ * // Returns: { shouldUsePostMessage: true, target: window.parent, origin: '*' }
5729
+ *
5730
+ * // In same context sending INIT
5731
+ * const context = getMessagingContext(MessageEvents.INIT)
5732
+ * // Returns: { shouldUsePostMessage: false, target: undefined, origin: '*' }
5733
+ * ```
5837
5734
  */
5838
- private _detectAuthContext;
5735
+ private getMessagingContext;
5839
5736
  /**
5840
- * Initializes connection monitoring if enabled.
5737
+ * **Send Via PostMessage Method**
5738
+ *
5739
+ * Sends a message using the browser's postMessage API for iframe-to-parent communication.
5740
+ * This method is used when the game is running in an iframe and needs to communicate
5741
+ * with the parent window (the Playcademy shell).
5742
+ *
5743
+ * **PostMessage Protocol**:
5744
+ * The postMessage API is the standard way for iframes to communicate with their parent.
5745
+ * It's secure, cross-origin capable, and designed specifically for this use case.
5746
+ *
5747
+ * **Message Structure**:
5748
+ * The method creates a message object with the following structure:
5749
+ * - `type`: The message event type (e.g., 'PLAYCADEMY_READY')
5750
+ * - `payload`: The message data (if any)
5751
+ *
5752
+ * **Payload Handling**:
5753
+ * - **All payloads**: Wrapped in a `payload` property for consistency (e.g., { type, payload: data })
5754
+ * - **Undefined payloads**: Only the type is sent (e.g., { type })
5755
+ *
5756
+ * **Security**:
5757
+ * The origin parameter controls which domains can receive the message.
5758
+ * Currently set to '*' for development, but should be restricted in production.
5759
+ *
5760
+ * @template K - The message event type (ensures type safety)
5761
+ * @param type - The message event type to send
5762
+ * @param payload - The data to send with the message
5763
+ * @param target - The target window (defaults to parent window)
5764
+ * @param origin - The allowed origin for the message (defaults to '*')
5765
+ *
5766
+ * @example
5767
+ * ```typescript
5768
+ * // Send ready signal (no payload)
5769
+ * sendViaPostMessage(MessageEvents.READY, undefined)
5770
+ * // Sends: { type: 'PLAYCADEMY_READY' }
5771
+ *
5772
+ * // Send telemetry data (object payload)
5773
+ * sendViaPostMessage(MessageEvents.TELEMETRY, { fps: 60, mem: 128 })
5774
+ * // Sends: { type: 'PLAYCADEMY_TELEMETRY', payload: { fps: 60, mem: 128 } }
5775
+ *
5776
+ * // Send overlay state (primitive payload)
5777
+ * sendViaPostMessage(MessageEvents.OVERLAY, true)
5778
+ * // Sends: { type: 'PLAYCADEMY_OVERLAY', payload: true }
5779
+ * ```
5841
5780
  */
5842
- private _initializeConnectionMonitor;
5843
- private _initializeInternalSession;
5781
+ private sendViaPostMessage;
5844
5782
  /**
5845
- * Current user data and inventory management.
5846
- * - `me()` - Get authenticated user profile
5847
- * - `inventory.get()` - List user's items
5848
- * - `inventory.add(slug, qty)` - Award items to user
5783
+ * **Send Via CustomEvent Method**
5784
+ *
5785
+ * Sends a message using the browser's CustomEvent API for local same-context communication.
5786
+ * This method is used when both the sender and receiver are in the same window context,
5787
+ * typically during local development or when the parent needs to send messages to the game.
5788
+ *
5789
+ * **CustomEvent Protocol**:
5790
+ * CustomEvent is a browser API for creating and dispatching custom events within the same
5791
+ * window context. It's simpler than postMessage but only works within the same origin/context.
5792
+ *
5793
+ * **Event Structure**:
5794
+ * - **type**: The event name (e.g., 'PLAYCADEMY_INIT')
5795
+ * - **detail**: The payload data attached to the event
5796
+ *
5797
+ * **When This Is Used**:
5798
+ * - Local development when game and shell run in the same context
5799
+ * - Parent-to-game communication (INIT, TOKEN_REFRESH, PAUSE, etc.)
5800
+ * - Any scenario where postMessage isn't needed
5801
+ *
5802
+ * **Event Bubbling**:
5803
+ * CustomEvents are dispatched on the window object and can be listened to by any
5804
+ * code in the same context using `addEventListener(type, handler)`.
5805
+ *
5806
+ * @template K - The message event type (ensures type safety)
5807
+ * @param type - The message event type to send (becomes the event name)
5808
+ * @param payload - The data to send with the message (stored in event.detail)
5809
+ *
5810
+ * @example
5811
+ * ```typescript
5812
+ * // Send initialization data
5813
+ * sendViaCustomEvent(MessageEvents.INIT, {
5814
+ * baseUrl: '/api',
5815
+ * token: 'abc123',
5816
+ * gameId: 'game-456'
5817
+ * })
5818
+ * // Creates: CustomEvent('PLAYCADEMY_INIT', { detail: { baseUrl, token, gameId } })
5819
+ *
5820
+ * // Send pause signal
5821
+ * sendViaCustomEvent(MessageEvents.PAUSE, undefined)
5822
+ * // Creates: CustomEvent('PLAYCADEMY_PAUSE', { detail: undefined })
5823
+ *
5824
+ * // Listeners can access the data via event.detail:
5825
+ * window.addEventListener('PLAYCADEMY_INIT', (event) => {
5826
+ * console.log(event.detail.gameId) // 'game-456'
5827
+ * })
5828
+ * ```
5849
5829
  */
5850
- users: {
5851
- me: () => Promise<AuthenticatedUser>;
5852
- inventory: {
5853
- get: () => Promise<InventoryItemWithItem[]>;
5854
- add: (identifier: string, qty: number) => Promise<InventoryMutationResponse>;
5855
- remove: (identifier: string, qty: number) => Promise<InventoryMutationResponse>;
5856
- quantity: (identifier: string) => Promise<number>;
5857
- has: (identifier: string, minQuantity?: number) => Promise<boolean>;
5858
- };
5859
- };
5830
+ private sendViaCustomEvent;
5860
5831
  }
5832
+ /**
5833
+ * **Playcademy Messaging Singleton**
5834
+ *
5835
+ * This is the main messaging instance used throughout the Playcademy platform.
5836
+ * It's exported as a singleton to ensure consistent communication across all parts
5837
+ * of the application.
5838
+ *
5839
+ * **Why a Singleton?**:
5840
+ * - Ensures all parts of the app use the same messaging instance
5841
+ * - Prevents conflicts between multiple messaging systems
5842
+ * - Simplifies the API - no need to pass instances around
5843
+ * - Maintains consistent event listener management
5844
+ *
5845
+ * **Usage in Different Contexts**:
5846
+ *
5847
+ * **In Games**:
5848
+ * ```typescript
5849
+ * import { messaging, MessageEvents } from '@playcademy/sdk'
5850
+ *
5851
+ * // Tell parent we're ready
5852
+ * messaging.send(MessageEvents.READY, undefined)
5853
+ *
5854
+ * // Listen for pause/resume
5855
+ * messaging.listen(MessageEvents.PAUSE, () => game.pause())
5856
+ * messaging.listen(MessageEvents.RESUME, () => game.resume())
5857
+ * ```
5858
+ *
5859
+ * **In Parent Shell**:
5860
+ * ```typescript
5861
+ * import { messaging, MessageEvents } from '@playcademy/sdk'
5862
+ *
5863
+ * // Send initialization data to game
5864
+ * messaging.send(MessageEvents.INIT, { baseUrl, token, gameId })
5865
+ *
5866
+ * // Listen for game events
5867
+ * messaging.listen(MessageEvents.EXIT, () => closeGame())
5868
+ * messaging.listen(MessageEvents.READY, () => showGame())
5869
+ * ```
5870
+ *
5871
+ * **Automatic Transport Selection**:
5872
+ * The messaging system automatically chooses the right transport method:
5873
+ * - Uses postMessage when game is in iframe sending to parent
5874
+ * - Uses CustomEvent for local development and parent-to-game communication
5875
+ *
5876
+ * **Type Safety**:
5877
+ * All message sending and receiving is fully type-safe with TypeScript.
5878
+ */
5879
+ declare const messaging: PlaycademyMessaging;
5861
5880
 
5862
5881
  /**
5863
5882
  * Auto-initializes a PlaycademyClient with context from the environment.
@@ -7754,6 +7773,9 @@ declare class PlaycademyInternalClient extends PlaycademyBaseClient {
7754
7773
  adjustTime: (request: AdjustTimebackTimeRequest) => Promise<TimebackAdminMutationResponse>;
7755
7774
  adjustMastery: (request: AdjustTimebackMasteryRequest) => Promise<TimebackAdminMutationResponse>;
7756
7775
  toggleCompletion: (request: ToggleCourseCompletionRequest) => Promise<TimebackAdminMutationResponse>;
7776
+ searchStudents: (gameId: string, courseId: string, query: string) => Promise<SearchStudentsResponse>;
7777
+ enrollStudent: (request: EnrollStudentRequest) => Promise<TimebackAdminMutationResponse>;
7778
+ unenrollStudent: (request: UnenrollStudentRequest) => Promise<TimebackAdminMutationResponse>;
7757
7779
  };
7758
7780
  };
7759
7781
  /** Auto-initializes a PlaycademyInternalClient with context from the environment */