@playcademy/sdk 0.4.1-beta.4 → 0.4.1-beta.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +86 -69
- package/dist/index.js +327 -61
- package/dist/internal.d.ts +1955 -1955
- package/dist/internal.js +327 -61
- package/dist/types.d.ts +776 -759
- package/package.json +1 -1
package/dist/internal.d.ts
CHANGED
|
@@ -9,352 +9,70 @@ import { AUTH_PROVIDER_IDS } from '@playcademy/constants';
|
|
|
9
9
|
type Method = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
|
-
*
|
|
12
|
+
* TimeBack Enums & Literal Types
|
|
13
13
|
*
|
|
14
|
-
*
|
|
14
|
+
* Basic type definitions used throughout the TimeBack integration.
|
|
15
15
|
*
|
|
16
|
-
* @module types/
|
|
16
|
+
* @module types/timeback/types
|
|
17
17
|
*/
|
|
18
|
-
type UserRoleEnumType = 'admin' | 'player' | 'developer' | 'teacher';
|
|
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
|
-
*
|
|
19
|
+
* Valid TimeBack subject values for course configuration.
|
|
20
|
+
* These are the supported subject values for OneRoster courses.
|
|
46
21
|
*/
|
|
47
|
-
|
|
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
|
-
*
|
|
65
|
-
*
|
|
24
|
+
* Grade levels per AE OneRoster GradeEnum.
|
|
25
|
+
* -1 = Pre-K, 0 = Kindergarten, 1-12 = Grades 1-12, 13 = AP
|
|
66
26
|
*/
|
|
67
|
-
|
|
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
|
-
*
|
|
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
|
|
99
|
-
type GamePlatform = 'web' | 'godot' | 'unity' | (string & {});
|
|
32
|
+
type CaliperSubject = 'Reading' | 'Language' | 'Vocabulary' | 'Social Studies' | 'Writing' | 'Science' | 'FastMath' | 'Math' | 'None';
|
|
100
33
|
/**
|
|
101
|
-
*
|
|
102
|
-
* Note: createdAt is a string here because it's parsed from JSON file.
|
|
34
|
+
* OneRoster organization types.
|
|
103
35
|
*/
|
|
104
|
-
|
|
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
|
-
*
|
|
135
|
-
*
|
|
136
|
-
* Extends the worker response with deployment metadata.
|
|
38
|
+
* Lesson types for PowerPath integration.
|
|
137
39
|
*/
|
|
138
|
-
|
|
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
|
-
*
|
|
43
|
+
* TimeBack Configuration Types
|
|
170
44
|
*
|
|
171
|
-
*
|
|
45
|
+
* Configuration interfaces for Organization, Course, Component,
|
|
46
|
+
* Resource, and complete TimeBack setup.
|
|
47
|
+
*
|
|
48
|
+
* @module types/timeback/config
|
|
172
49
|
*/
|
|
173
|
-
|
|
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;
|
|
202
|
-
}
|
|
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;
|
|
211
|
-
}
|
|
50
|
+
|
|
212
51
|
/**
|
|
213
|
-
*
|
|
214
|
-
* Used when fetching leaderboards for a specific game.
|
|
52
|
+
* Organization configuration for TimeBack (user input - optionals allowed)
|
|
215
53
|
*/
|
|
216
|
-
interface
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
metadata?: Record<string, unknown>;
|
|
224
|
-
gameId: string;
|
|
225
|
-
gameTitle: string;
|
|
226
|
-
gameSlug: string;
|
|
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;
|
|
227
61
|
}
|
|
228
|
-
|
|
229
62
|
/**
|
|
230
|
-
*
|
|
231
|
-
*
|
|
232
|
-
* @module types/achievement
|
|
63
|
+
* Course goals for daily student targets
|
|
233
64
|
*/
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
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;
|
|
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;
|
|
358
76
|
}
|
|
359
77
|
/**
|
|
360
78
|
* Course metrics and totals
|
|
@@ -820,169 +538,451 @@ interface TimebackAdminMutationResponse {
|
|
|
820
538
|
}
|
|
821
539
|
|
|
822
540
|
/**
|
|
823
|
-
*
|
|
824
|
-
*
|
|
825
|
-
* Literal types for inventory system. Database row types are in @playcademy/data/types.
|
|
541
|
+
* Achievement Types
|
|
826
542
|
*
|
|
827
|
-
* @module types/
|
|
828
|
-
*/
|
|
829
|
-
/**
|
|
830
|
-
* Item categories available on the platform.
|
|
543
|
+
* @module types/achievement
|
|
831
544
|
*/
|
|
832
|
-
type
|
|
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"
|
|
552
|
+
}
|
|
553
|
+
interface AchievementCurrent {
|
|
554
|
+
id: string;
|
|
555
|
+
title: string;
|
|
556
|
+
description?: string | 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;
|
|
570
|
+
}
|
|
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;
|
|
588
|
+
}
|
|
589
|
+
interface AchievementHistoryEntry {
|
|
590
|
+
achievementId: string;
|
|
591
|
+
title: string;
|
|
592
|
+
rewardCredits: number;
|
|
593
|
+
createdAt: Date;
|
|
594
|
+
scopeKey: string;
|
|
595
|
+
}
|
|
596
|
+
interface AchievementProgressResponse {
|
|
597
|
+
achievementId: string;
|
|
598
|
+
status: 'completed' | 'already_completed';
|
|
599
|
+
rewardCredits: number;
|
|
600
|
+
scopeKey: string;
|
|
601
|
+
createdAt: Date;
|
|
602
|
+
}
|
|
833
603
|
|
|
834
604
|
/**
|
|
835
|
-
*
|
|
836
|
-
*
|
|
837
|
-
* Literal types and JSON shapes for maps. Database row types are in @playcademy/data/types.
|
|
605
|
+
* Notification Types
|
|
838
606
|
*
|
|
839
|
-
* @module types/
|
|
840
|
-
*/
|
|
841
|
-
/**
|
|
842
|
-
* Allowed map interaction types.
|
|
843
|
-
*/
|
|
844
|
-
type InteractionType = 'game_entry' | 'game_registry' | 'info' | 'teleport' | 'door_in' | 'door_out' | 'npc_interaction' | 'quest_trigger';
|
|
845
|
-
/**
|
|
846
|
-
* Metadata for interactive map elements.
|
|
607
|
+
* @module types/notification
|
|
847
608
|
*/
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
targetSpawnTileY?: number;
|
|
854
|
-
sourceTiledObjects?: Record<string, unknown>[];
|
|
855
|
-
[key: string]: unknown;
|
|
609
|
+
|
|
610
|
+
declare enum NotificationType {
|
|
611
|
+
ACHIEVEMENT = "achievement",
|
|
612
|
+
SYSTEM = "system",
|
|
613
|
+
PROMO = "promo"
|
|
856
614
|
}
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
[key: string]: unknown;
|
|
615
|
+
declare enum NotificationStatus {
|
|
616
|
+
PENDING = "pending",
|
|
617
|
+
DELIVERED = "delivered",
|
|
618
|
+
SEEN = "seen",
|
|
619
|
+
CLICKED = "clicked",
|
|
620
|
+
DISMISSED = "dismissed",
|
|
621
|
+
EXPIRED = "expired"
|
|
865
622
|
}
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
id: string;
|
|
871
|
-
identifier: string;
|
|
872
|
-
displayName: string;
|
|
873
|
-
description?: string | null;
|
|
874
|
-
metadata?: Record<string, unknown> | null;
|
|
623
|
+
declare enum NotificationMethod {
|
|
624
|
+
REALTIME = "realtime",
|
|
625
|
+
LOGIN = "login",
|
|
626
|
+
PUSH = "push"
|
|
875
627
|
}
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
height?: number;
|
|
885
|
-
rotation?: number;
|
|
886
|
-
scale?: number;
|
|
887
|
-
metadata?: Record<string, unknown>;
|
|
628
|
+
interface NotificationStats {
|
|
629
|
+
total: number;
|
|
630
|
+
delivered: number;
|
|
631
|
+
seen: number;
|
|
632
|
+
clicked: number;
|
|
633
|
+
dismissed: number;
|
|
634
|
+
expired: number;
|
|
635
|
+
clickThroughRate: number;
|
|
888
636
|
}
|
|
889
637
|
|
|
890
638
|
/**
|
|
891
|
-
*
|
|
639
|
+
* User Types
|
|
892
640
|
*
|
|
893
|
-
*
|
|
641
|
+
* Enums, DTOs and API response types. Database row types are in @playcademy/data/types.
|
|
894
642
|
*
|
|
895
|
-
* @module types/
|
|
643
|
+
* @module types/user
|
|
896
644
|
*/
|
|
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 {
|
|
658
|
+
id: string;
|
|
659
|
+
name: string | null;
|
|
660
|
+
type: TimebackOrgType | string;
|
|
661
|
+
isPrimary: boolean;
|
|
662
|
+
}
|
|
663
|
+
interface TimebackStudentProfile {
|
|
664
|
+
role: TimebackUserRole;
|
|
665
|
+
organizations: UserOrganization[];
|
|
666
|
+
}
|
|
667
|
+
interface UserTimebackData extends TimebackStudentProfile {
|
|
668
|
+
id: string;
|
|
669
|
+
enrollments: UserEnrollment[];
|
|
670
|
+
}
|
|
897
671
|
/**
|
|
898
|
-
*
|
|
672
|
+
* OpenID Connect UserInfo claims (NOT a database row).
|
|
899
673
|
*/
|
|
900
|
-
|
|
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;
|
|
689
|
+
}
|
|
690
|
+
/**
|
|
691
|
+
* Authenticated user for API responses.
|
|
692
|
+
* Differs from UserRow: omits timebackId, adds hasTimebackAccount and timeback.
|
|
693
|
+
*/
|
|
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;
|
|
716
|
+
}
|
|
901
717
|
|
|
902
718
|
/**
|
|
903
|
-
*
|
|
719
|
+
* Game Types
|
|
904
720
|
*
|
|
905
|
-
*
|
|
721
|
+
* Literal types and API DTOs. Database row types are in @playcademy/data/types.
|
|
906
722
|
*
|
|
907
|
-
* @module types/
|
|
723
|
+
* @module types/game
|
|
908
724
|
*/
|
|
725
|
+
type GameType = 'hosted' | 'external';
|
|
726
|
+
type GamePlatform = 'web' | 'godot' | 'unity' | (string & {});
|
|
909
727
|
/**
|
|
910
|
-
*
|
|
728
|
+
* Game manifest file format (manifest.json).
|
|
729
|
+
* Note: createdAt is a string here because it's parsed from JSON file.
|
|
911
730
|
*/
|
|
912
|
-
interface
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
creditsAwarded: number;
|
|
917
|
-
xpToNextLevel: number;
|
|
731
|
+
interface ManifestV1 {
|
|
732
|
+
version: string;
|
|
733
|
+
platform: string;
|
|
734
|
+
createdAt: string;
|
|
918
735
|
}
|
|
919
|
-
/**
|
|
920
|
-
|
|
921
|
-
*/
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
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';
|
|
928
759
|
}
|
|
929
760
|
/**
|
|
930
|
-
*
|
|
761
|
+
* API response for seed operations (what the API returns to the CLI).
|
|
762
|
+
*
|
|
763
|
+
* Extends the worker response with deployment metadata.
|
|
931
764
|
*/
|
|
932
|
-
interface
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
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;
|
|
788
|
+
};
|
|
789
|
+
ssl?: {
|
|
790
|
+
txt_name?: string;
|
|
791
|
+
txt_value?: string;
|
|
792
|
+
}[];
|
|
937
793
|
}
|
|
938
794
|
|
|
939
795
|
/**
|
|
940
|
-
*
|
|
796
|
+
* Leaderboard Types
|
|
941
797
|
*
|
|
942
|
-
* @module types/
|
|
798
|
+
* @module types/leaderboard
|
|
943
799
|
*/
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
800
|
+
type LeaderboardTimeframe = 'all_time' | 'monthly' | 'weekly' | 'daily';
|
|
801
|
+
interface LeaderboardOptions {
|
|
802
|
+
timeframe?: LeaderboardTimeframe;
|
|
803
|
+
limit?: number;
|
|
804
|
+
offset?: number;
|
|
805
|
+
gameId?: string;
|
|
949
806
|
}
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
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;
|
|
957
818
|
}
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
819
|
+
interface UserRank {
|
|
820
|
+
rank: number;
|
|
821
|
+
totalPlayers: number;
|
|
822
|
+
score: number;
|
|
823
|
+
percentile: number;
|
|
962
824
|
}
|
|
963
|
-
interface
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
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;
|
|
838
|
+
}
|
|
839
|
+
/**
|
|
840
|
+
* Leaderboard entry with required game context.
|
|
841
|
+
* Used when fetching leaderboards for a specific game.
|
|
842
|
+
*/
|
|
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;
|
|
971
854
|
}
|
|
972
855
|
|
|
973
856
|
/**
|
|
974
|
-
* Shop Types
|
|
857
|
+
* Inventory & Shop Types
|
|
975
858
|
*
|
|
976
|
-
*
|
|
859
|
+
* Literal types for inventory system. Database row types are in @playcademy/data/types.
|
|
860
|
+
*
|
|
861
|
+
* @module types/inventory
|
|
977
862
|
*/
|
|
978
863
|
/**
|
|
979
|
-
*
|
|
864
|
+
* Item categories available on the platform.
|
|
980
865
|
*/
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
866
|
+
type ItemType = 'currency' | 'badge' | 'trophy' | 'collectible' | 'consumable' | 'unlock' | 'upgrade' | 'accessory' | 'other';
|
|
867
|
+
|
|
868
|
+
/**
|
|
869
|
+
* Map & Placement Types
|
|
870
|
+
*
|
|
871
|
+
* Literal types and JSON shapes for maps. Database row types are in @playcademy/data/types.
|
|
872
|
+
*
|
|
873
|
+
* @module types/map
|
|
874
|
+
*/
|
|
875
|
+
/**
|
|
876
|
+
* Allowed map interaction types.
|
|
877
|
+
*/
|
|
878
|
+
type InteractionType = 'game_entry' | 'game_registry' | 'info' | 'teleport' | 'door_in' | 'door_out' | 'npc_interaction' | 'quest_trigger';
|
|
879
|
+
/**
|
|
880
|
+
* Metadata for interactive map elements.
|
|
881
|
+
*/
|
|
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;
|
|
890
|
+
}
|
|
891
|
+
/**
|
|
892
|
+
* Metadata describing how an item should be placed on the map.
|
|
893
|
+
*/
|
|
894
|
+
interface PlaceableItemMetadata {
|
|
895
|
+
tilesWide?: number;
|
|
896
|
+
tilesHigh?: number;
|
|
897
|
+
flippable?: boolean;
|
|
898
|
+
[key: string]: unknown;
|
|
899
|
+
}
|
|
900
|
+
/**
|
|
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.
|
|
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
|
+
}
|
|
923
|
+
|
|
924
|
+
/**
|
|
925
|
+
* Character & Avatar Types
|
|
926
|
+
*
|
|
927
|
+
* Literal types for character system. Database row types are in @playcademy/data/types.
|
|
928
|
+
*
|
|
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
|
|
938
|
+
*
|
|
939
|
+
* API response DTOs for level system. Database row types are in @playcademy/data/types.
|
|
940
|
+
*
|
|
941
|
+
* @module types/level
|
|
942
|
+
*/
|
|
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;
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
/**
|
|
974
|
+
* Shop Types
|
|
975
|
+
*
|
|
976
|
+
* @module types/shop
|
|
977
|
+
*/
|
|
978
|
+
/**
|
|
979
|
+
* Currency display info for shop UI.
|
|
980
|
+
*/
|
|
981
|
+
interface ShopCurrency {
|
|
982
|
+
id: string;
|
|
983
|
+
symbol: string | null;
|
|
984
|
+
isPrimary: boolean;
|
|
985
|
+
displayName?: string | null;
|
|
986
986
|
imageUrl?: string | null;
|
|
987
987
|
}
|
|
988
988
|
/**
|
|
@@ -1068,1326 +1068,226 @@ interface SpriteConfigWithDimensions {
|
|
|
1068
1068
|
animations: Record<string, SpriteAnimationFrame>;
|
|
1069
1069
|
}
|
|
1070
1070
|
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1071
|
+
declare const users: drizzle_orm_pg_core.PgTableWithColumns<{
|
|
1072
|
+
name: "user";
|
|
1073
|
+
schema: undefined;
|
|
1074
|
+
columns: {
|
|
1075
|
+
id: drizzle_orm_pg_core.PgColumn<{
|
|
1076
|
+
name: "id";
|
|
1077
|
+
tableName: "user";
|
|
1078
|
+
dataType: "string";
|
|
1079
|
+
columnType: "PgText";
|
|
1080
|
+
data: string;
|
|
1081
|
+
driverParam: string;
|
|
1082
|
+
notNull: true;
|
|
1083
|
+
hasDefault: true;
|
|
1084
|
+
isPrimaryKey: true;
|
|
1085
|
+
isAutoincrement: false;
|
|
1086
|
+
hasRuntimeDefault: true;
|
|
1087
|
+
enumValues: [string, ...string[]];
|
|
1088
|
+
baseColumn: never;
|
|
1089
|
+
identity: undefined;
|
|
1090
|
+
generated: undefined;
|
|
1091
|
+
}, {}, {}>;
|
|
1092
|
+
name: drizzle_orm_pg_core.PgColumn<{
|
|
1093
|
+
name: "name";
|
|
1094
|
+
tableName: "user";
|
|
1095
|
+
dataType: "string";
|
|
1096
|
+
columnType: "PgText";
|
|
1097
|
+
data: string;
|
|
1098
|
+
driverParam: string;
|
|
1099
|
+
notNull: true;
|
|
1100
|
+
hasDefault: false;
|
|
1101
|
+
isPrimaryKey: false;
|
|
1102
|
+
isAutoincrement: false;
|
|
1103
|
+
hasRuntimeDefault: false;
|
|
1104
|
+
enumValues: [string, ...string[]];
|
|
1105
|
+
baseColumn: never;
|
|
1106
|
+
identity: undefined;
|
|
1107
|
+
generated: undefined;
|
|
1108
|
+
}, {}, {}>;
|
|
1109
|
+
username: drizzle_orm_pg_core.PgColumn<{
|
|
1110
|
+
name: "username";
|
|
1111
|
+
tableName: "user";
|
|
1112
|
+
dataType: "string";
|
|
1113
|
+
columnType: "PgText";
|
|
1114
|
+
data: string;
|
|
1115
|
+
driverParam: string;
|
|
1116
|
+
notNull: false;
|
|
1117
|
+
hasDefault: false;
|
|
1118
|
+
isPrimaryKey: false;
|
|
1119
|
+
isAutoincrement: false;
|
|
1120
|
+
hasRuntimeDefault: false;
|
|
1121
|
+
enumValues: [string, ...string[]];
|
|
1122
|
+
baseColumn: never;
|
|
1123
|
+
identity: undefined;
|
|
1124
|
+
generated: undefined;
|
|
1125
|
+
}, {}, {}>;
|
|
1126
|
+
email: drizzle_orm_pg_core.PgColumn<{
|
|
1127
|
+
name: "email";
|
|
1128
|
+
tableName: "user";
|
|
1129
|
+
dataType: "string";
|
|
1130
|
+
columnType: "PgText";
|
|
1131
|
+
data: string;
|
|
1132
|
+
driverParam: string;
|
|
1133
|
+
notNull: true;
|
|
1134
|
+
hasDefault: false;
|
|
1135
|
+
isPrimaryKey: false;
|
|
1136
|
+
isAutoincrement: false;
|
|
1137
|
+
hasRuntimeDefault: false;
|
|
1138
|
+
enumValues: [string, ...string[]];
|
|
1139
|
+
baseColumn: never;
|
|
1140
|
+
identity: undefined;
|
|
1141
|
+
generated: undefined;
|
|
1142
|
+
}, {}, {}>;
|
|
1143
|
+
timebackId: drizzle_orm_pg_core.PgColumn<{
|
|
1144
|
+
name: "timeback_id";
|
|
1145
|
+
tableName: "user";
|
|
1146
|
+
dataType: "string";
|
|
1147
|
+
columnType: "PgText";
|
|
1148
|
+
data: string;
|
|
1149
|
+
driverParam: string;
|
|
1150
|
+
notNull: false;
|
|
1151
|
+
hasDefault: false;
|
|
1152
|
+
isPrimaryKey: false;
|
|
1153
|
+
isAutoincrement: false;
|
|
1154
|
+
hasRuntimeDefault: false;
|
|
1155
|
+
enumValues: [string, ...string[]];
|
|
1156
|
+
baseColumn: never;
|
|
1157
|
+
identity: undefined;
|
|
1158
|
+
generated: undefined;
|
|
1159
|
+
}, {}, {}>;
|
|
1160
|
+
emailVerified: drizzle_orm_pg_core.PgColumn<{
|
|
1161
|
+
name: "email_verified";
|
|
1162
|
+
tableName: "user";
|
|
1163
|
+
dataType: "boolean";
|
|
1164
|
+
columnType: "PgBoolean";
|
|
1165
|
+
data: boolean;
|
|
1166
|
+
driverParam: boolean;
|
|
1167
|
+
notNull: true;
|
|
1168
|
+
hasDefault: true;
|
|
1169
|
+
isPrimaryKey: false;
|
|
1170
|
+
isAutoincrement: false;
|
|
1171
|
+
hasRuntimeDefault: false;
|
|
1172
|
+
enumValues: undefined;
|
|
1173
|
+
baseColumn: never;
|
|
1174
|
+
identity: undefined;
|
|
1175
|
+
generated: undefined;
|
|
1176
|
+
}, {}, {}>;
|
|
1177
|
+
image: drizzle_orm_pg_core.PgColumn<{
|
|
1178
|
+
name: "image";
|
|
1179
|
+
tableName: "user";
|
|
1180
|
+
dataType: "string";
|
|
1181
|
+
columnType: "PgText";
|
|
1182
|
+
data: string;
|
|
1183
|
+
driverParam: string;
|
|
1184
|
+
notNull: false;
|
|
1185
|
+
hasDefault: false;
|
|
1186
|
+
isPrimaryKey: false;
|
|
1187
|
+
isAutoincrement: false;
|
|
1188
|
+
hasRuntimeDefault: false;
|
|
1189
|
+
enumValues: [string, ...string[]];
|
|
1190
|
+
baseColumn: never;
|
|
1191
|
+
identity: undefined;
|
|
1192
|
+
generated: undefined;
|
|
1193
|
+
}, {}, {}>;
|
|
1194
|
+
role: drizzle_orm_pg_core.PgColumn<{
|
|
1195
|
+
name: "role";
|
|
1196
|
+
tableName: "user";
|
|
1197
|
+
dataType: "string";
|
|
1198
|
+
columnType: "PgEnumColumn";
|
|
1199
|
+
data: "admin" | "developer" | "player" | "teacher";
|
|
1200
|
+
driverParam: string;
|
|
1201
|
+
notNull: true;
|
|
1202
|
+
hasDefault: true;
|
|
1203
|
+
isPrimaryKey: false;
|
|
1204
|
+
isAutoincrement: false;
|
|
1205
|
+
hasRuntimeDefault: false;
|
|
1206
|
+
enumValues: ["admin", "player", "developer", "teacher"];
|
|
1207
|
+
baseColumn: never;
|
|
1208
|
+
identity: undefined;
|
|
1209
|
+
generated: undefined;
|
|
1210
|
+
}, {}, {}>;
|
|
1211
|
+
developerStatus: drizzle_orm_pg_core.PgColumn<{
|
|
1212
|
+
name: "developer_status";
|
|
1213
|
+
tableName: "user";
|
|
1214
|
+
dataType: "string";
|
|
1215
|
+
columnType: "PgEnumColumn";
|
|
1216
|
+
data: "approved" | "none" | "pending";
|
|
1217
|
+
driverParam: string;
|
|
1218
|
+
notNull: true;
|
|
1219
|
+
hasDefault: true;
|
|
1220
|
+
isPrimaryKey: false;
|
|
1221
|
+
isAutoincrement: false;
|
|
1222
|
+
hasRuntimeDefault: false;
|
|
1223
|
+
enumValues: ["none", "pending", "approved"];
|
|
1224
|
+
baseColumn: never;
|
|
1225
|
+
identity: undefined;
|
|
1226
|
+
generated: undefined;
|
|
1227
|
+
}, {}, {}>;
|
|
1228
|
+
characterCreated: drizzle_orm_pg_core.PgColumn<{
|
|
1229
|
+
name: "character_created";
|
|
1230
|
+
tableName: "user";
|
|
1231
|
+
dataType: "boolean";
|
|
1232
|
+
columnType: "PgBoolean";
|
|
1233
|
+
data: boolean;
|
|
1234
|
+
driverParam: boolean;
|
|
1235
|
+
notNull: true;
|
|
1236
|
+
hasDefault: true;
|
|
1237
|
+
isPrimaryKey: false;
|
|
1238
|
+
isAutoincrement: false;
|
|
1239
|
+
hasRuntimeDefault: false;
|
|
1240
|
+
enumValues: undefined;
|
|
1241
|
+
baseColumn: never;
|
|
1242
|
+
identity: undefined;
|
|
1243
|
+
generated: undefined;
|
|
1244
|
+
}, {}, {}>;
|
|
1245
|
+
createdAt: drizzle_orm_pg_core.PgColumn<{
|
|
1246
|
+
name: "created_at";
|
|
1247
|
+
tableName: "user";
|
|
1248
|
+
dataType: "date";
|
|
1249
|
+
columnType: "PgTimestamp";
|
|
1250
|
+
data: Date;
|
|
1251
|
+
driverParam: string;
|
|
1252
|
+
notNull: true;
|
|
1253
|
+
hasDefault: false;
|
|
1254
|
+
isPrimaryKey: false;
|
|
1255
|
+
isAutoincrement: false;
|
|
1256
|
+
hasRuntimeDefault: false;
|
|
1257
|
+
enumValues: undefined;
|
|
1258
|
+
baseColumn: never;
|
|
1259
|
+
identity: undefined;
|
|
1260
|
+
generated: undefined;
|
|
1261
|
+
}, {}, {}>;
|
|
1262
|
+
updatedAt: drizzle_orm_pg_core.PgColumn<{
|
|
1263
|
+
name: "updated_at";
|
|
1264
|
+
tableName: "user";
|
|
1265
|
+
dataType: "date";
|
|
1266
|
+
columnType: "PgTimestamp";
|
|
1267
|
+
data: Date;
|
|
1268
|
+
driverParam: string;
|
|
1269
|
+
notNull: true;
|
|
1270
|
+
hasDefault: false;
|
|
1271
|
+
isPrimaryKey: false;
|
|
1272
|
+
isAutoincrement: false;
|
|
1273
|
+
hasRuntimeDefault: false;
|
|
1274
|
+
enumValues: undefined;
|
|
1275
|
+
baseColumn: never;
|
|
1276
|
+
identity: undefined;
|
|
1277
|
+
generated: undefined;
|
|
1278
|
+
}, {}, {}>;
|
|
1279
|
+
};
|
|
1280
|
+
dialect: "pg";
|
|
1281
|
+
}>;
|
|
1218
1282
|
|
|
1283
|
+
interface GameMetadata {
|
|
1284
|
+
description?: string;
|
|
1285
|
+
emoji?: string;
|
|
1286
|
+
[key: string]: unknown;
|
|
1287
|
+
}
|
|
1219
1288
|
/**
|
|
1220
|
-
*
|
|
1221
|
-
*
|
|
1222
|
-
* Type definitions for connection state, configuration, and callbacks.
|
|
1223
|
-
*/
|
|
1224
|
-
/**
|
|
1225
|
-
* Possible connection states.
|
|
1226
|
-
*
|
|
1227
|
-
* - **online**: Connection is stable and healthy
|
|
1228
|
-
* - **offline**: Complete loss of network connectivity
|
|
1229
|
-
* - **degraded**: Connection is slow or experiencing intermittent issues
|
|
1230
|
-
*/
|
|
1231
|
-
type ConnectionState = 'online' | 'offline' | 'degraded';
|
|
1232
|
-
/**
|
|
1233
|
-
* Configuration options for ConnectionMonitor.
|
|
1234
|
-
*
|
|
1235
|
-
* @see {@link ConnectionMonitor} for usage
|
|
1236
|
-
*/
|
|
1237
|
-
interface ConnectionMonitorConfig {
|
|
1238
|
-
/** Base URL for heartbeat pings (e.g., 'https://api.playcademy.com') */
|
|
1239
|
-
baseUrl: string;
|
|
1240
|
-
/** How often to send heartbeat pings in milliseconds (default: 10000) */
|
|
1241
|
-
heartbeatInterval?: number;
|
|
1242
|
-
/** How long to wait for heartbeat response in milliseconds (default: 5000) */
|
|
1243
|
-
heartbeatTimeout?: number;
|
|
1244
|
-
/** Number of consecutive failures before triggering disconnect (default: 2) */
|
|
1245
|
-
failureThreshold?: number;
|
|
1246
|
-
/** Enable periodic heartbeat monitoring (default: true) */
|
|
1247
|
-
enableHeartbeat?: boolean;
|
|
1248
|
-
/** Enable browser online/offline event listeners (default: true) */
|
|
1249
|
-
enableOfflineEvents?: boolean;
|
|
1250
|
-
}
|
|
1251
|
-
/**
|
|
1252
|
-
* Callback function signature for connection state changes.
|
|
1253
|
-
*
|
|
1254
|
-
* @param state - The new connection state
|
|
1255
|
-
* @param reason - Human-readable reason for the state change
|
|
1256
|
-
*/
|
|
1257
|
-
type ConnectionChangeCallback = (state: ConnectionState, reason: string) => void;
|
|
1258
|
-
|
|
1259
|
-
/**
|
|
1260
|
-
* Connection Monitor
|
|
1261
|
-
*
|
|
1262
|
-
* Monitors network connectivity using multiple signals:
|
|
1263
|
-
* 1. navigator.onLine - Instant offline detection
|
|
1264
|
-
* 2. Periodic heartbeat - Detects slow/degraded connections
|
|
1265
|
-
* 3. Request failure tracking - Piggybacks on actual API calls
|
|
1266
|
-
*
|
|
1267
|
-
* Designed for school WiFi environments where connections may be
|
|
1268
|
-
* unstable or degraded without fully disconnecting.
|
|
1269
|
-
*/
|
|
1270
|
-
|
|
1271
|
-
/**
|
|
1272
|
-
* Monitors network connectivity using multiple signals and notifies callbacks of state changes.
|
|
1273
|
-
*
|
|
1274
|
-
* The ConnectionMonitor uses a multi-signal approach to detect connection issues:
|
|
1275
|
-
*
|
|
1276
|
-
* 1. **navigator.onLine events** - Instant detection of hard disconnects
|
|
1277
|
-
* 2. **Heartbeat pings** - Periodic checks to detect slow/degraded connections
|
|
1278
|
-
* 3. **Request failure tracking** - Piggybacks on actual API calls
|
|
1279
|
-
*
|
|
1280
|
-
* This comprehensive approach ensures reliable detection across different network
|
|
1281
|
-
* failure modes common in school WiFi environments (hard disconnect, slow connection,
|
|
1282
|
-
* intermittent failures).
|
|
1283
|
-
*
|
|
1284
|
-
* @example
|
|
1285
|
-
* ```typescript
|
|
1286
|
-
* const monitor = new ConnectionMonitor({
|
|
1287
|
-
* baseUrl: 'https://api.playcademy.com',
|
|
1288
|
-
* heartbeatInterval: 10000, // Check every 10s
|
|
1289
|
-
* failureThreshold: 2 // Trigger after 2 failures
|
|
1290
|
-
* })
|
|
1291
|
-
*
|
|
1292
|
-
* monitor.onChange((state, reason) => {
|
|
1293
|
-
* console.log(`Connection: ${state} - ${reason}`)
|
|
1294
|
-
* })
|
|
1295
|
-
*
|
|
1296
|
-
* monitor.start()
|
|
1297
|
-
* ```
|
|
1298
|
-
*
|
|
1299
|
-
* @see {@link ConnectionManagerConfig} for configuration options
|
|
1300
|
-
*/
|
|
1301
|
-
declare class ConnectionMonitor {
|
|
1302
|
-
private state;
|
|
1303
|
-
private callbacks;
|
|
1304
|
-
private heartbeatInterval?;
|
|
1305
|
-
private consecutiveFailures;
|
|
1306
|
-
private isMonitoring;
|
|
1307
|
-
private config;
|
|
1308
|
-
/**
|
|
1309
|
-
* Creates a new ConnectionMonitor instance.
|
|
1310
|
-
*
|
|
1311
|
-
* The monitor starts in a stopped state. Call `start()` to begin monitoring.
|
|
1312
|
-
*
|
|
1313
|
-
* @param config - Configuration options
|
|
1314
|
-
* @param config.baseUrl - Base URL for heartbeat pings
|
|
1315
|
-
* @param config.heartbeatInterval - How often to check (default: 10000ms)
|
|
1316
|
-
* @param config.heartbeatTimeout - Request timeout (default: 5000ms)
|
|
1317
|
-
* @param config.failureThreshold - Failures before triggering disconnect (default: 2)
|
|
1318
|
-
* @param config.enableHeartbeat - Enable periodic checks (default: true)
|
|
1319
|
-
* @param config.enableOfflineEvents - Listen to browser events (default: true)
|
|
1320
|
-
*/
|
|
1321
|
-
constructor(config: ConnectionMonitorConfig);
|
|
1322
|
-
/**
|
|
1323
|
-
* Starts monitoring the connection state.
|
|
1324
|
-
*
|
|
1325
|
-
* Sets up event listeners and begins heartbeat checks based on configuration.
|
|
1326
|
-
* Idempotent - safe to call multiple times.
|
|
1327
|
-
*/
|
|
1328
|
-
start(): void;
|
|
1329
|
-
/**
|
|
1330
|
-
* Stops monitoring the connection state and cleans up resources.
|
|
1331
|
-
*
|
|
1332
|
-
* Removes event listeners and clears heartbeat intervals.
|
|
1333
|
-
* Idempotent - safe to call multiple times.
|
|
1334
|
-
*/
|
|
1335
|
-
stop(): void;
|
|
1336
|
-
/**
|
|
1337
|
-
* Registers a callback to be notified of all connection state changes.
|
|
1338
|
-
*
|
|
1339
|
-
* The callback fires for all state transitions: online → offline,
|
|
1340
|
-
* offline → degraded, degraded → online, etc.
|
|
1341
|
-
*
|
|
1342
|
-
* @param callback - Function called with (state, reason) when connection changes
|
|
1343
|
-
* @returns Cleanup function to unregister the callback
|
|
1344
|
-
*
|
|
1345
|
-
* @example
|
|
1346
|
-
* ```typescript
|
|
1347
|
-
* const cleanup = monitor.onChange((state, reason) => {
|
|
1348
|
-
* console.log(`Connection: ${state}`)
|
|
1349
|
-
* if (state === 'offline') {
|
|
1350
|
-
* showReconnectingUI()
|
|
1351
|
-
* }
|
|
1352
|
-
* })
|
|
1353
|
-
*
|
|
1354
|
-
* // Later: cleanup() to unregister
|
|
1355
|
-
* ```
|
|
1356
|
-
*/
|
|
1357
|
-
onChange(callback: ConnectionChangeCallback): () => void;
|
|
1358
|
-
/**
|
|
1359
|
-
* Gets the current connection state.
|
|
1360
|
-
*
|
|
1361
|
-
* @returns The current state ('online', 'offline', or 'degraded')
|
|
1362
|
-
*/
|
|
1363
|
-
getState(): ConnectionState;
|
|
1364
|
-
/**
|
|
1365
|
-
* Manually triggers an immediate connection check.
|
|
1366
|
-
*
|
|
1367
|
-
* Forces a heartbeat ping to verify connectivity right now, bypassing
|
|
1368
|
-
* the normal interval. Useful before critical operations.
|
|
1369
|
-
*
|
|
1370
|
-
* @returns Promise resolving to the current connection state after the check
|
|
1371
|
-
*
|
|
1372
|
-
* @example
|
|
1373
|
-
* ```typescript
|
|
1374
|
-
* const state = await monitor.checkNow()
|
|
1375
|
-
* if (state !== 'online') {
|
|
1376
|
-
* alert('Please check your internet connection')
|
|
1377
|
-
* }
|
|
1378
|
-
* ```
|
|
1379
|
-
*/
|
|
1380
|
-
checkNow(): Promise<ConnectionState>;
|
|
1381
|
-
/**
|
|
1382
|
-
* Reports a request failure for tracking.
|
|
1383
|
-
*
|
|
1384
|
-
* This should be called from your request wrapper whenever an API call fails.
|
|
1385
|
-
* Only network errors are tracked (TypeError, fetch failures) - HTTP error
|
|
1386
|
-
* responses (4xx, 5xx) are ignored.
|
|
1387
|
-
*
|
|
1388
|
-
* After consecutive failures exceed the threshold, the monitor transitions
|
|
1389
|
-
* to 'degraded' or 'offline' state.
|
|
1390
|
-
*
|
|
1391
|
-
* @param error - The error from the failed request
|
|
1392
|
-
*
|
|
1393
|
-
* @example
|
|
1394
|
-
* ```typescript
|
|
1395
|
-
* try {
|
|
1396
|
-
* await fetch('/api/data')
|
|
1397
|
-
* } catch (error) {
|
|
1398
|
-
* monitor.reportRequestFailure(error)
|
|
1399
|
-
* throw error
|
|
1400
|
-
* }
|
|
1401
|
-
* ```
|
|
1402
|
-
*/
|
|
1403
|
-
reportRequestFailure(error: unknown): void;
|
|
1404
|
-
/**
|
|
1405
|
-
* Reports a successful request.
|
|
1406
|
-
*
|
|
1407
|
-
* This should be called from your request wrapper whenever an API call succeeds.
|
|
1408
|
-
* Resets the consecutive failure counter and transitions from 'degraded' to
|
|
1409
|
-
* 'online' if the connection has recovered.
|
|
1410
|
-
*
|
|
1411
|
-
* @example
|
|
1412
|
-
* ```typescript
|
|
1413
|
-
* try {
|
|
1414
|
-
* const result = await fetch('/api/data')
|
|
1415
|
-
* monitor.reportRequestSuccess()
|
|
1416
|
-
* return result
|
|
1417
|
-
* } catch (error) {
|
|
1418
|
-
* monitor.reportRequestFailure(error)
|
|
1419
|
-
* throw error
|
|
1420
|
-
* }
|
|
1421
|
-
* ```
|
|
1422
|
-
*/
|
|
1423
|
-
reportRequestSuccess(): void;
|
|
1424
|
-
private _detectInitialState;
|
|
1425
|
-
private _handleOnline;
|
|
1426
|
-
private _handleOffline;
|
|
1427
|
-
private _startHeartbeat;
|
|
1428
|
-
private _performHeartbeat;
|
|
1429
|
-
private _handleHeartbeatFailure;
|
|
1430
|
-
private _setState;
|
|
1431
|
-
}
|
|
1432
|
-
|
|
1433
|
-
/**
|
|
1434
|
-
* Connection Manager
|
|
1435
|
-
*
|
|
1436
|
-
* Manages connection monitoring and integrates it with the Playcademy client.
|
|
1437
|
-
* Handles event wiring, state management, and disconnect callbacks.
|
|
1438
|
-
*
|
|
1439
|
-
* In iframe mode, disables local monitoring and listens to platform connection
|
|
1440
|
-
* state broadcasts instead (avoids duplicate heartbeats).
|
|
1441
|
-
*/
|
|
1442
|
-
|
|
1443
|
-
/**
|
|
1444
|
-
* Configuration for the ConnectionManager.
|
|
1445
|
-
*/
|
|
1446
|
-
interface ConnectionManagerConfig {
|
|
1447
|
-
/** Base URL for API requests (used for heartbeat pings) */
|
|
1448
|
-
baseUrl: string;
|
|
1449
|
-
/** Authentication context (iframe vs standalone) for alert routing */
|
|
1450
|
-
authContext?: {
|
|
1451
|
-
isInIframe: boolean;
|
|
1452
|
-
};
|
|
1453
|
-
/** Handler to call when connection issues are detected */
|
|
1454
|
-
onDisconnect?: DisconnectHandler;
|
|
1455
|
-
/** Callback to emit connection change events to the client */
|
|
1456
|
-
onConnectionChange?: (state: ConnectionState, reason: string) => void;
|
|
1457
|
-
}
|
|
1458
|
-
/**
|
|
1459
|
-
* Manages connection monitoring for the Playcademy client.
|
|
1460
|
-
*
|
|
1461
|
-
* The ConnectionManager serves as an integration layer between the low-level
|
|
1462
|
-
* ConnectionMonitor and the PlaycademyClient. It handles:
|
|
1463
|
-
* - Event wiring and coordination
|
|
1464
|
-
* - Disconnect callbacks with context
|
|
1465
|
-
* - Platform-level alert integration
|
|
1466
|
-
* - Request success/failure tracking
|
|
1467
|
-
*
|
|
1468
|
-
* This class is used internally by PlaycademyClient and typically not
|
|
1469
|
-
* instantiated directly by game developers.
|
|
1470
|
-
*
|
|
1471
|
-
* @see {@link ConnectionMonitor} for the underlying monitoring implementation
|
|
1472
|
-
* @see {@link PlaycademyClient.onDisconnect} for the public API
|
|
1473
|
-
*/
|
|
1474
|
-
declare class ConnectionManager {
|
|
1475
|
-
private monitor?;
|
|
1476
|
-
private authContext?;
|
|
1477
|
-
private disconnectHandler?;
|
|
1478
|
-
private connectionChangeCallback?;
|
|
1479
|
-
private currentState;
|
|
1480
|
-
private additionalDisconnectHandlers;
|
|
1481
|
-
/**
|
|
1482
|
-
* Creates a new ConnectionManager instance.
|
|
1483
|
-
*
|
|
1484
|
-
* @param config - Configuration options for the manager
|
|
1485
|
-
*
|
|
1486
|
-
* @example
|
|
1487
|
-
* ```typescript
|
|
1488
|
-
* const manager = new ConnectionManager({
|
|
1489
|
-
* baseUrl: 'https://api.playcademy.com',
|
|
1490
|
-
* authContext: { isInIframe: false },
|
|
1491
|
-
* onDisconnect: (context) => {
|
|
1492
|
-
* console.log(`Disconnected: ${context.state}`)
|
|
1493
|
-
* },
|
|
1494
|
-
* onConnectionChange: (state, reason) => {
|
|
1495
|
-
* console.log(`Connection changed: ${state}`)
|
|
1496
|
-
* }
|
|
1497
|
-
* })
|
|
1498
|
-
* ```
|
|
1499
|
-
*/
|
|
1500
|
-
constructor(config: ConnectionManagerConfig);
|
|
1501
|
-
/**
|
|
1502
|
-
* Gets the current connection state.
|
|
1503
|
-
*
|
|
1504
|
-
* @returns The current connection state ('online', 'offline', or 'degraded')
|
|
1505
|
-
*
|
|
1506
|
-
* @example
|
|
1507
|
-
* ```typescript
|
|
1508
|
-
* const state = manager.getState()
|
|
1509
|
-
* if (state === 'offline') {
|
|
1510
|
-
* console.log('No connection')
|
|
1511
|
-
* }
|
|
1512
|
-
* ```
|
|
1513
|
-
*/
|
|
1514
|
-
getState(): ConnectionState;
|
|
1515
|
-
/**
|
|
1516
|
-
* Manually triggers a connection check immediately.
|
|
1517
|
-
*
|
|
1518
|
-
* Forces a heartbeat ping to verify the current connection status.
|
|
1519
|
-
* Useful when you need to check connectivity before a critical operation.
|
|
1520
|
-
*
|
|
1521
|
-
* In iframe mode, this returns the last known state from platform.
|
|
1522
|
-
*
|
|
1523
|
-
* @returns Promise resolving to the current connection state
|
|
1524
|
-
*
|
|
1525
|
-
* @example
|
|
1526
|
-
* ```typescript
|
|
1527
|
-
* const state = await manager.checkNow()
|
|
1528
|
-
* if (state === 'online') {
|
|
1529
|
-
* await performCriticalOperation()
|
|
1530
|
-
* }
|
|
1531
|
-
* ```
|
|
1532
|
-
*/
|
|
1533
|
-
checkNow(): Promise<ConnectionState>;
|
|
1534
|
-
/**
|
|
1535
|
-
* Reports a successful API request to the connection monitor.
|
|
1536
|
-
*
|
|
1537
|
-
* This resets the consecutive failure counter and transitions from
|
|
1538
|
-
* 'degraded' to 'online' state if applicable.
|
|
1539
|
-
*
|
|
1540
|
-
* Typically called automatically by the SDK's request wrapper.
|
|
1541
|
-
* No-op in iframe mode (platform handles monitoring).
|
|
1542
|
-
*/
|
|
1543
|
-
reportRequestSuccess(): void;
|
|
1544
|
-
/**
|
|
1545
|
-
* Reports a failed API request to the connection monitor.
|
|
1546
|
-
*
|
|
1547
|
-
* Only network errors are tracked (not 4xx/5xx HTTP responses).
|
|
1548
|
-
* After consecutive failures exceed the threshold, the state transitions
|
|
1549
|
-
* to 'degraded' or 'offline'.
|
|
1550
|
-
*
|
|
1551
|
-
* Typically called automatically by the SDK's request wrapper.
|
|
1552
|
-
* No-op in iframe mode (platform handles monitoring).
|
|
1553
|
-
*
|
|
1554
|
-
* @param error - The error from the failed request
|
|
1555
|
-
*/
|
|
1556
|
-
reportRequestFailure(error: unknown): void;
|
|
1557
|
-
/**
|
|
1558
|
-
* Registers a callback to be called when connection issues are detected.
|
|
1559
|
-
*
|
|
1560
|
-
* The callback only fires for 'offline' and 'degraded' states, not when
|
|
1561
|
-
* recovering to 'online'. This provides a clean API for games to handle
|
|
1562
|
-
* disconnect scenarios without being notified of every state change.
|
|
1563
|
-
*
|
|
1564
|
-
* Works in both iframe and standalone modes transparently.
|
|
1565
|
-
*
|
|
1566
|
-
* @param callback - Function to call when connection degrades
|
|
1567
|
-
* @returns Cleanup function to unregister the callback
|
|
1568
|
-
*
|
|
1569
|
-
* @example
|
|
1570
|
-
* ```typescript
|
|
1571
|
-
* const cleanup = manager.onDisconnect(({ state, reason, displayAlert }) => {
|
|
1572
|
-
* if (state === 'offline') {
|
|
1573
|
-
* displayAlert?.('Connection lost. Saving your progress...', { type: 'error' })
|
|
1574
|
-
* saveGameState()
|
|
1575
|
-
* }
|
|
1576
|
-
* })
|
|
1577
|
-
*
|
|
1578
|
-
* // Later: cleanup() to unregister
|
|
1579
|
-
* ```
|
|
1580
|
-
*/
|
|
1581
|
-
onDisconnect(callback: DisconnectHandler): () => void;
|
|
1582
|
-
/**
|
|
1583
|
-
* Stops connection monitoring and performs cleanup.
|
|
1584
|
-
*
|
|
1585
|
-
* Removes event listeners and clears heartbeat intervals.
|
|
1586
|
-
* Should be called when the client is being destroyed.
|
|
1587
|
-
*/
|
|
1588
|
-
stop(): void;
|
|
1589
|
-
/**
|
|
1590
|
-
* Sets up listener for platform connection state broadcasts (iframe mode only).
|
|
1591
|
-
*/
|
|
1592
|
-
private _setupPlatformListener;
|
|
1593
|
-
/**
|
|
1594
|
-
* Handles connection state changes from the monitor or platform.
|
|
1595
|
-
*
|
|
1596
|
-
* Coordinates between:
|
|
1597
|
-
* 1. Emitting events to the client (for client.on('connectionChange'))
|
|
1598
|
-
* 2. Calling the disconnect handler if configured
|
|
1599
|
-
* 3. Calling any additional handlers registered via onDisconnect()
|
|
1600
|
-
*
|
|
1601
|
-
* @param state - The new connection state
|
|
1602
|
-
* @param reason - Human-readable reason for the state change
|
|
1603
|
-
*/
|
|
1604
|
-
private _handleConnectionChange;
|
|
1605
|
-
}
|
|
1606
|
-
|
|
1607
|
-
/**
|
|
1608
|
-
* @fileoverview Playcademy Messaging System
|
|
1609
|
-
*
|
|
1610
|
-
* This file implements a unified messaging system for the Playcademy platform that handles
|
|
1611
|
-
* communication between different contexts:
|
|
1612
|
-
*
|
|
1613
|
-
* 1. **Iframe-to-Parent Communication**: When games run inside iframes (production/development),
|
|
1614
|
-
* they need to communicate with the parent window using postMessage API
|
|
1615
|
-
*
|
|
1616
|
-
* 2. **Local Communication**: When games run in the same context (local development),
|
|
1617
|
-
* they use CustomEvents for internal messaging
|
|
1618
|
-
*
|
|
1619
|
-
* The system automatically detects the runtime environment and chooses the appropriate
|
|
1620
|
-
* transport method, abstracting this complexity from the developer.
|
|
1621
|
-
*
|
|
1622
|
-
* **Architecture Overview**:
|
|
1623
|
-
* - Games run in iframes for security and isolation
|
|
1624
|
-
* - Parent window (Playcademy shell) manages game lifecycle
|
|
1625
|
-
* - Messages flow bidirectionally between parent and iframe
|
|
1626
|
-
* - Local development mode simulates this architecture without iframes
|
|
1627
|
-
*/
|
|
1628
|
-
|
|
1629
|
-
/**
|
|
1630
|
-
* Enumeration of all message types used in the Playcademy messaging system.
|
|
1631
|
-
*
|
|
1632
|
-
* **Message Flow Patterns**:
|
|
1633
|
-
*
|
|
1634
|
-
* **Parent → Game (Overworld → Game)**:
|
|
1635
|
-
* - INIT: Provides game with authentication token and configuration
|
|
1636
|
-
* - TOKEN_REFRESH: Updates game's authentication token before expiry
|
|
1637
|
-
* - PAUSE/RESUME: Controls game execution state
|
|
1638
|
-
* - FORCE_EXIT: Immediately terminates the game
|
|
1639
|
-
* - OVERLAY: Shows/hides UI overlays over the game
|
|
1640
|
-
*
|
|
1641
|
-
* **Game → Parent (Game → Overworld)**:
|
|
1642
|
-
* - READY: Game has loaded and is ready to receive messages
|
|
1643
|
-
* - EXIT: Game requests to be closed (user clicked exit, game ended, etc.)
|
|
1644
|
-
* - TELEMETRY: Game reports performance metrics (FPS, memory usage, etc.)
|
|
1645
|
-
*/
|
|
1646
|
-
declare enum MessageEvents {
|
|
1647
|
-
/**
|
|
1648
|
-
* Initializes the game with authentication context and configuration.
|
|
1649
|
-
* Sent immediately after game iframe loads.
|
|
1650
|
-
* Payload:
|
|
1651
|
-
* - `baseUrl`: string
|
|
1652
|
-
* - `token`: string
|
|
1653
|
-
* - `gameId`: string
|
|
1654
|
-
*/
|
|
1655
|
-
INIT = "PLAYCADEMY_INIT",
|
|
1656
|
-
/**
|
|
1657
|
-
* Updates the game's authentication token before it expires.
|
|
1658
|
-
* Sent periodically to maintain valid authentication.
|
|
1659
|
-
* Payload:
|
|
1660
|
-
* - `token`: string
|
|
1661
|
-
* - `exp`: number
|
|
1662
|
-
*/
|
|
1663
|
-
TOKEN_REFRESH = "PLAYCADEMY_TOKEN_REFRESH",
|
|
1664
|
-
/**
|
|
1665
|
-
* Pauses game execution (e.g., when user switches tabs).
|
|
1666
|
-
* Game should pause timers, animations, and user input.
|
|
1667
|
-
* Payload: void
|
|
1668
|
-
*/
|
|
1669
|
-
PAUSE = "PLAYCADEMY_PAUSE",
|
|
1670
|
-
/**
|
|
1671
|
-
* Resumes game execution after being paused.
|
|
1672
|
-
* Game should restore timers, animations, and user input.
|
|
1673
|
-
* Payload: void
|
|
1674
|
-
*/
|
|
1675
|
-
RESUME = "PLAYCADEMY_RESUME",
|
|
1676
|
-
/**
|
|
1677
|
-
* Forces immediate game termination (emergency exit).
|
|
1678
|
-
* Game should clean up resources and exit immediately.
|
|
1679
|
-
* Payload: void
|
|
1680
|
-
*/
|
|
1681
|
-
FORCE_EXIT = "PLAYCADEMY_FORCE_EXIT",
|
|
1682
|
-
/**
|
|
1683
|
-
* Shows or hides UI overlays over the game.
|
|
1684
|
-
* Game may need to pause or adjust rendering accordingly.
|
|
1685
|
-
* Payload: boolean (true = show overlay, false = hide overlay)
|
|
1686
|
-
*/
|
|
1687
|
-
OVERLAY = "PLAYCADEMY_OVERLAY",
|
|
1688
|
-
/**
|
|
1689
|
-
* Broadcasts connection state changes to games.
|
|
1690
|
-
* Sent by platform when network connectivity changes.
|
|
1691
|
-
* Payload:
|
|
1692
|
-
* - `state`: 'online' | 'offline' | 'degraded'
|
|
1693
|
-
* - `reason`: string
|
|
1694
|
-
*/
|
|
1695
|
-
CONNECTION_STATE = "PLAYCADEMY_CONNECTION_STATE",
|
|
1696
|
-
/**
|
|
1697
|
-
* Game has finished loading and is ready to receive messages.
|
|
1698
|
-
* Sent once after game initialization is complete.
|
|
1699
|
-
* Payload: void
|
|
1700
|
-
*/
|
|
1701
|
-
READY = "PLAYCADEMY_READY",
|
|
1702
|
-
/**
|
|
1703
|
-
* Game requests to be closed/exited.
|
|
1704
|
-
* Sent when user clicks exit button or game naturally ends.
|
|
1705
|
-
* Payload: void
|
|
1706
|
-
*/
|
|
1707
|
-
EXIT = "PLAYCADEMY_EXIT",
|
|
1708
|
-
/**
|
|
1709
|
-
* Game reports performance telemetry data.
|
|
1710
|
-
* Sent periodically for monitoring and analytics.
|
|
1711
|
-
* Payload:
|
|
1712
|
-
* - `fps`: number
|
|
1713
|
-
* - `mem`: number
|
|
1714
|
-
*/
|
|
1715
|
-
TELEMETRY = "PLAYCADEMY_TELEMETRY",
|
|
1716
|
-
/**
|
|
1717
|
-
* Game reports key events to parent.
|
|
1718
|
-
* Sent when certain keys are pressed within the game iframe.
|
|
1719
|
-
* Payload:
|
|
1720
|
-
* - `key`: string
|
|
1721
|
-
* - `code?`: string
|
|
1722
|
-
* - `type`: 'keydown' | 'keyup'
|
|
1723
|
-
*/
|
|
1724
|
-
KEY_EVENT = "PLAYCADEMY_KEY_EVENT",
|
|
1725
|
-
/**
|
|
1726
|
-
* Game requests platform to display an alert.
|
|
1727
|
-
* Sent when connection issues are detected or other important events occur.
|
|
1728
|
-
* Payload:
|
|
1729
|
-
* - `message`: string
|
|
1730
|
-
* - `options`: `{ type?: 'info' | 'warning' | 'error', duration?: number }`
|
|
1731
|
-
*/
|
|
1732
|
-
DISPLAY_ALERT = "PLAYCADEMY_DISPLAY_ALERT",
|
|
1733
|
-
/**
|
|
1734
|
-
* Notifies about authentication state changes.
|
|
1735
|
-
* Can be sent in both directions depending on auth flow.
|
|
1736
|
-
* Payload:
|
|
1737
|
-
* - `authenticated`: boolean
|
|
1738
|
-
* - `user`: UserInfo | null
|
|
1739
|
-
* - `error`: Error | null
|
|
1740
|
-
*/
|
|
1741
|
-
AUTH_STATE_CHANGE = "PLAYCADEMY_AUTH_STATE_CHANGE",
|
|
1742
|
-
/**
|
|
1743
|
-
* OAuth callback data from popup/new-tab windows.
|
|
1744
|
-
* Sent from popup window back to parent after OAuth completes.
|
|
1745
|
-
* Payload:
|
|
1746
|
-
* - `code`: string (OAuth authorization code)
|
|
1747
|
-
* - `state`: string (OAuth state for CSRF protection)
|
|
1748
|
-
* - `error`: string | null (OAuth error if any)
|
|
1749
|
-
*/
|
|
1750
|
-
AUTH_CALLBACK = "PLAYCADEMY_AUTH_CALLBACK"
|
|
1751
|
-
}
|
|
1752
|
-
/**
|
|
1753
|
-
* Type definition for message handler functions.
|
|
1754
|
-
* These functions are called when a specific message type is received.
|
|
1755
|
-
*
|
|
1756
|
-
* @template T - The type of payload data the handler expects
|
|
1757
|
-
* @param payload - The data sent with the message
|
|
1758
|
-
*/
|
|
1759
|
-
type MessageHandler<T = unknown> = (payload: T) => void;
|
|
1760
|
-
/**
|
|
1761
|
-
* Type mapping that defines the payload structure for each message type.
|
|
1762
|
-
* This ensures type safety when sending and receiving messages.
|
|
1763
|
-
*
|
|
1764
|
-
* **Usage Examples**:
|
|
1765
|
-
* ```typescript
|
|
1766
|
-
* // Type-safe message sending
|
|
1767
|
-
* messaging.send(MessageEvents.INIT, { baseUrl: '/api', token: 'abc', gameId: '123' })
|
|
1768
|
-
*
|
|
1769
|
-
* // Type-safe message handling
|
|
1770
|
-
* messaging.listen(MessageEvents.TOKEN_REFRESH, ({ token, exp }) => {
|
|
1771
|
-
* console.log(`New token expires at: ${new Date(exp)}`)
|
|
1772
|
-
* })
|
|
1773
|
-
* ```
|
|
1774
|
-
*/
|
|
1775
|
-
interface MessageEventMap {
|
|
1776
|
-
/** Game initialization context with API endpoint, auth token, and game ID */
|
|
1777
|
-
[MessageEvents.INIT]: GameContextPayload;
|
|
1778
|
-
/** Token refresh data with new token and expiration timestamp */
|
|
1779
|
-
[MessageEvents.TOKEN_REFRESH]: TokenRefreshPayload;
|
|
1780
|
-
/** Pause message has no payload data */
|
|
1781
|
-
[MessageEvents.PAUSE]: void;
|
|
1782
|
-
/** Resume message has no payload data */
|
|
1783
|
-
[MessageEvents.RESUME]: void;
|
|
1784
|
-
/** Force exit message has no payload data */
|
|
1785
|
-
[MessageEvents.FORCE_EXIT]: void;
|
|
1786
|
-
/** Overlay visibility state (true = show, false = hide) */
|
|
1787
|
-
[MessageEvents.OVERLAY]: boolean;
|
|
1788
|
-
/** Connection state change from platform */
|
|
1789
|
-
[MessageEvents.CONNECTION_STATE]: ConnectionStatePayload;
|
|
1790
|
-
/** Ready message has no payload data */
|
|
1791
|
-
[MessageEvents.READY]: void;
|
|
1792
|
-
/** Exit message has no payload data */
|
|
1793
|
-
[MessageEvents.EXIT]: void;
|
|
1794
|
-
/** Performance telemetry data */
|
|
1795
|
-
[MessageEvents.TELEMETRY]: TelemetryPayload;
|
|
1796
|
-
/** Key event data */
|
|
1797
|
-
[MessageEvents.KEY_EVENT]: KeyEventPayload;
|
|
1798
|
-
/** Display alert request from game */
|
|
1799
|
-
[MessageEvents.DISPLAY_ALERT]: DisplayAlertPayload;
|
|
1800
|
-
/** Authentication state change notification */
|
|
1801
|
-
[MessageEvents.AUTH_STATE_CHANGE]: AuthStateChangePayload;
|
|
1802
|
-
/** OAuth callback data from popup/new-tab windows */
|
|
1803
|
-
[MessageEvents.AUTH_CALLBACK]: AuthCallbackPayload;
|
|
1804
|
-
}
|
|
1805
|
-
/**
|
|
1806
|
-
* **PlaycademyMessaging Class**
|
|
1807
|
-
*
|
|
1808
|
-
* This is the core messaging system that handles all communication in the Playcademy platform.
|
|
1809
|
-
* It automatically detects the runtime environment and chooses the appropriate transport method.
|
|
1810
|
-
*
|
|
1811
|
-
* **Key Features**:
|
|
1812
|
-
* 1. **Automatic Transport Selection**: Detects iframe vs local context and uses appropriate method
|
|
1813
|
-
* 2. **Type Safety**: Full TypeScript support with payload type checking
|
|
1814
|
-
* 3. **Bidirectional Communication**: Handles both parent→game and game→parent messages
|
|
1815
|
-
* 4. **Event Cleanup**: Proper listener management to prevent memory leaks
|
|
1816
|
-
*
|
|
1817
|
-
* **Transport Methods**:
|
|
1818
|
-
* - **postMessage**: Used for iframe-to-parent communication (production/development)
|
|
1819
|
-
* - **CustomEvent**: Used for local same-context communication (local development)
|
|
1820
|
-
*
|
|
1821
|
-
* **Runtime Detection Logic**:
|
|
1822
|
-
* - If `window.self !== window.top`: We're in an iframe, use postMessage
|
|
1823
|
-
* - If `window.self === window.top`: We're in the same context, use CustomEvent
|
|
1824
|
-
*
|
|
1825
|
-
* **Example Usage**:
|
|
1826
|
-
* ```typescript
|
|
1827
|
-
* // Send a message (automatically chooses transport)
|
|
1828
|
-
* messaging.send(MessageEvents.READY, undefined)
|
|
1829
|
-
*
|
|
1830
|
-
* // Listen for messages (handles both transports)
|
|
1831
|
-
* messaging.listen(MessageEvents.INIT, (payload) => {
|
|
1832
|
-
* console.log('Game initialized with:', payload)
|
|
1833
|
-
* })
|
|
1834
|
-
*
|
|
1835
|
-
* // Clean up listeners
|
|
1836
|
-
* messaging.unlisten(MessageEvents.INIT, handler)
|
|
1837
|
-
* ```
|
|
1838
|
-
*/
|
|
1839
|
-
declare class PlaycademyMessaging {
|
|
1840
|
-
/**
|
|
1841
|
-
* Internal storage for message listeners.
|
|
1842
|
-
*
|
|
1843
|
-
* **Structure Explanation**:
|
|
1844
|
-
* - Outer Map: MessageEvents → Map of handlers for that event type
|
|
1845
|
-
* - Inner Map: MessageHandler → Object containing both listener types
|
|
1846
|
-
* - Object: { postMessage: EventListener, customEvent: EventListener }
|
|
1847
|
-
*
|
|
1848
|
-
* **Why Two Listeners Per Handler?**:
|
|
1849
|
-
* Since we don't know at registration time which transport will be used,
|
|
1850
|
-
* we register both a postMessage listener and a CustomEvent listener.
|
|
1851
|
-
* The appropriate one will be triggered based on the runtime context.
|
|
1852
|
-
*/
|
|
1853
|
-
private listeners;
|
|
1854
|
-
/**
|
|
1855
|
-
* **Send Message Method**
|
|
1856
|
-
*
|
|
1857
|
-
* Sends a message using the appropriate transport method based on the runtime context.
|
|
1858
|
-
* This is the main public API for sending messages in the Playcademy system.
|
|
1859
|
-
*
|
|
1860
|
-
* **How It Works**:
|
|
1861
|
-
* 1. Analyzes the message type and current runtime context
|
|
1862
|
-
* 2. Determines if we're in an iframe and if this message should use postMessage
|
|
1863
|
-
* 3. Routes to the appropriate transport method (postMessage or CustomEvent)
|
|
1864
|
-
*
|
|
1865
|
-
* **Transport Selection Logic**:
|
|
1866
|
-
* - **postMessage**: Used when game is in iframe and sending to parent, OR when target window is specified
|
|
1867
|
-
* - **CustomEvent**: Used for local/same-context communication
|
|
1868
|
-
*
|
|
1869
|
-
* **Type Safety**:
|
|
1870
|
-
* The generic type parameter `K` ensures that the payload type matches
|
|
1871
|
-
* the expected type for the given message event.
|
|
1872
|
-
*
|
|
1873
|
-
* @template K - The message event type (ensures type safety)
|
|
1874
|
-
* @param type - The message event type to send
|
|
1875
|
-
* @param payload - The data to send with the message (type-checked)
|
|
1876
|
-
* @param options - Optional configuration for message sending
|
|
1877
|
-
*
|
|
1878
|
-
* @example
|
|
1879
|
-
* ```typescript
|
|
1880
|
-
* // Send game ready signal (no payload)
|
|
1881
|
-
* messaging.send(MessageEvents.READY, undefined)
|
|
1882
|
-
*
|
|
1883
|
-
* // Send telemetry data (typed payload)
|
|
1884
|
-
* messaging.send(MessageEvents.TELEMETRY, { fps: 60, mem: 128 })
|
|
1885
|
-
*
|
|
1886
|
-
* // Send to specific iframe window (parent to iframe communication)
|
|
1887
|
-
* messaging.send(MessageEvents.INIT, { baseUrl, token, gameId }, {
|
|
1888
|
-
* target: iframe.contentWindow,
|
|
1889
|
-
* origin: '*'
|
|
1890
|
-
* })
|
|
1891
|
-
*
|
|
1892
|
-
* // TypeScript will error if payload type is wrong
|
|
1893
|
-
* messaging.send(MessageEvents.INIT, "wrong type") // ❌ Error
|
|
1894
|
-
* ```
|
|
1895
|
-
*/
|
|
1896
|
-
send<K extends MessageEvents>(type: K, payload: MessageEventMap[K], options?: {
|
|
1897
|
-
target?: Window;
|
|
1898
|
-
origin?: string;
|
|
1899
|
-
}): void;
|
|
1900
|
-
/**
|
|
1901
|
-
* **Listen for Messages Method**
|
|
1902
|
-
*
|
|
1903
|
-
* Registers a message listener that will be called when the specified message type is received.
|
|
1904
|
-
* This method automatically handles both postMessage and CustomEvent sources.
|
|
1905
|
-
*
|
|
1906
|
-
* **Why Register Two Listeners?**:
|
|
1907
|
-
* Since we don't know at registration time which transport method will be used to send
|
|
1908
|
-
* messages, we register listeners for both possible sources:
|
|
1909
|
-
* 1. **postMessage listener**: Handles messages from iframe-to-parent communication
|
|
1910
|
-
* 2. **CustomEvent listener**: Handles messages from local same-context communication
|
|
1911
|
-
*
|
|
1912
|
-
* **Message Processing**:
|
|
1913
|
-
* - **postMessage**: Extracts data from `event.data.payload` or `event.data`
|
|
1914
|
-
* - **CustomEvent**: Extracts data from `event.detail`
|
|
1915
|
-
*
|
|
1916
|
-
* **Type Safety**:
|
|
1917
|
-
* The handler function receives the correctly typed payload based on the message type.
|
|
1918
|
-
*
|
|
1919
|
-
* @template K - The message event type (ensures type safety)
|
|
1920
|
-
* @param type - The message event type to listen for
|
|
1921
|
-
* @param handler - Function to call when the message is received
|
|
1922
|
-
*
|
|
1923
|
-
* @example
|
|
1924
|
-
* ```typescript
|
|
1925
|
-
* // Listen for game initialization
|
|
1926
|
-
* messaging.listen(MessageEvents.INIT, (payload) => {
|
|
1927
|
-
* // payload is automatically typed as GameContextPayload
|
|
1928
|
-
* console.log(`Game ${payload.gameId} initialized`)
|
|
1929
|
-
* console.log(`API base URL: ${payload.baseUrl}`)
|
|
1930
|
-
* })
|
|
1931
|
-
*
|
|
1932
|
-
* // Listen for token refresh
|
|
1933
|
-
* messaging.listen(MessageEvents.TOKEN_REFRESH, ({ token, exp }) => {
|
|
1934
|
-
* // payload is automatically typed as { token: string; exp: number }
|
|
1935
|
-
* updateAuthToken(token)
|
|
1936
|
-
* scheduleTokenRefresh(exp)
|
|
1937
|
-
* })
|
|
1938
|
-
* ```
|
|
1939
|
-
*/
|
|
1940
|
-
listen<K extends MessageEvents>(type: K, handler: MessageHandler<MessageEventMap[K]>): void;
|
|
1941
|
-
/**
|
|
1942
|
-
* **Remove Message Listener Method**
|
|
1943
|
-
*
|
|
1944
|
-
* Removes a previously registered message listener to prevent memory leaks and unwanted callbacks.
|
|
1945
|
-
* This method cleans up both the postMessage and CustomEvent listeners that were registered.
|
|
1946
|
-
*
|
|
1947
|
-
* **Why Clean Up Both Listeners?**:
|
|
1948
|
-
* When we registered the listener with `listen()`, we created two browser event listeners:
|
|
1949
|
-
* 1. A 'message' event listener for postMessage communication
|
|
1950
|
-
* 2. A custom event listener for local CustomEvent communication
|
|
1951
|
-
*
|
|
1952
|
-
* Both must be removed to prevent memory leaks and ensure the handler is completely unregistered.
|
|
1953
|
-
*
|
|
1954
|
-
* **Memory Management**:
|
|
1955
|
-
* - Removes both event listeners from the browser
|
|
1956
|
-
* - Cleans up internal tracking maps
|
|
1957
|
-
* - If no more handlers exist for a message type, removes the entire type entry
|
|
1958
|
-
*
|
|
1959
|
-
* **Safe to Call Multiple Times**:
|
|
1960
|
-
* This method is idempotent - calling it multiple times with the same handler is safe.
|
|
1961
|
-
*
|
|
1962
|
-
* @template K - The message event type (ensures type safety)
|
|
1963
|
-
* @param type - The message event type to stop listening for
|
|
1964
|
-
* @param handler - The exact handler function that was passed to listen()
|
|
1965
|
-
*
|
|
1966
|
-
* @example
|
|
1967
|
-
* ```typescript
|
|
1968
|
-
* // Register a handler
|
|
1969
|
-
* const handleInit = (payload) => console.log('Game initialized')
|
|
1970
|
-
* messaging.listen(MessageEvents.INIT, handleInit)
|
|
1971
|
-
*
|
|
1972
|
-
* // Later, remove the handler
|
|
1973
|
-
* messaging.unlisten(MessageEvents.INIT, handleInit)
|
|
1974
|
-
*
|
|
1975
|
-
* // Safe to call multiple times
|
|
1976
|
-
* messaging.unlisten(MessageEvents.INIT, handleInit) // No error
|
|
1977
|
-
* ```
|
|
1978
|
-
*/
|
|
1979
|
-
unlisten<K extends MessageEvents>(type: K, handler: MessageHandler<MessageEventMap[K]>): void;
|
|
1980
|
-
/**
|
|
1981
|
-
* **Get Messaging Context Method**
|
|
1982
|
-
*
|
|
1983
|
-
* Analyzes the current runtime environment and message type to determine the appropriate
|
|
1984
|
-
* transport method and configuration for sending messages.
|
|
1985
|
-
*
|
|
1986
|
-
* **Runtime Environment Detection**:
|
|
1987
|
-
* The method detects whether the code is running in an iframe by comparing:
|
|
1988
|
-
* - `window.self`: Reference to the current window
|
|
1989
|
-
* - `window.top`: Reference to the topmost window in the hierarchy
|
|
1990
|
-
*
|
|
1991
|
-
* If they're different, we're in an iframe. If they're the same, we're in the top-level window.
|
|
1992
|
-
*
|
|
1993
|
-
* **Message Direction Analysis**:
|
|
1994
|
-
* Different message types flow in different directions:
|
|
1995
|
-
* - **Game → Parent**: READY, EXIT, TELEMETRY (use postMessage when in iframe)
|
|
1996
|
-
* - **Parent → Game**: INIT, TOKEN_REFRESH, PAUSE, etc. (use CustomEvent in local dev, or postMessage with target)
|
|
1997
|
-
*
|
|
1998
|
-
* **Cross-Context Communication**:
|
|
1999
|
-
* The messaging system supports cross-context targeting through the optional `target` parameter.
|
|
2000
|
-
* When a target window is specified, postMessage is used regardless of the current context.
|
|
2001
|
-
* This enables parent-to-iframe communication through the unified messaging API.
|
|
2002
|
-
*
|
|
2003
|
-
* **Transport Selection Logic**:
|
|
2004
|
-
* - **postMessage**: Used when game is in iframe AND sending to parent, OR when target window is specified
|
|
2005
|
-
* - **CustomEvent**: Used for all other cases (local development, same-context communication)
|
|
2006
|
-
*
|
|
2007
|
-
* **Security Considerations**:
|
|
2008
|
-
* The origin is currently set to '*' for development convenience, but should be
|
|
2009
|
-
* configurable for production security.
|
|
2010
|
-
*
|
|
2011
|
-
* @param eventType - The message event type being sent
|
|
2012
|
-
* @returns Configuration object with transport method and target information
|
|
2013
|
-
*
|
|
2014
|
-
* @example
|
|
2015
|
-
* ```typescript
|
|
2016
|
-
* // In iframe sending READY to parent
|
|
2017
|
-
* const context = getMessagingContext(MessageEvents.READY)
|
|
2018
|
-
* // Returns: { shouldUsePostMessage: true, target: window.parent, origin: '*' }
|
|
2019
|
-
*
|
|
2020
|
-
* // In same context sending INIT
|
|
2021
|
-
* const context = getMessagingContext(MessageEvents.INIT)
|
|
2022
|
-
* // Returns: { shouldUsePostMessage: false, target: undefined, origin: '*' }
|
|
2023
|
-
* ```
|
|
2024
|
-
*/
|
|
2025
|
-
private getMessagingContext;
|
|
2026
|
-
/**
|
|
2027
|
-
* **Send Via PostMessage Method**
|
|
2028
|
-
*
|
|
2029
|
-
* Sends a message using the browser's postMessage API for iframe-to-parent communication.
|
|
2030
|
-
* This method is used when the game is running in an iframe and needs to communicate
|
|
2031
|
-
* with the parent window (the Playcademy shell).
|
|
2032
|
-
*
|
|
2033
|
-
* **PostMessage Protocol**:
|
|
2034
|
-
* The postMessage API is the standard way for iframes to communicate with their parent.
|
|
2035
|
-
* It's secure, cross-origin capable, and designed specifically for this use case.
|
|
2036
|
-
*
|
|
2037
|
-
* **Message Structure**:
|
|
2038
|
-
* The method creates a message object with the following structure:
|
|
2039
|
-
* - `type`: The message event type (e.g., 'PLAYCADEMY_READY')
|
|
2040
|
-
* - `payload`: The message data (if any)
|
|
2041
|
-
*
|
|
2042
|
-
* **Payload Handling**:
|
|
2043
|
-
* - **All payloads**: Wrapped in a `payload` property for consistency (e.g., { type, payload: data })
|
|
2044
|
-
* - **Undefined payloads**: Only the type is sent (e.g., { type })
|
|
2045
|
-
*
|
|
2046
|
-
* **Security**:
|
|
2047
|
-
* The origin parameter controls which domains can receive the message.
|
|
2048
|
-
* Currently set to '*' for development, but should be restricted in production.
|
|
2049
|
-
*
|
|
2050
|
-
* @template K - The message event type (ensures type safety)
|
|
2051
|
-
* @param type - The message event type to send
|
|
2052
|
-
* @param payload - The data to send with the message
|
|
2053
|
-
* @param target - The target window (defaults to parent window)
|
|
2054
|
-
* @param origin - The allowed origin for the message (defaults to '*')
|
|
2055
|
-
*
|
|
2056
|
-
* @example
|
|
2057
|
-
* ```typescript
|
|
2058
|
-
* // Send ready signal (no payload)
|
|
2059
|
-
* sendViaPostMessage(MessageEvents.READY, undefined)
|
|
2060
|
-
* // Sends: { type: 'PLAYCADEMY_READY' }
|
|
2061
|
-
*
|
|
2062
|
-
* // Send telemetry data (object payload)
|
|
2063
|
-
* sendViaPostMessage(MessageEvents.TELEMETRY, { fps: 60, mem: 128 })
|
|
2064
|
-
* // Sends: { type: 'PLAYCADEMY_TELEMETRY', payload: { fps: 60, mem: 128 } }
|
|
2065
|
-
*
|
|
2066
|
-
* // Send overlay state (primitive payload)
|
|
2067
|
-
* sendViaPostMessage(MessageEvents.OVERLAY, true)
|
|
2068
|
-
* // Sends: { type: 'PLAYCADEMY_OVERLAY', payload: true }
|
|
2069
|
-
* ```
|
|
2070
|
-
*/
|
|
2071
|
-
private sendViaPostMessage;
|
|
2072
|
-
/**
|
|
2073
|
-
* **Send Via CustomEvent Method**
|
|
2074
|
-
*
|
|
2075
|
-
* Sends a message using the browser's CustomEvent API for local same-context communication.
|
|
2076
|
-
* This method is used when both the sender and receiver are in the same window context,
|
|
2077
|
-
* typically during local development or when the parent needs to send messages to the game.
|
|
2078
|
-
*
|
|
2079
|
-
* **CustomEvent Protocol**:
|
|
2080
|
-
* CustomEvent is a browser API for creating and dispatching custom events within the same
|
|
2081
|
-
* window context. It's simpler than postMessage but only works within the same origin/context.
|
|
2082
|
-
*
|
|
2083
|
-
* **Event Structure**:
|
|
2084
|
-
* - **type**: The event name (e.g., 'PLAYCADEMY_INIT')
|
|
2085
|
-
* - **detail**: The payload data attached to the event
|
|
2086
|
-
*
|
|
2087
|
-
* **When This Is Used**:
|
|
2088
|
-
* - Local development when game and shell run in the same context
|
|
2089
|
-
* - Parent-to-game communication (INIT, TOKEN_REFRESH, PAUSE, etc.)
|
|
2090
|
-
* - Any scenario where postMessage isn't needed
|
|
2091
|
-
*
|
|
2092
|
-
* **Event Bubbling**:
|
|
2093
|
-
* CustomEvents are dispatched on the window object and can be listened to by any
|
|
2094
|
-
* code in the same context using `addEventListener(type, handler)`.
|
|
2095
|
-
*
|
|
2096
|
-
* @template K - The message event type (ensures type safety)
|
|
2097
|
-
* @param type - The message event type to send (becomes the event name)
|
|
2098
|
-
* @param payload - The data to send with the message (stored in event.detail)
|
|
2099
|
-
*
|
|
2100
|
-
* @example
|
|
2101
|
-
* ```typescript
|
|
2102
|
-
* // Send initialization data
|
|
2103
|
-
* sendViaCustomEvent(MessageEvents.INIT, {
|
|
2104
|
-
* baseUrl: '/api',
|
|
2105
|
-
* token: 'abc123',
|
|
2106
|
-
* gameId: 'game-456'
|
|
2107
|
-
* })
|
|
2108
|
-
* // Creates: CustomEvent('PLAYCADEMY_INIT', { detail: { baseUrl, token, gameId } })
|
|
2109
|
-
*
|
|
2110
|
-
* // Send pause signal
|
|
2111
|
-
* sendViaCustomEvent(MessageEvents.PAUSE, undefined)
|
|
2112
|
-
* // Creates: CustomEvent('PLAYCADEMY_PAUSE', { detail: undefined })
|
|
2113
|
-
*
|
|
2114
|
-
* // Listeners can access the data via event.detail:
|
|
2115
|
-
* window.addEventListener('PLAYCADEMY_INIT', (event) => {
|
|
2116
|
-
* console.log(event.detail.gameId) // 'game-456'
|
|
2117
|
-
* })
|
|
2118
|
-
* ```
|
|
2119
|
-
*/
|
|
2120
|
-
private sendViaCustomEvent;
|
|
2121
|
-
}
|
|
2122
|
-
/**
|
|
2123
|
-
* **Playcademy Messaging Singleton**
|
|
2124
|
-
*
|
|
2125
|
-
* This is the main messaging instance used throughout the Playcademy platform.
|
|
2126
|
-
* It's exported as a singleton to ensure consistent communication across all parts
|
|
2127
|
-
* of the application.
|
|
2128
|
-
*
|
|
2129
|
-
* **Why a Singleton?**:
|
|
2130
|
-
* - Ensures all parts of the app use the same messaging instance
|
|
2131
|
-
* - Prevents conflicts between multiple messaging systems
|
|
2132
|
-
* - Simplifies the API - no need to pass instances around
|
|
2133
|
-
* - Maintains consistent event listener management
|
|
2134
|
-
*
|
|
2135
|
-
* **Usage in Different Contexts**:
|
|
2136
|
-
*
|
|
2137
|
-
* **In Games**:
|
|
2138
|
-
* ```typescript
|
|
2139
|
-
* import { messaging, MessageEvents } from '@playcademy/sdk'
|
|
2140
|
-
*
|
|
2141
|
-
* // Tell parent we're ready
|
|
2142
|
-
* messaging.send(MessageEvents.READY, undefined)
|
|
2143
|
-
*
|
|
2144
|
-
* // Listen for pause/resume
|
|
2145
|
-
* messaging.listen(MessageEvents.PAUSE, () => game.pause())
|
|
2146
|
-
* messaging.listen(MessageEvents.RESUME, () => game.resume())
|
|
2147
|
-
* ```
|
|
2148
|
-
*
|
|
2149
|
-
* **In Parent Shell**:
|
|
2150
|
-
* ```typescript
|
|
2151
|
-
* import { messaging, MessageEvents } from '@playcademy/sdk'
|
|
2152
|
-
*
|
|
2153
|
-
* // Send initialization data to game
|
|
2154
|
-
* messaging.send(MessageEvents.INIT, { baseUrl, token, gameId })
|
|
2155
|
-
*
|
|
2156
|
-
* // Listen for game events
|
|
2157
|
-
* messaging.listen(MessageEvents.EXIT, () => closeGame())
|
|
2158
|
-
* messaging.listen(MessageEvents.READY, () => showGame())
|
|
2159
|
-
* ```
|
|
2160
|
-
*
|
|
2161
|
-
* **Automatic Transport Selection**:
|
|
2162
|
-
* The messaging system automatically chooses the right transport method:
|
|
2163
|
-
* - Uses postMessage when game is in iframe sending to parent
|
|
2164
|
-
* - Uses CustomEvent for local development and parent-to-game communication
|
|
2165
|
-
*
|
|
2166
|
-
* **Type Safety**:
|
|
2167
|
-
* All message sending and receiving is fully type-safe with TypeScript.
|
|
2168
|
-
*/
|
|
2169
|
-
declare const messaging: PlaycademyMessaging;
|
|
2170
|
-
|
|
2171
|
-
declare const users: drizzle_orm_pg_core.PgTableWithColumns<{
|
|
2172
|
-
name: "user";
|
|
2173
|
-
schema: undefined;
|
|
2174
|
-
columns: {
|
|
2175
|
-
id: drizzle_orm_pg_core.PgColumn<{
|
|
2176
|
-
name: "id";
|
|
2177
|
-
tableName: "user";
|
|
2178
|
-
dataType: "string";
|
|
2179
|
-
columnType: "PgText";
|
|
2180
|
-
data: string;
|
|
2181
|
-
driverParam: string;
|
|
2182
|
-
notNull: true;
|
|
2183
|
-
hasDefault: true;
|
|
2184
|
-
isPrimaryKey: true;
|
|
2185
|
-
isAutoincrement: false;
|
|
2186
|
-
hasRuntimeDefault: true;
|
|
2187
|
-
enumValues: [string, ...string[]];
|
|
2188
|
-
baseColumn: never;
|
|
2189
|
-
identity: undefined;
|
|
2190
|
-
generated: undefined;
|
|
2191
|
-
}, {}, {}>;
|
|
2192
|
-
name: drizzle_orm_pg_core.PgColumn<{
|
|
2193
|
-
name: "name";
|
|
2194
|
-
tableName: "user";
|
|
2195
|
-
dataType: "string";
|
|
2196
|
-
columnType: "PgText";
|
|
2197
|
-
data: string;
|
|
2198
|
-
driverParam: string;
|
|
2199
|
-
notNull: true;
|
|
2200
|
-
hasDefault: false;
|
|
2201
|
-
isPrimaryKey: false;
|
|
2202
|
-
isAutoincrement: false;
|
|
2203
|
-
hasRuntimeDefault: false;
|
|
2204
|
-
enumValues: [string, ...string[]];
|
|
2205
|
-
baseColumn: never;
|
|
2206
|
-
identity: undefined;
|
|
2207
|
-
generated: undefined;
|
|
2208
|
-
}, {}, {}>;
|
|
2209
|
-
username: drizzle_orm_pg_core.PgColumn<{
|
|
2210
|
-
name: "username";
|
|
2211
|
-
tableName: "user";
|
|
2212
|
-
dataType: "string";
|
|
2213
|
-
columnType: "PgText";
|
|
2214
|
-
data: string;
|
|
2215
|
-
driverParam: string;
|
|
2216
|
-
notNull: false;
|
|
2217
|
-
hasDefault: false;
|
|
2218
|
-
isPrimaryKey: false;
|
|
2219
|
-
isAutoincrement: false;
|
|
2220
|
-
hasRuntimeDefault: false;
|
|
2221
|
-
enumValues: [string, ...string[]];
|
|
2222
|
-
baseColumn: never;
|
|
2223
|
-
identity: undefined;
|
|
2224
|
-
generated: undefined;
|
|
2225
|
-
}, {}, {}>;
|
|
2226
|
-
email: drizzle_orm_pg_core.PgColumn<{
|
|
2227
|
-
name: "email";
|
|
2228
|
-
tableName: "user";
|
|
2229
|
-
dataType: "string";
|
|
2230
|
-
columnType: "PgText";
|
|
2231
|
-
data: string;
|
|
2232
|
-
driverParam: string;
|
|
2233
|
-
notNull: true;
|
|
2234
|
-
hasDefault: false;
|
|
2235
|
-
isPrimaryKey: false;
|
|
2236
|
-
isAutoincrement: false;
|
|
2237
|
-
hasRuntimeDefault: false;
|
|
2238
|
-
enumValues: [string, ...string[]];
|
|
2239
|
-
baseColumn: never;
|
|
2240
|
-
identity: undefined;
|
|
2241
|
-
generated: undefined;
|
|
2242
|
-
}, {}, {}>;
|
|
2243
|
-
timebackId: drizzle_orm_pg_core.PgColumn<{
|
|
2244
|
-
name: "timeback_id";
|
|
2245
|
-
tableName: "user";
|
|
2246
|
-
dataType: "string";
|
|
2247
|
-
columnType: "PgText";
|
|
2248
|
-
data: string;
|
|
2249
|
-
driverParam: string;
|
|
2250
|
-
notNull: false;
|
|
2251
|
-
hasDefault: false;
|
|
2252
|
-
isPrimaryKey: false;
|
|
2253
|
-
isAutoincrement: false;
|
|
2254
|
-
hasRuntimeDefault: false;
|
|
2255
|
-
enumValues: [string, ...string[]];
|
|
2256
|
-
baseColumn: never;
|
|
2257
|
-
identity: undefined;
|
|
2258
|
-
generated: undefined;
|
|
2259
|
-
}, {}, {}>;
|
|
2260
|
-
emailVerified: drizzle_orm_pg_core.PgColumn<{
|
|
2261
|
-
name: "email_verified";
|
|
2262
|
-
tableName: "user";
|
|
2263
|
-
dataType: "boolean";
|
|
2264
|
-
columnType: "PgBoolean";
|
|
2265
|
-
data: boolean;
|
|
2266
|
-
driverParam: boolean;
|
|
2267
|
-
notNull: true;
|
|
2268
|
-
hasDefault: true;
|
|
2269
|
-
isPrimaryKey: false;
|
|
2270
|
-
isAutoincrement: false;
|
|
2271
|
-
hasRuntimeDefault: false;
|
|
2272
|
-
enumValues: undefined;
|
|
2273
|
-
baseColumn: never;
|
|
2274
|
-
identity: undefined;
|
|
2275
|
-
generated: undefined;
|
|
2276
|
-
}, {}, {}>;
|
|
2277
|
-
image: drizzle_orm_pg_core.PgColumn<{
|
|
2278
|
-
name: "image";
|
|
2279
|
-
tableName: "user";
|
|
2280
|
-
dataType: "string";
|
|
2281
|
-
columnType: "PgText";
|
|
2282
|
-
data: string;
|
|
2283
|
-
driverParam: string;
|
|
2284
|
-
notNull: false;
|
|
2285
|
-
hasDefault: false;
|
|
2286
|
-
isPrimaryKey: false;
|
|
2287
|
-
isAutoincrement: false;
|
|
2288
|
-
hasRuntimeDefault: false;
|
|
2289
|
-
enumValues: [string, ...string[]];
|
|
2290
|
-
baseColumn: never;
|
|
2291
|
-
identity: undefined;
|
|
2292
|
-
generated: undefined;
|
|
2293
|
-
}, {}, {}>;
|
|
2294
|
-
role: drizzle_orm_pg_core.PgColumn<{
|
|
2295
|
-
name: "role";
|
|
2296
|
-
tableName: "user";
|
|
2297
|
-
dataType: "string";
|
|
2298
|
-
columnType: "PgEnumColumn";
|
|
2299
|
-
data: "admin" | "developer" | "player" | "teacher";
|
|
2300
|
-
driverParam: string;
|
|
2301
|
-
notNull: true;
|
|
2302
|
-
hasDefault: true;
|
|
2303
|
-
isPrimaryKey: false;
|
|
2304
|
-
isAutoincrement: false;
|
|
2305
|
-
hasRuntimeDefault: false;
|
|
2306
|
-
enumValues: ["admin", "player", "developer", "teacher"];
|
|
2307
|
-
baseColumn: never;
|
|
2308
|
-
identity: undefined;
|
|
2309
|
-
generated: undefined;
|
|
2310
|
-
}, {}, {}>;
|
|
2311
|
-
developerStatus: drizzle_orm_pg_core.PgColumn<{
|
|
2312
|
-
name: "developer_status";
|
|
2313
|
-
tableName: "user";
|
|
2314
|
-
dataType: "string";
|
|
2315
|
-
columnType: "PgEnumColumn";
|
|
2316
|
-
data: "approved" | "none" | "pending";
|
|
2317
|
-
driverParam: string;
|
|
2318
|
-
notNull: true;
|
|
2319
|
-
hasDefault: true;
|
|
2320
|
-
isPrimaryKey: false;
|
|
2321
|
-
isAutoincrement: false;
|
|
2322
|
-
hasRuntimeDefault: false;
|
|
2323
|
-
enumValues: ["none", "pending", "approved"];
|
|
2324
|
-
baseColumn: never;
|
|
2325
|
-
identity: undefined;
|
|
2326
|
-
generated: undefined;
|
|
2327
|
-
}, {}, {}>;
|
|
2328
|
-
characterCreated: drizzle_orm_pg_core.PgColumn<{
|
|
2329
|
-
name: "character_created";
|
|
2330
|
-
tableName: "user";
|
|
2331
|
-
dataType: "boolean";
|
|
2332
|
-
columnType: "PgBoolean";
|
|
2333
|
-
data: boolean;
|
|
2334
|
-
driverParam: boolean;
|
|
2335
|
-
notNull: true;
|
|
2336
|
-
hasDefault: true;
|
|
2337
|
-
isPrimaryKey: false;
|
|
2338
|
-
isAutoincrement: false;
|
|
2339
|
-
hasRuntimeDefault: false;
|
|
2340
|
-
enumValues: undefined;
|
|
2341
|
-
baseColumn: never;
|
|
2342
|
-
identity: undefined;
|
|
2343
|
-
generated: undefined;
|
|
2344
|
-
}, {}, {}>;
|
|
2345
|
-
createdAt: drizzle_orm_pg_core.PgColumn<{
|
|
2346
|
-
name: "created_at";
|
|
2347
|
-
tableName: "user";
|
|
2348
|
-
dataType: "date";
|
|
2349
|
-
columnType: "PgTimestamp";
|
|
2350
|
-
data: Date;
|
|
2351
|
-
driverParam: string;
|
|
2352
|
-
notNull: true;
|
|
2353
|
-
hasDefault: false;
|
|
2354
|
-
isPrimaryKey: false;
|
|
2355
|
-
isAutoincrement: false;
|
|
2356
|
-
hasRuntimeDefault: false;
|
|
2357
|
-
enumValues: undefined;
|
|
2358
|
-
baseColumn: never;
|
|
2359
|
-
identity: undefined;
|
|
2360
|
-
generated: undefined;
|
|
2361
|
-
}, {}, {}>;
|
|
2362
|
-
updatedAt: drizzle_orm_pg_core.PgColumn<{
|
|
2363
|
-
name: "updated_at";
|
|
2364
|
-
tableName: "user";
|
|
2365
|
-
dataType: "date";
|
|
2366
|
-
columnType: "PgTimestamp";
|
|
2367
|
-
data: Date;
|
|
2368
|
-
driverParam: string;
|
|
2369
|
-
notNull: true;
|
|
2370
|
-
hasDefault: false;
|
|
2371
|
-
isPrimaryKey: false;
|
|
2372
|
-
isAutoincrement: false;
|
|
2373
|
-
hasRuntimeDefault: false;
|
|
2374
|
-
enumValues: undefined;
|
|
2375
|
-
baseColumn: never;
|
|
2376
|
-
identity: undefined;
|
|
2377
|
-
generated: undefined;
|
|
2378
|
-
}, {}, {}>;
|
|
2379
|
-
};
|
|
2380
|
-
dialect: "pg";
|
|
2381
|
-
}>;
|
|
2382
|
-
|
|
2383
|
-
interface GameMetadata {
|
|
2384
|
-
description?: string;
|
|
2385
|
-
emoji?: string;
|
|
2386
|
-
[key: string]: unknown;
|
|
2387
|
-
}
|
|
2388
|
-
/**
|
|
2389
|
-
* DNS validation records for custom hostname
|
|
2390
|
-
* Structure for the validationRecords JSON field in game_custom_hostnames table
|
|
1289
|
+
* DNS validation records for custom hostname
|
|
1290
|
+
* Structure for the validationRecords JSON field in game_custom_hostnames table
|
|
2391
1291
|
*/
|
|
2392
1292
|
interface CustomHostnameValidationRecords {
|
|
2393
1293
|
/** TXT record for ownership verification */
|
|
@@ -5616,267 +4516,1367 @@ type UpdateCurrencyInput = z.infer<typeof UpdateCurrencySchema>;
|
|
|
5616
4516
|
type InsertShopListingInput = z.infer<typeof InsertShopListingSchema>;
|
|
5617
4517
|
type UpdateShopListingInput = z.infer<typeof UpdateShopListingSchema>;
|
|
5618
4518
|
|
|
5619
|
-
type MapRow = typeof maps.$inferSelect;
|
|
5620
|
-
type MapElementRow = typeof mapElements.$inferSelect;
|
|
5621
|
-
type MapObjectRow = typeof mapObjects.$inferSelect;
|
|
4519
|
+
type MapRow = typeof maps.$inferSelect;
|
|
4520
|
+
type MapElementRow = typeof mapElements.$inferSelect;
|
|
4521
|
+
type MapObjectRow = typeof mapObjects.$inferSelect;
|
|
4522
|
+
|
|
4523
|
+
type UserRow = typeof users.$inferSelect;
|
|
4524
|
+
|
|
4525
|
+
/**
|
|
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
|
+
*/
|
|
5622
4849
|
|
|
5623
|
-
|
|
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
|
+
}
|
|
5624
5013
|
|
|
5625
5014
|
/**
|
|
5626
|
-
*
|
|
5627
|
-
*
|
|
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.
|
|
5628
5019
|
*/
|
|
5629
|
-
|
|
5630
|
-
spriteSheetUrl: string;
|
|
5631
|
-
};
|
|
5632
|
-
type InventoryItemWithItem = InventoryItemRow & {
|
|
5633
|
-
item: ItemRow;
|
|
5634
|
-
};
|
|
5020
|
+
|
|
5635
5021
|
/**
|
|
5636
|
-
*
|
|
5022
|
+
* Base interface for authentication strategies
|
|
5637
5023
|
*/
|
|
5638
|
-
|
|
5639
|
-
|
|
5640
|
-
|
|
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
|
+
|
|
5641
5033
|
/**
|
|
5642
|
-
*
|
|
5643
|
-
*
|
|
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).
|
|
5644
5039
|
*/
|
|
5645
|
-
|
|
5646
|
-
|
|
5647
|
-
|
|
5648
|
-
|
|
5649
|
-
|
|
5650
|
-
|
|
5651
|
-
|
|
5652
|
-
|
|
5653
|
-
|
|
5654
|
-
|
|
5655
|
-
|
|
5656
|
-
|
|
5657
|
-
|
|
5658
|
-
|
|
5659
|
-
|
|
5660
|
-
|
|
5661
|
-
|
|
5662
|
-
|
|
5663
|
-
|
|
5664
|
-
|
|
5665
|
-
|
|
5666
|
-
|
|
5667
|
-
|
|
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
|
+
};
|
|
5668
5167
|
}
|
|
5669
|
-
|
|
5670
|
-
|
|
5671
|
-
|
|
5672
|
-
|
|
5673
|
-
|
|
5674
|
-
|
|
5675
|
-
worldY?: number;
|
|
5676
|
-
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);
|
|
5677
5174
|
}
|
|
5678
|
-
|
|
5679
|
-
|
|
5680
|
-
|
|
5681
|
-
|
|
5682
|
-
|
|
5683
|
-
|
|
5684
|
-
|
|
5685
|
-
|
|
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
|
+
};
|
|
5686
5200
|
}
|
|
5687
5201
|
/**
|
|
5688
|
-
*
|
|
5689
|
-
*
|
|
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
|
+
* ```
|
|
5690
5223
|
*/
|
|
5691
|
-
|
|
5692
|
-
|
|
5693
|
-
|
|
5694
|
-
|
|
5695
|
-
|
|
5696
|
-
|
|
5697
|
-
|
|
5698
|
-
|
|
5699
|
-
|
|
5700
|
-
|
|
5701
|
-
|
|
5702
|
-
|
|
5703
|
-
|
|
5704
|
-
|
|
5705
|
-
|
|
5706
|
-
|
|
5707
|
-
|
|
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
|
+
}
|
|
5708
5286
|
/**
|
|
5709
|
-
*
|
|
5287
|
+
* Extracted error information for display purposes.
|
|
5710
5288
|
*/
|
|
5711
|
-
|
|
5712
|
-
|
|
5713
|
-
|
|
5714
|
-
|
|
5715
|
-
|
|
5716
|
-
|
|
5717
|
-
|
|
5718
|
-
|
|
5719
|
-
|
|
5720
|
-
}
|
|
5721
|
-
|
|
5722
|
-
type SpriteTemplateRow = typeof spriteTemplates.$inferSelect;
|
|
5723
|
-
|
|
5724
|
-
type NotificationRow = InferSelectModel<typeof notifications>;
|
|
5725
|
-
|
|
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
|
+
}
|
|
5726
5299
|
/**
|
|
5727
|
-
*
|
|
5300
|
+
* Extract useful error information from an API error.
|
|
5301
|
+
* Useful for displaying errors to users in a friendly way.
|
|
5728
5302
|
*
|
|
5729
|
-
*
|
|
5730
|
-
*
|
|
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
|
+
* ```
|
|
5731
5314
|
*/
|
|
5315
|
+
declare function extractApiErrorInfo(error: unknown): ApiErrorInfo | null;
|
|
5732
5316
|
|
|
5733
5317
|
/**
|
|
5734
|
-
*
|
|
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
|
|
5735
5337
|
*/
|
|
5736
|
-
interface AuthStrategy {
|
|
5737
|
-
/** Get the token value */
|
|
5738
|
-
getToken(): string | null;
|
|
5739
|
-
/** Get the token type */
|
|
5740
|
-
getType(): TokenType;
|
|
5741
|
-
/** Get authentication headers for a request */
|
|
5742
|
-
getHeaders(): Record<string, string>;
|
|
5743
|
-
}
|
|
5744
5338
|
|
|
5745
5339
|
/**
|
|
5746
|
-
*
|
|
5747
|
-
* Provides authentication, HTTP requests, events, connection monitoring,
|
|
5748
|
-
* and fundamental namespaces used by all clients.
|
|
5340
|
+
* Enumeration of all message types used in the Playcademy messaging system.
|
|
5749
5341
|
*
|
|
5750
|
-
*
|
|
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.)
|
|
5751
5355
|
*/
|
|
5752
|
-
declare
|
|
5753
|
-
baseUrl: string;
|
|
5754
|
-
gameUrl?: string;
|
|
5755
|
-
protected authStrategy: AuthStrategy;
|
|
5756
|
-
protected gameId?: string;
|
|
5757
|
-
protected config: Partial<ClientConfig>;
|
|
5758
|
-
protected listeners: EventListeners;
|
|
5759
|
-
protected internalClientSessionId?: string;
|
|
5760
|
-
protected authContext?: {
|
|
5761
|
-
isInIframe: boolean;
|
|
5762
|
-
};
|
|
5763
|
-
protected initPayload?: InitPayload;
|
|
5764
|
-
protected connectionManager?: ConnectionManager;
|
|
5765
|
-
protected launchId?: string;
|
|
5356
|
+
declare enum MessageEvents {
|
|
5766
5357
|
/**
|
|
5767
|
-
*
|
|
5768
|
-
*
|
|
5769
|
-
*
|
|
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
|
|
5770
5364
|
*/
|
|
5771
|
-
|
|
5772
|
-
startSession: (gameId: string) => Promise<{
|
|
5773
|
-
sessionId: string;
|
|
5774
|
-
}>;
|
|
5775
|
-
endSession: (sessionId: string, gameId: string) => Promise<void>;
|
|
5776
|
-
};
|
|
5777
|
-
constructor(config?: Partial<ClientConfig>);
|
|
5365
|
+
INIT = "PLAYCADEMY_INIT",
|
|
5778
5366
|
/**
|
|
5779
|
-
*
|
|
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
|
|
5780
5372
|
*/
|
|
5781
|
-
|
|
5373
|
+
TOKEN_REFRESH = "PLAYCADEMY_TOKEN_REFRESH",
|
|
5782
5374
|
/**
|
|
5783
|
-
*
|
|
5375
|
+
* Pauses game execution (e.g., when user switches tabs).
|
|
5376
|
+
* Game should pause timers, animations, and user input.
|
|
5377
|
+
* Payload: void
|
|
5784
5378
|
*/
|
|
5785
|
-
|
|
5379
|
+
PAUSE = "PLAYCADEMY_PAUSE",
|
|
5786
5380
|
/**
|
|
5787
|
-
*
|
|
5381
|
+
* Resumes game execution after being paused.
|
|
5382
|
+
* Game should restore timers, animations, and user input.
|
|
5383
|
+
* Payload: void
|
|
5788
5384
|
*/
|
|
5789
|
-
|
|
5385
|
+
RESUME = "PLAYCADEMY_RESUME",
|
|
5790
5386
|
/**
|
|
5791
|
-
*
|
|
5387
|
+
* Forces immediate game termination (emergency exit).
|
|
5388
|
+
* Game should clean up resources and exit immediately.
|
|
5389
|
+
* Payload: void
|
|
5792
5390
|
*/
|
|
5793
|
-
|
|
5794
|
-
setLaunchId(launchId: string | null | undefined): void;
|
|
5391
|
+
FORCE_EXIT = "PLAYCADEMY_FORCE_EXIT",
|
|
5795
5392
|
/**
|
|
5796
|
-
*
|
|
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)
|
|
5797
5396
|
*/
|
|
5798
|
-
|
|
5397
|
+
OVERLAY = "PLAYCADEMY_OVERLAY",
|
|
5799
5398
|
/**
|
|
5800
|
-
*
|
|
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
|
|
5801
5404
|
*/
|
|
5802
|
-
|
|
5405
|
+
CONNECTION_STATE = "PLAYCADEMY_CONNECTION_STATE",
|
|
5803
5406
|
/**
|
|
5804
|
-
*
|
|
5407
|
+
* Game has finished loading and is ready to receive messages.
|
|
5408
|
+
* Sent once after game initialization is complete.
|
|
5409
|
+
* Payload: void
|
|
5805
5410
|
*/
|
|
5806
|
-
|
|
5411
|
+
READY = "PLAYCADEMY_READY",
|
|
5807
5412
|
/**
|
|
5808
|
-
*
|
|
5413
|
+
* Game requests to be closed/exited.
|
|
5414
|
+
* Sent when user clicks exit button or game naturally ends.
|
|
5415
|
+
* Payload: void
|
|
5809
5416
|
*/
|
|
5810
|
-
|
|
5417
|
+
EXIT = "PLAYCADEMY_EXIT",
|
|
5811
5418
|
/**
|
|
5812
|
-
*
|
|
5419
|
+
* Game reports performance telemetry data.
|
|
5420
|
+
* Sent periodically for monitoring and analytics.
|
|
5421
|
+
* Payload:
|
|
5422
|
+
* - `fps`: number
|
|
5423
|
+
* - `mem`: number
|
|
5813
5424
|
*/
|
|
5814
|
-
|
|
5425
|
+
TELEMETRY = "PLAYCADEMY_TELEMETRY",
|
|
5815
5426
|
/**
|
|
5816
|
-
*
|
|
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'
|
|
5817
5433
|
*/
|
|
5818
|
-
|
|
5434
|
+
KEY_EVENT = "PLAYCADEMY_KEY_EVENT",
|
|
5819
5435
|
/**
|
|
5820
|
-
*
|
|
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 }`
|
|
5821
5441
|
*/
|
|
5822
|
-
|
|
5442
|
+
DISPLAY_ALERT = "PLAYCADEMY_DISPLAY_ALERT",
|
|
5823
5443
|
/**
|
|
5824
|
-
*
|
|
5825
|
-
*
|
|
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
|
|
5826
5450
|
*/
|
|
5827
|
-
|
|
5828
|
-
isInIframe: boolean;
|
|
5829
|
-
}): void;
|
|
5451
|
+
AUTH_STATE_CHANGE = "PLAYCADEMY_AUTH_STATE_CHANGE",
|
|
5830
5452
|
/**
|
|
5831
|
-
*
|
|
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)
|
|
5832
5459
|
*/
|
|
5833
|
-
|
|
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 {
|
|
5834
5550
|
/**
|
|
5835
|
-
*
|
|
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.
|
|
5836
5562
|
*/
|
|
5837
|
-
|
|
5563
|
+
private listeners;
|
|
5838
5564
|
/**
|
|
5839
|
-
*
|
|
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
|
+
* ```
|
|
5840
5605
|
*/
|
|
5841
|
-
|
|
5842
|
-
|
|
5843
|
-
|
|
5844
|
-
|
|
5845
|
-
}): Promise<T>;
|
|
5606
|
+
send<K extends MessageEvents>(type: K, payload: MessageEventMap[K], options?: {
|
|
5607
|
+
target?: Window;
|
|
5608
|
+
origin?: string;
|
|
5609
|
+
}): void;
|
|
5846
5610
|
/**
|
|
5847
|
-
*
|
|
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
|
+
* ```
|
|
5848
5649
|
*/
|
|
5849
|
-
|
|
5650
|
+
listen<K extends MessageEvents>(type: K, handler: MessageHandler<MessageEventMap[K]>): void;
|
|
5850
5651
|
/**
|
|
5851
|
-
*
|
|
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
|
+
* ```
|
|
5852
5688
|
*/
|
|
5853
|
-
|
|
5689
|
+
unlisten<K extends MessageEvents>(type: K, handler: MessageHandler<MessageEventMap[K]>): void;
|
|
5854
5690
|
/**
|
|
5855
|
-
*
|
|
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
|
+
* ```
|
|
5856
5734
|
*/
|
|
5857
|
-
private
|
|
5735
|
+
private getMessagingContext;
|
|
5858
5736
|
/**
|
|
5859
|
-
*
|
|
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
|
+
* ```
|
|
5860
5780
|
*/
|
|
5861
|
-
private
|
|
5862
|
-
private _initializeInternalSession;
|
|
5781
|
+
private sendViaPostMessage;
|
|
5863
5782
|
/**
|
|
5864
|
-
*
|
|
5865
|
-
*
|
|
5866
|
-
*
|
|
5867
|
-
*
|
|
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
|
+
* ```
|
|
5868
5829
|
*/
|
|
5869
|
-
|
|
5870
|
-
me: () => Promise<AuthenticatedUser>;
|
|
5871
|
-
inventory: {
|
|
5872
|
-
get: () => Promise<InventoryItemWithItem[]>;
|
|
5873
|
-
add: (identifier: string, qty: number) => Promise<InventoryMutationResponse>;
|
|
5874
|
-
remove: (identifier: string, qty: number) => Promise<InventoryMutationResponse>;
|
|
5875
|
-
quantity: (identifier: string) => Promise<number>;
|
|
5876
|
-
has: (identifier: string, minQuantity?: number) => Promise<boolean>;
|
|
5877
|
-
};
|
|
5878
|
-
};
|
|
5830
|
+
private sendViaCustomEvent;
|
|
5879
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;
|
|
5880
5880
|
|
|
5881
5881
|
/**
|
|
5882
5882
|
* Auto-initializes a PlaycademyClient with context from the environment.
|