@playcademy/sdk 0.2.1 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,531 +1,246 @@
1
1
  import * as _playcademy_timeback_types from '@playcademy/timeback/types';
2
2
  import { CourseConfig, OrganizationConfig, ComponentConfig, ResourceConfig, ComponentResourceConfig } from '@playcademy/timeback/types';
3
- import * as _playcademy_realtime_server_types from '@playcademy/realtime/server/types';
3
+ import { SchemaInfo } from '@playcademy/cloudflare';
4
4
  import { InferSelectModel } from 'drizzle-orm';
5
5
  import * as drizzle_orm_pg_core from 'drizzle-orm/pg-core';
6
6
  import * as drizzle_zod from 'drizzle-zod';
7
7
  import { z } from 'zod';
8
8
  import { AUTH_PROVIDER_IDS } from '@playcademy/constants';
9
- import { SchemaInfo } from '@playcademy/cloudflare';
10
9
 
11
- /**
12
- * Cache configuration types for runtime customization
13
- */
14
- /**
15
- * Runtime configuration for TTL cache behavior
16
- */
17
- interface TTLCacheConfig {
18
- /** Time-to-live in milliseconds. Set to 0 to disable caching for this call. */
19
- ttl?: number;
20
- /** Force refresh, bypassing cache */
21
- force?: boolean;
22
- /** Skip cache and fetch fresh data (alias for force) */
23
- skipCache?: boolean;
24
- }
25
- /**
26
- * Runtime configuration for cooldown cache behavior
27
- */
28
- interface CooldownCacheConfig {
29
- /** Cooldown period in milliseconds. Set to 0 to disable cooldown for this call. */
30
- cooldown?: number;
31
- /** Force refresh, bypassing cooldown */
32
- force?: boolean;
33
- }
10
+ declare const userRoleEnum: drizzle_orm_pg_core.PgEnum<["admin", "player", "developer"]>;
11
+ declare const users: drizzle_orm_pg_core.PgTableWithColumns<{
12
+ name: "user";
13
+ schema: undefined;
14
+ columns: {
15
+ id: drizzle_orm_pg_core.PgColumn<{
16
+ name: "id";
17
+ tableName: "user";
18
+ dataType: "string";
19
+ columnType: "PgText";
20
+ data: string;
21
+ driverParam: string;
22
+ notNull: true;
23
+ hasDefault: true;
24
+ isPrimaryKey: true;
25
+ isAutoincrement: false;
26
+ hasRuntimeDefault: true;
27
+ enumValues: [string, ...string[]];
28
+ baseColumn: never;
29
+ identity: undefined;
30
+ generated: undefined;
31
+ }, {}, {}>;
32
+ name: drizzle_orm_pg_core.PgColumn<{
33
+ name: "name";
34
+ tableName: "user";
35
+ dataType: "string";
36
+ columnType: "PgText";
37
+ data: string;
38
+ driverParam: string;
39
+ notNull: true;
40
+ hasDefault: false;
41
+ isPrimaryKey: false;
42
+ isAutoincrement: false;
43
+ hasRuntimeDefault: false;
44
+ enumValues: [string, ...string[]];
45
+ baseColumn: never;
46
+ identity: undefined;
47
+ generated: undefined;
48
+ }, {}, {}>;
49
+ username: drizzle_orm_pg_core.PgColumn<{
50
+ name: "username";
51
+ tableName: "user";
52
+ dataType: "string";
53
+ columnType: "PgText";
54
+ data: string;
55
+ driverParam: string;
56
+ notNull: false;
57
+ hasDefault: false;
58
+ isPrimaryKey: false;
59
+ isAutoincrement: false;
60
+ hasRuntimeDefault: false;
61
+ enumValues: [string, ...string[]];
62
+ baseColumn: never;
63
+ identity: undefined;
64
+ generated: undefined;
65
+ }, {}, {}>;
66
+ email: drizzle_orm_pg_core.PgColumn<{
67
+ name: "email";
68
+ tableName: "user";
69
+ dataType: "string";
70
+ columnType: "PgText";
71
+ data: string;
72
+ driverParam: string;
73
+ notNull: true;
74
+ hasDefault: false;
75
+ isPrimaryKey: false;
76
+ isAutoincrement: false;
77
+ hasRuntimeDefault: false;
78
+ enumValues: [string, ...string[]];
79
+ baseColumn: never;
80
+ identity: undefined;
81
+ generated: undefined;
82
+ }, {}, {}>;
83
+ timebackId: drizzle_orm_pg_core.PgColumn<{
84
+ name: "timeback_id";
85
+ tableName: "user";
86
+ dataType: "string";
87
+ columnType: "PgText";
88
+ data: string;
89
+ driverParam: string;
90
+ notNull: false;
91
+ hasDefault: false;
92
+ isPrimaryKey: false;
93
+ isAutoincrement: false;
94
+ hasRuntimeDefault: false;
95
+ enumValues: [string, ...string[]];
96
+ baseColumn: never;
97
+ identity: undefined;
98
+ generated: undefined;
99
+ }, {}, {}>;
100
+ emailVerified: drizzle_orm_pg_core.PgColumn<{
101
+ name: "email_verified";
102
+ tableName: "user";
103
+ dataType: "boolean";
104
+ columnType: "PgBoolean";
105
+ data: boolean;
106
+ driverParam: boolean;
107
+ notNull: true;
108
+ hasDefault: true;
109
+ isPrimaryKey: false;
110
+ isAutoincrement: false;
111
+ hasRuntimeDefault: false;
112
+ enumValues: undefined;
113
+ baseColumn: never;
114
+ identity: undefined;
115
+ generated: undefined;
116
+ }, {}, {}>;
117
+ image: drizzle_orm_pg_core.PgColumn<{
118
+ name: "image";
119
+ tableName: "user";
120
+ dataType: "string";
121
+ columnType: "PgText";
122
+ data: string;
123
+ driverParam: string;
124
+ notNull: false;
125
+ hasDefault: false;
126
+ isPrimaryKey: false;
127
+ isAutoincrement: false;
128
+ hasRuntimeDefault: false;
129
+ enumValues: [string, ...string[]];
130
+ baseColumn: never;
131
+ identity: undefined;
132
+ generated: undefined;
133
+ }, {}, {}>;
134
+ role: drizzle_orm_pg_core.PgColumn<{
135
+ name: "role";
136
+ tableName: "user";
137
+ dataType: "string";
138
+ columnType: "PgEnumColumn";
139
+ data: "admin" | "player" | "developer";
140
+ driverParam: string;
141
+ notNull: true;
142
+ hasDefault: true;
143
+ isPrimaryKey: false;
144
+ isAutoincrement: false;
145
+ hasRuntimeDefault: false;
146
+ enumValues: ["admin", "player", "developer"];
147
+ baseColumn: never;
148
+ identity: undefined;
149
+ generated: undefined;
150
+ }, {}, {}>;
151
+ developerStatus: drizzle_orm_pg_core.PgColumn<{
152
+ name: "developer_status";
153
+ tableName: "user";
154
+ dataType: "string";
155
+ columnType: "PgEnumColumn";
156
+ data: "none" | "pending" | "approved";
157
+ driverParam: string;
158
+ notNull: true;
159
+ hasDefault: true;
160
+ isPrimaryKey: false;
161
+ isAutoincrement: false;
162
+ hasRuntimeDefault: false;
163
+ enumValues: ["none", "pending", "approved"];
164
+ baseColumn: never;
165
+ identity: undefined;
166
+ generated: undefined;
167
+ }, {}, {}>;
168
+ characterCreated: drizzle_orm_pg_core.PgColumn<{
169
+ name: "character_created";
170
+ tableName: "user";
171
+ dataType: "boolean";
172
+ columnType: "PgBoolean";
173
+ data: boolean;
174
+ driverParam: boolean;
175
+ notNull: true;
176
+ hasDefault: true;
177
+ isPrimaryKey: false;
178
+ isAutoincrement: false;
179
+ hasRuntimeDefault: false;
180
+ enumValues: undefined;
181
+ baseColumn: never;
182
+ identity: undefined;
183
+ generated: undefined;
184
+ }, {}, {}>;
185
+ createdAt: drizzle_orm_pg_core.PgColumn<{
186
+ name: "created_at";
187
+ tableName: "user";
188
+ dataType: "date";
189
+ columnType: "PgTimestamp";
190
+ data: Date;
191
+ driverParam: string;
192
+ notNull: true;
193
+ hasDefault: false;
194
+ isPrimaryKey: false;
195
+ isAutoincrement: false;
196
+ hasRuntimeDefault: false;
197
+ enumValues: undefined;
198
+ baseColumn: never;
199
+ identity: undefined;
200
+ generated: undefined;
201
+ }, {}, {}>;
202
+ updatedAt: drizzle_orm_pg_core.PgColumn<{
203
+ name: "updated_at";
204
+ tableName: "user";
205
+ dataType: "date";
206
+ columnType: "PgTimestamp";
207
+ data: Date;
208
+ driverParam: string;
209
+ notNull: true;
210
+ hasDefault: false;
211
+ isPrimaryKey: false;
212
+ isAutoincrement: false;
213
+ hasRuntimeDefault: false;
214
+ enumValues: undefined;
215
+ baseColumn: never;
216
+ identity: undefined;
217
+ generated: undefined;
218
+ }, {}, {}>;
219
+ };
220
+ dialect: "pg";
221
+ }>;
34
222
 
223
+ type GameMetadata = {
224
+ description?: string;
225
+ emoji?: string;
226
+ [key: string]: unknown;
227
+ };
35
228
  /**
36
- * Base error class for Cademy SDK specific errors.
37
- */
38
- declare class PlaycademyError extends Error {
39
- constructor(message: string);
40
- }
41
- declare class ApiError extends Error {
42
- status: number;
43
- details: unknown;
44
- constructor(status: number, message: string, details: unknown);
45
- }
46
- /**
47
- * Structure of error response bodies returned by API endpoints
229
+ * DNS validation records for custom hostname
230
+ * Structure for the validationRecords JSON field in game_custom_hostnames table
48
231
  */
49
- interface ErrorResponseBody {
50
- error?: string;
51
- message?: string;
52
- }
53
- /**
54
- * Extract useful error information from an API error
55
- * Useful for displaying errors to users in a friendly way
56
- */
57
- interface ApiErrorInfo {
58
- status: number;
59
- statusText: string;
60
- error?: string;
61
- message?: string;
62
- details?: unknown;
63
- }
64
- declare function extractApiErrorInfo(error: unknown): ApiErrorInfo | null;
65
-
66
- /**
67
- * Connection monitoring types
68
- *
69
- * Type definitions for connection state, configuration, and callbacks.
70
- */
71
- /**
72
- * Possible connection states.
73
- *
74
- * - **online**: Connection is stable and healthy
75
- * - **offline**: Complete loss of network connectivity
76
- * - **degraded**: Connection is slow or experiencing intermittent issues
77
- */
78
- type ConnectionState = 'online' | 'offline' | 'degraded';
79
- /**
80
- * Configuration options for ConnectionMonitor.
81
- *
82
- * @see {@link ConnectionMonitor} for usage
83
- */
84
- interface ConnectionMonitorConfig {
85
- /** Base URL for heartbeat pings (e.g., 'https://api.playcademy.com') */
86
- baseUrl: string;
87
- /** How often to send heartbeat pings in milliseconds (default: 10000) */
88
- heartbeatInterval?: number;
89
- /** How long to wait for heartbeat response in milliseconds (default: 5000) */
90
- heartbeatTimeout?: number;
91
- /** Number of consecutive failures before triggering disconnect (default: 2) */
92
- failureThreshold?: number;
93
- /** Enable periodic heartbeat monitoring (default: true) */
94
- enableHeartbeat?: boolean;
95
- /** Enable browser online/offline event listeners (default: true) */
96
- enableOfflineEvents?: boolean;
97
- }
98
- /**
99
- * Callback function signature for connection state changes.
100
- *
101
- * @param state - The new connection state
102
- * @param reason - Human-readable reason for the state change
103
- */
104
- type ConnectionChangeCallback = (state: ConnectionState, reason: string) => void;
105
-
106
- /**
107
- * Connection Monitor
108
- *
109
- * Monitors network connectivity using multiple signals:
110
- * 1. navigator.onLine - Instant offline detection
111
- * 2. Periodic heartbeat - Detects slow/degraded connections
112
- * 3. Request failure tracking - Piggybacks on actual API calls
113
- *
114
- * Designed for school WiFi environments where connections may be
115
- * unstable or degraded without fully disconnecting.
116
- */
117
-
118
- /**
119
- * Monitors network connectivity using multiple signals and notifies callbacks of state changes.
120
- *
121
- * The ConnectionMonitor uses a multi-signal approach to detect connection issues:
122
- *
123
- * 1. **navigator.onLine events** - Instant detection of hard disconnects
124
- * 2. **Heartbeat pings** - Periodic checks to detect slow/degraded connections
125
- * 3. **Request failure tracking** - Piggybacks on actual API calls
126
- *
127
- * This comprehensive approach ensures reliable detection across different network
128
- * failure modes common in school WiFi environments (hard disconnect, slow connection,
129
- * intermittent failures).
130
- *
131
- * @example
132
- * ```typescript
133
- * const monitor = new ConnectionMonitor({
134
- * baseUrl: 'https://api.playcademy.com',
135
- * heartbeatInterval: 10000, // Check every 10s
136
- * failureThreshold: 2 // Trigger after 2 failures
137
- * })
138
- *
139
- * monitor.onChange((state, reason) => {
140
- * console.log(`Connection: ${state} - ${reason}`)
141
- * })
142
- *
143
- * monitor.start()
144
- * ```
145
- *
146
- * @see {@link ConnectionManagerConfig} for configuration options
147
- */
148
- declare class ConnectionMonitor {
149
- private state;
150
- private callbacks;
151
- private heartbeatInterval?;
152
- private consecutiveFailures;
153
- private isMonitoring;
154
- private config;
155
- /**
156
- * Creates a new ConnectionMonitor instance.
157
- *
158
- * The monitor starts in a stopped state. Call `start()` to begin monitoring.
159
- *
160
- * @param config - Configuration options
161
- * @param config.baseUrl - Base URL for heartbeat pings
162
- * @param config.heartbeatInterval - How often to check (default: 10000ms)
163
- * @param config.heartbeatTimeout - Request timeout (default: 5000ms)
164
- * @param config.failureThreshold - Failures before triggering disconnect (default: 2)
165
- * @param config.enableHeartbeat - Enable periodic checks (default: true)
166
- * @param config.enableOfflineEvents - Listen to browser events (default: true)
167
- */
168
- constructor(config: ConnectionMonitorConfig);
169
- /**
170
- * Starts monitoring the connection state.
171
- *
172
- * Sets up event listeners and begins heartbeat checks based on configuration.
173
- * Idempotent - safe to call multiple times.
174
- */
175
- start(): void;
176
- /**
177
- * Stops monitoring the connection state and cleans up resources.
178
- *
179
- * Removes event listeners and clears heartbeat intervals.
180
- * Idempotent - safe to call multiple times.
181
- */
182
- stop(): void;
183
- /**
184
- * Registers a callback to be notified of all connection state changes.
185
- *
186
- * The callback fires for all state transitions: online → offline,
187
- * offline → degraded, degraded → online, etc.
188
- *
189
- * @param callback - Function called with (state, reason) when connection changes
190
- * @returns Cleanup function to unregister the callback
191
- *
192
- * @example
193
- * ```typescript
194
- * const cleanup = monitor.onChange((state, reason) => {
195
- * console.log(`Connection: ${state}`)
196
- * if (state === 'offline') {
197
- * showReconnectingUI()
198
- * }
199
- * })
200
- *
201
- * // Later: cleanup() to unregister
202
- * ```
203
- */
204
- onChange(callback: ConnectionChangeCallback): () => void;
205
- /**
206
- * Gets the current connection state.
207
- *
208
- * @returns The current state ('online', 'offline', or 'degraded')
209
- */
210
- getState(): ConnectionState;
211
- /**
212
- * Manually triggers an immediate connection check.
213
- *
214
- * Forces a heartbeat ping to verify connectivity right now, bypassing
215
- * the normal interval. Useful before critical operations.
216
- *
217
- * @returns Promise resolving to the current connection state after the check
218
- *
219
- * @example
220
- * ```typescript
221
- * const state = await monitor.checkNow()
222
- * if (state !== 'online') {
223
- * alert('Please check your internet connection')
224
- * }
225
- * ```
226
- */
227
- checkNow(): Promise<ConnectionState>;
228
- /**
229
- * Reports a request failure for tracking.
230
- *
231
- * This should be called from your request wrapper whenever an API call fails.
232
- * Only network errors are tracked (TypeError, fetch failures) - HTTP error
233
- * responses (4xx, 5xx) are ignored.
234
- *
235
- * After consecutive failures exceed the threshold, the monitor transitions
236
- * to 'degraded' or 'offline' state.
237
- *
238
- * @param error - The error from the failed request
239
- *
240
- * @example
241
- * ```typescript
242
- * try {
243
- * await fetch('/api/data')
244
- * } catch (error) {
245
- * monitor.reportRequestFailure(error)
246
- * throw error
247
- * }
248
- * ```
249
- */
250
- reportRequestFailure(error: unknown): void;
251
- /**
252
- * Reports a successful request.
253
- *
254
- * This should be called from your request wrapper whenever an API call succeeds.
255
- * Resets the consecutive failure counter and transitions from 'degraded' to
256
- * 'online' if the connection has recovered.
257
- *
258
- * @example
259
- * ```typescript
260
- * try {
261
- * const result = await fetch('/api/data')
262
- * monitor.reportRequestSuccess()
263
- * return result
264
- * } catch (error) {
265
- * monitor.reportRequestFailure(error)
266
- * throw error
267
- * }
268
- * ```
269
- */
270
- reportRequestSuccess(): void;
271
- private _detectInitialState;
272
- private _handleOnline;
273
- private _handleOffline;
274
- private _startHeartbeat;
275
- private _performHeartbeat;
276
- private _handleHeartbeatFailure;
277
- private _setState;
278
- }
279
-
280
- /**
281
- * OAuth 2.0 implementation for the Playcademy SDK
282
- */
283
-
284
- /**
285
- * Parses an OAuth state parameter to extract CSRF token and any encoded data.
286
- *
287
- * @param state - The OAuth state parameter to parse
288
- * @returns Object containing CSRF token and optional decoded data
289
- */
290
- declare function parseOAuthState(state: string): {
291
- csrfToken: string;
292
- data?: Record<string, string>;
293
- };
294
-
295
- declare const userRoleEnum: drizzle_orm_pg_core.PgEnum<["admin", "player", "developer"]>;
296
- declare const users: drizzle_orm_pg_core.PgTableWithColumns<{
297
- name: "user";
298
- schema: undefined;
299
- columns: {
300
- id: drizzle_orm_pg_core.PgColumn<{
301
- name: "id";
302
- tableName: "user";
303
- dataType: "string";
304
- columnType: "PgText";
305
- data: string;
306
- driverParam: string;
307
- notNull: true;
308
- hasDefault: true;
309
- isPrimaryKey: true;
310
- isAutoincrement: false;
311
- hasRuntimeDefault: true;
312
- enumValues: [string, ...string[]];
313
- baseColumn: never;
314
- identity: undefined;
315
- generated: undefined;
316
- }, {}, {}>;
317
- name: drizzle_orm_pg_core.PgColumn<{
318
- name: "name";
319
- tableName: "user";
320
- dataType: "string";
321
- columnType: "PgText";
322
- data: string;
323
- driverParam: string;
324
- notNull: true;
325
- hasDefault: false;
326
- isPrimaryKey: false;
327
- isAutoincrement: false;
328
- hasRuntimeDefault: false;
329
- enumValues: [string, ...string[]];
330
- baseColumn: never;
331
- identity: undefined;
332
- generated: undefined;
333
- }, {}, {}>;
334
- username: drizzle_orm_pg_core.PgColumn<{
335
- name: "username";
336
- tableName: "user";
337
- dataType: "string";
338
- columnType: "PgText";
339
- data: string;
340
- driverParam: string;
341
- notNull: false;
342
- hasDefault: false;
343
- isPrimaryKey: false;
344
- isAutoincrement: false;
345
- hasRuntimeDefault: false;
346
- enumValues: [string, ...string[]];
347
- baseColumn: never;
348
- identity: undefined;
349
- generated: undefined;
350
- }, {}, {}>;
351
- email: drizzle_orm_pg_core.PgColumn<{
352
- name: "email";
353
- tableName: "user";
354
- dataType: "string";
355
- columnType: "PgText";
356
- data: string;
357
- driverParam: string;
358
- notNull: true;
359
- hasDefault: false;
360
- isPrimaryKey: false;
361
- isAutoincrement: false;
362
- hasRuntimeDefault: false;
363
- enumValues: [string, ...string[]];
364
- baseColumn: never;
365
- identity: undefined;
366
- generated: undefined;
367
- }, {}, {}>;
368
- timebackId: drizzle_orm_pg_core.PgColumn<{
369
- name: "timeback_id";
370
- tableName: "user";
371
- dataType: "string";
372
- columnType: "PgText";
373
- data: string;
374
- driverParam: string;
375
- notNull: false;
376
- hasDefault: false;
377
- isPrimaryKey: false;
378
- isAutoincrement: false;
379
- hasRuntimeDefault: false;
380
- enumValues: [string, ...string[]];
381
- baseColumn: never;
382
- identity: undefined;
383
- generated: undefined;
384
- }, {}, {}>;
385
- emailVerified: drizzle_orm_pg_core.PgColumn<{
386
- name: "email_verified";
387
- tableName: "user";
388
- dataType: "boolean";
389
- columnType: "PgBoolean";
390
- data: boolean;
391
- driverParam: boolean;
392
- notNull: true;
393
- hasDefault: true;
394
- isPrimaryKey: false;
395
- isAutoincrement: false;
396
- hasRuntimeDefault: false;
397
- enumValues: undefined;
398
- baseColumn: never;
399
- identity: undefined;
400
- generated: undefined;
401
- }, {}, {}>;
402
- image: drizzle_orm_pg_core.PgColumn<{
403
- name: "image";
404
- tableName: "user";
405
- dataType: "string";
406
- columnType: "PgText";
407
- data: string;
408
- driverParam: string;
409
- notNull: false;
410
- hasDefault: false;
411
- isPrimaryKey: false;
412
- isAutoincrement: false;
413
- hasRuntimeDefault: false;
414
- enumValues: [string, ...string[]];
415
- baseColumn: never;
416
- identity: undefined;
417
- generated: undefined;
418
- }, {}, {}>;
419
- role: drizzle_orm_pg_core.PgColumn<{
420
- name: "role";
421
- tableName: "user";
422
- dataType: "string";
423
- columnType: "PgEnumColumn";
424
- data: "admin" | "player" | "developer";
425
- driverParam: string;
426
- notNull: true;
427
- hasDefault: true;
428
- isPrimaryKey: false;
429
- isAutoincrement: false;
430
- hasRuntimeDefault: false;
431
- enumValues: ["admin", "player", "developer"];
432
- baseColumn: never;
433
- identity: undefined;
434
- generated: undefined;
435
- }, {}, {}>;
436
- developerStatus: drizzle_orm_pg_core.PgColumn<{
437
- name: "developer_status";
438
- tableName: "user";
439
- dataType: "string";
440
- columnType: "PgEnumColumn";
441
- data: "none" | "pending" | "approved";
442
- driverParam: string;
443
- notNull: true;
444
- hasDefault: true;
445
- isPrimaryKey: false;
446
- isAutoincrement: false;
447
- hasRuntimeDefault: false;
448
- enumValues: ["none", "pending", "approved"];
449
- baseColumn: never;
450
- identity: undefined;
451
- generated: undefined;
452
- }, {}, {}>;
453
- characterCreated: drizzle_orm_pg_core.PgColumn<{
454
- name: "character_created";
455
- tableName: "user";
456
- dataType: "boolean";
457
- columnType: "PgBoolean";
458
- data: boolean;
459
- driverParam: boolean;
460
- notNull: true;
461
- hasDefault: true;
462
- isPrimaryKey: false;
463
- isAutoincrement: false;
464
- hasRuntimeDefault: false;
465
- enumValues: undefined;
466
- baseColumn: never;
467
- identity: undefined;
468
- generated: undefined;
469
- }, {}, {}>;
470
- createdAt: drizzle_orm_pg_core.PgColumn<{
471
- name: "created_at";
472
- tableName: "user";
473
- dataType: "date";
474
- columnType: "PgTimestamp";
475
- data: Date;
476
- driverParam: string;
477
- notNull: true;
478
- hasDefault: false;
479
- isPrimaryKey: false;
480
- isAutoincrement: false;
481
- hasRuntimeDefault: false;
482
- enumValues: undefined;
483
- baseColumn: never;
484
- identity: undefined;
485
- generated: undefined;
486
- }, {}, {}>;
487
- updatedAt: drizzle_orm_pg_core.PgColumn<{
488
- name: "updated_at";
489
- tableName: "user";
490
- dataType: "date";
491
- columnType: "PgTimestamp";
492
- data: Date;
493
- driverParam: string;
494
- notNull: true;
495
- hasDefault: false;
496
- isPrimaryKey: false;
497
- isAutoincrement: false;
498
- hasRuntimeDefault: false;
499
- enumValues: undefined;
500
- baseColumn: never;
501
- identity: undefined;
502
- generated: undefined;
503
- }, {}, {}>;
504
- };
505
- dialect: "pg";
506
- }>;
507
-
508
- type GameMetadata = {
509
- description?: string;
510
- emoji?: string;
511
- [key: string]: unknown;
512
- };
513
- /**
514
- * DNS validation records for custom hostname
515
- * Structure for the validationRecords JSON field in game_custom_hostnames table
516
- */
517
- type CustomHostnameValidationRecords = {
518
- /** TXT record for ownership verification */
519
- ownership?: {
520
- name?: string;
521
- value?: string;
522
- type?: string;
523
- };
524
- /** TXT records for SSL certificate validation */
525
- ssl?: Array<{
526
- txt_name?: string;
527
- txt_value?: string;
528
- }>;
232
+ type CustomHostnameValidationRecords = {
233
+ /** TXT record for ownership verification */
234
+ ownership?: {
235
+ name?: string;
236
+ value?: string;
237
+ type?: string;
238
+ };
239
+ /** TXT records for SSL certificate validation */
240
+ ssl?: Array<{
241
+ txt_name?: string;
242
+ txt_value?: string;
243
+ }>;
529
244
  };
530
245
  declare const games: drizzle_orm_pg_core.PgTableWithColumns<{
531
246
  name: "games";
@@ -3974,29 +3689,82 @@ type UserRoleEnumType = (typeof userRoleEnum.enumValues)[number];
3974
3689
  type DeveloperStatusResponse = z.infer<typeof DeveloperStatusResponseSchema>;
3975
3690
  type DeveloperStatusValue = DeveloperStatusResponse['status'];
3976
3691
  /**
3977
- * User data with authentication provider information.
3978
- * Returned by the /users/me endpoint with additional auth context.
3692
+ * TimeBack enrollment information for a game.
3979
3693
  */
3980
- type AuthenticatedUser = User & {
3981
- /** Whether the user authenticated via Timeback SSO */
3982
- hasTimebackAccount: boolean;
3694
+ type UserEnrollment = {
3695
+ gameId?: string;
3696
+ courseId: string;
3697
+ grade: number;
3698
+ subject: string;
3699
+ orgId?: string;
3983
3700
  };
3984
-
3985
3701
  /**
3986
- * Cross-Domain Composite Types
3987
- * Types that combine data from multiple domains
3702
+ * TimeBack user role (matches OneRoster spec).
3988
3703
  */
3989
- type ManifestV1 = z.infer<typeof ManifestV1Schema>;
3704
+ type TimebackUserRole = 'administrator' | 'aide' | 'guardian' | 'parent' | 'proctor' | 'relative' | 'student' | 'teacher';
3990
3705
  /**
3991
- * Basic user information in the shape of the claims from identity providers
3706
+ * Organization type (matches OneRoster spec).
3992
3707
  */
3993
- interface UserInfo {
3994
- /** Unique user identifier (sub claim from JWT) */
3995
- sub: string;
3996
- /** User's email address */
3997
- email: string;
3998
- /** User's display name */
3999
- name: string;
3708
+ type TimebackOrgType = 'department' | 'school' | 'district' | 'local' | 'state' | 'national';
3709
+ /**
3710
+ * TimeBack organization data for a user.
3711
+ * Represents schools, districts, or other educational organizations.
3712
+ */
3713
+ type UserOrganization = {
3714
+ /** Organization ID (OneRoster sourcedId) */
3715
+ id: string;
3716
+ /** Organization name */
3717
+ name: string | null;
3718
+ /** Organization type (school, district, etc.) */
3719
+ type: TimebackOrgType | string;
3720
+ /** Whether this is the user's primary organization */
3721
+ isPrimary: boolean;
3722
+ };
3723
+ /**
3724
+ * TimeBack student profile (role + organizations).
3725
+ * Subset of UserTimebackData returned by OneRoster API.
3726
+ */
3727
+ type TimebackStudentProfile = {
3728
+ /** User's primary role in TimeBack (student, parent, teacher, etc.) */
3729
+ role: TimebackUserRole;
3730
+ /** User's organizations (schools/districts) */
3731
+ organizations: UserOrganization[];
3732
+ };
3733
+ /**
3734
+ * TimeBack-related data for a user.
3735
+ */
3736
+ type UserTimebackData = TimebackStudentProfile & {
3737
+ /** User's TimeBack ID (sourcedId) */
3738
+ id: string;
3739
+ /** Course enrollments */
3740
+ enrollments: UserEnrollment[];
3741
+ };
3742
+ /**
3743
+ * User data with authentication provider information.
3744
+ * Returned by the /users/me endpoint with additional auth context.
3745
+ */
3746
+ type AuthenticatedUser = Omit<User, 'timebackId'> & {
3747
+ /** Whether the user authenticated via Timeback SSO */
3748
+ hasTimebackAccount: boolean;
3749
+ /** TimeBack data (id, role, enrollments, organizations) - only present if user has a timeback account */
3750
+ timeback?: UserTimebackData;
3751
+ };
3752
+
3753
+ /**
3754
+ * Cross-Domain Composite Types
3755
+ * Types that combine data from multiple domains
3756
+ */
3757
+ type ManifestV1 = z.infer<typeof ManifestV1Schema>;
3758
+ /**
3759
+ * Basic user information in the shape of the claims from identity providers
3760
+ */
3761
+ interface UserInfo {
3762
+ /** Unique user identifier (sub claim from JWT) */
3763
+ sub: string;
3764
+ /** User's email address */
3765
+ email: string;
3766
+ /** User's display name */
3767
+ name: string;
4000
3768
  /** Whether the email has been verified */
4001
3769
  email_verified: boolean;
4002
3770
  /** Optional given name (first name) */
@@ -4386,1819 +4154,2178 @@ type EndActivityResponse = {
4386
4154
  inProgress?: string;
4387
4155
  };
4388
4156
 
4157
+ /** Permitted HTTP verbs */
4158
+ type Method = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
4159
+
4389
4160
  /**
4390
- * Auto-initializes a PlaycademyClient with context from the environment.
4391
- * Works in both iframe mode (production/development) and standalone mode (local dev).
4161
+ * Base error class for Cademy SDK specific errors.
4162
+ */
4163
+ declare class PlaycademyError extends Error {
4164
+ constructor(message: string);
4165
+ }
4166
+ declare class ApiError extends Error {
4167
+ status: number;
4168
+ details: unknown;
4169
+ constructor(status: number, message: string, details: unknown);
4170
+ }
4171
+ /**
4172
+ * Structure of error response bodies returned by API endpoints
4173
+ */
4174
+ interface ErrorResponseBody {
4175
+ error?: string;
4176
+ message?: string;
4177
+ }
4178
+ /**
4179
+ * Extract useful error information from an API error
4180
+ * Useful for displaying errors to users in a friendly way
4181
+ */
4182
+ interface ApiErrorInfo {
4183
+ status: number;
4184
+ statusText: string;
4185
+ error?: string;
4186
+ message?: string;
4187
+ details?: unknown;
4188
+ }
4189
+ declare function extractApiErrorInfo(error: unknown): ApiErrorInfo | null;
4190
+
4191
+ /**
4192
+ * Connection monitoring types
4392
4193
  *
4393
- * This is the recommended way to initialize the SDK as it automatically:
4394
- * - Detects the runtime environment (iframe vs standalone)
4395
- * - Configures the client with the appropriate context
4396
- * - Sets up event listeners for token refresh
4397
- * - Exposes the client for debugging in development mode
4194
+ * Type definitions for connection state, configuration, and callbacks.
4195
+ */
4196
+ /**
4197
+ * Possible connection states.
4398
4198
  *
4399
- * @param options - Optional configuration overrides
4400
- * @param options.baseUrl - Override the base URL for API requests
4401
- * @returns Promise resolving to a fully initialized PlaycademyClient
4402
- * @throws Error if not running in a browser context
4199
+ * - **online**: Connection is stable and healthy
4200
+ * - **offline**: Complete loss of network connectivity
4201
+ * - **degraded**: Connection is slow or experiencing intermittent issues
4202
+ */
4203
+ type ConnectionState = 'online' | 'offline' | 'degraded';
4204
+ /**
4205
+ * Configuration options for ConnectionMonitor.
4403
4206
  *
4404
- * @example
4405
- * ```typescript
4406
- * // Default initialization
4407
- * const client = await PlaycademyClient.init()
4207
+ * @see {@link ConnectionMonitor} for usage
4208
+ */
4209
+ interface ConnectionMonitorConfig {
4210
+ /** Base URL for heartbeat pings (e.g., 'https://api.playcademy.com') */
4211
+ baseUrl: string;
4212
+ /** How often to send heartbeat pings in milliseconds (default: 10000) */
4213
+ heartbeatInterval?: number;
4214
+ /** How long to wait for heartbeat response in milliseconds (default: 5000) */
4215
+ heartbeatTimeout?: number;
4216
+ /** Number of consecutive failures before triggering disconnect (default: 2) */
4217
+ failureThreshold?: number;
4218
+ /** Enable periodic heartbeat monitoring (default: true) */
4219
+ enableHeartbeat?: boolean;
4220
+ /** Enable browser online/offline event listeners (default: true) */
4221
+ enableOfflineEvents?: boolean;
4222
+ }
4223
+ /**
4224
+ * Callback function signature for connection state changes.
4408
4225
  *
4409
- * // With custom base URL
4410
- * const client = await PlaycademyClient.init({ baseUrl: 'https://custom.api.com' })
4411
- * ```
4226
+ * @param state - The new connection state
4227
+ * @param reason - Human-readable reason for the state change
4412
4228
  */
4413
- declare function init<T extends PlaycademyClient = PlaycademyClient>(this: new (...args: ConstructorParameters<typeof PlaycademyClient>) => T, options?: {
4414
- baseUrl?: string;
4415
- allowedParentOrigins?: string[];
4416
- onDisconnect?: DisconnectHandler;
4417
- enableConnectionMonitoring?: boolean;
4418
- }): Promise<T>;
4229
+ type ConnectionChangeCallback = (state: ConnectionState, reason: string) => void;
4419
4230
 
4420
4231
  /**
4421
- * Authenticates a user with email and password.
4232
+ * Connection Monitor
4422
4233
  *
4423
- * This is a standalone authentication method that doesn't require an initialized client.
4424
- * Use this for login flows before creating a client instance.
4234
+ * Monitors network connectivity using multiple signals:
4235
+ * 1. navigator.onLine - Instant offline detection
4236
+ * 2. Periodic heartbeat - Detects slow/degraded connections
4237
+ * 3. Request failure tracking - Piggybacks on actual API calls
4425
4238
  *
4426
- * @deprecated Use client.auth.login() instead for better error handling and automatic token management
4239
+ * Designed for school WiFi environments where connections may be
4240
+ * unstable or degraded without fully disconnecting.
4241
+ */
4242
+
4243
+ /**
4244
+ * Monitors network connectivity using multiple signals and notifies callbacks of state changes.
4427
4245
  *
4428
- * @param baseUrl - The base URL of the Playcademy API
4429
- * @param email - User's email address
4430
- * @param password - User's password
4431
- * @returns Promise resolving to authentication response with token
4432
- * @throws PlaycademyError if authentication fails or network error occurs
4246
+ * The ConnectionMonitor uses a multi-signal approach to detect connection issues:
4247
+ *
4248
+ * 1. **navigator.onLine events** - Instant detection of hard disconnects
4249
+ * 2. **Heartbeat pings** - Periodic checks to detect slow/degraded connections
4250
+ * 3. **Request failure tracking** - Piggybacks on actual API calls
4251
+ *
4252
+ * This comprehensive approach ensures reliable detection across different network
4253
+ * failure modes common in school WiFi environments (hard disconnect, slow connection,
4254
+ * intermittent failures).
4433
4255
  *
4434
4256
  * @example
4435
4257
  * ```typescript
4436
- * // Preferred approach:
4437
- * const client = new PlaycademyClient({ baseUrl: '/api' })
4438
- * const result = await client.auth.login({
4439
- * email: 'user@example.com',
4440
- * password: 'password'
4258
+ * const monitor = new ConnectionMonitor({
4259
+ * baseUrl: 'https://api.playcademy.com',
4260
+ * heartbeatInterval: 10000, // Check every 10s
4261
+ * failureThreshold: 2 // Trigger after 2 failures
4441
4262
  * })
4442
4263
  *
4443
- * // Legacy approach (still works):
4444
- * try {
4445
- * const response = await PlaycademyClient.login('/api', 'user@example.com', 'password')
4446
- * const client = new PlaycademyClient({ token: response.token })
4447
- * } catch (error) {
4448
- * console.error('Login failed:', error.message)
4449
- * }
4264
+ * monitor.onChange((state, reason) => {
4265
+ * console.log(`Connection: ${state} - ${reason}`)
4266
+ * })
4267
+ *
4268
+ * monitor.start()
4450
4269
  * ```
4270
+ *
4271
+ * @see {@link ConnectionManagerConfig} for configuration options
4451
4272
  */
4452
- declare function login(baseUrl: string, email: string, password: string): Promise<LoginResponse>;
4453
-
4454
- /** Permitted HTTP verbs */
4455
- type Method = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
4456
-
4457
- /**
4458
- * Main Playcademy SDK client for interacting with the platform API.
4459
- * Provides namespaced access to all platform features including games, users, inventory, and more.
4460
- */
4461
- declare class PlaycademyClient {
4462
- baseUrl: string;
4463
- gameUrl?: string;
4464
- private authStrategy;
4465
- private gameId?;
4273
+ declare class ConnectionMonitor {
4274
+ private state;
4275
+ private callbacks;
4276
+ private heartbeatInterval?;
4277
+ private consecutiveFailures;
4278
+ private isMonitoring;
4466
4279
  private config;
4467
- private listeners;
4468
- private internalClientSessionId?;
4469
- private authContext?;
4470
- private initPayload?;
4471
- private connectionManager?;
4472
- /**
4473
- * Internal session manager for automatic session lifecycle.
4474
- *
4475
- * This manager handles starting and ending game sessions automatically.
4476
- * Game developers don't need to call these methods directly - they're managed
4477
- * by the SDK during initialization and cleanup.
4478
- *
4479
- * @private
4480
- * @internal
4481
- */
4482
- private _sessionManager;
4483
- /**
4484
- * Creates a new PlaycademyClient instance.
4485
- *
4486
- * @param config - Optional configuration object
4487
- * @param config.baseUrl - Base URL (e.g., 'https://hub.playcademy.net' or '/'). SDK automatically appends /api
4488
- * @param config.token - Authentication token
4489
- * @param config.tokenType - Optional token type (auto-detected if not provided)
4490
- * @param config.gameId - Game ID for automatic session management
4491
- * @param config.autoStartSession - Automatically start a game session?
4492
- */
4493
- constructor(config?: Partial<ClientConfig>);
4494
4280
  /**
4495
- * Gets the effective base URL for API requests.
4496
- * Converts relative URLs to absolute URLs in browser environments.
4497
- * Note: baseUrl already includes /api suffix from constructor.
4281
+ * Creates a new ConnectionMonitor instance.
4498
4282
  *
4499
- * @returns The complete base URL for API requests (with /api suffix)
4500
- */
4501
- getBaseUrl(): string;
4502
- /**
4503
- * Gets the effective game backend URL for integration requests.
4504
- * Throws if gameUrl is not configured.
4283
+ * The monitor starts in a stopped state. Call `start()` to begin monitoring.
4505
4284
  *
4506
- * @returns The complete game backend URL for API requests (with /api suffix)
4507
- * @throws PlaycademyError if gameUrl is not set
4285
+ * @param config - Configuration options
4286
+ * @param config.baseUrl - Base URL for heartbeat pings
4287
+ * @param config.heartbeatInterval - How often to check (default: 10000ms)
4288
+ * @param config.heartbeatTimeout - Request timeout (default: 5000ms)
4289
+ * @param config.failureThreshold - Failures before triggering disconnect (default: 2)
4290
+ * @param config.enableHeartbeat - Enable periodic checks (default: true)
4291
+ * @param config.enableOfflineEvents - Listen to browser events (default: true)
4508
4292
  */
4509
- private getGameBackendUrl;
4293
+ constructor(config: ConnectionMonitorConfig);
4510
4294
  /**
4511
- * Simple ping method for testing connectivity.
4295
+ * Starts monitoring the connection state.
4512
4296
  *
4513
- * @returns 'pong' string response
4297
+ * Sets up event listeners and begins heartbeat checks based on configuration.
4298
+ * Idempotent - safe to call multiple times.
4514
4299
  */
4515
- ping(): string;
4300
+ start(): void;
4516
4301
  /**
4517
- * Sets the authentication token for API requests.
4518
- * Emits an 'authChange' event when the token changes.
4302
+ * Stops monitoring the connection state and cleans up resources.
4519
4303
  *
4520
- * @param token - The authentication token, or null to clear
4521
- * @param tokenType - Optional token type (auto-detected if not provided)
4304
+ * Removes event listeners and clears heartbeat intervals.
4305
+ * Idempotent - safe to call multiple times.
4522
4306
  */
4523
- setToken(token: string | null, tokenType?: TokenType): void;
4307
+ stop(): void;
4524
4308
  /**
4525
- * Gets the current token type.
4309
+ * Registers a callback to be notified of all connection state changes.
4526
4310
  *
4527
- * @returns The token type
4528
- */
4529
- getTokenType(): TokenType;
4530
- /**
4531
- * Gets the current authentication token.
4311
+ * The callback fires for all state transitions: online → offline,
4312
+ * offline → degraded, degraded → online, etc.
4532
4313
  *
4533
- * @returns The current token or null if not authenticated
4314
+ * @param callback - Function called with (state, reason) when connection changes
4315
+ * @returns Cleanup function to unregister the callback
4534
4316
  *
4535
4317
  * @example
4536
4318
  * ```typescript
4537
- * // Send token to your backend for verification
4538
- * const token = client.getToken()
4539
- * const response = await fetch('/api/auth/playcademy', {
4540
- * method: 'POST',
4541
- * body: JSON.stringify({ gameToken: token })
4319
+ * const cleanup = monitor.onChange((state, reason) => {
4320
+ * console.log(`Connection: ${state}`)
4321
+ * if (state === 'offline') {
4322
+ * showReconnectingUI()
4323
+ * }
4542
4324
  * })
4325
+ *
4326
+ * // Later: cleanup() to unregister
4543
4327
  * ```
4544
4328
  */
4545
- getToken(): string | null;
4329
+ onChange(callback: ConnectionChangeCallback): () => void;
4546
4330
  /**
4547
- * Checks if the client has a valid API token for making Playcademy API requests.
4548
- *
4549
- * For games (iframe context): Checks if we have a valid token from the parent.
4550
- * For Cademy (standalone): Checks if we have a token from better-auth.
4551
- *
4552
- * Note: This checks for API authentication, not whether a user has linked
4553
- * their identity via OAuth.
4331
+ * Gets the current connection state.
4554
4332
  *
4555
- * @returns true if API token exists, false otherwise
4333
+ * @returns The current state ('online', 'offline', or 'degraded')
4334
+ */
4335
+ getState(): ConnectionState;
4336
+ /**
4337
+ * Manually triggers an immediate connection check.
4338
+ *
4339
+ * Forces a heartbeat ping to verify connectivity right now, bypassing
4340
+ * the normal interval. Useful before critical operations.
4341
+ *
4342
+ * @returns Promise resolving to the current connection state after the check
4556
4343
  *
4557
4344
  * @example
4558
4345
  * ```typescript
4559
- * if (client.isAuthenticated()) {
4560
- * // Can make API calls
4561
- * const games = await client.games.list()
4562
- * } else {
4563
- * console.error('No API token available')
4346
+ * const state = await monitor.checkNow()
4347
+ * if (state !== 'online') {
4348
+ * alert('Please check your internet connection')
4564
4349
  * }
4565
4350
  * ```
4566
4351
  */
4567
- isAuthenticated(): boolean;
4352
+ checkNow(): Promise<ConnectionState>;
4568
4353
  /**
4569
- * Registers a callback to be called when authentication state changes.
4354
+ * Reports a request failure for tracking.
4355
+ *
4356
+ * This should be called from your request wrapper whenever an API call fails.
4357
+ * Only network errors are tracked (TypeError, fetch failures) - HTTP error
4358
+ * responses (4xx, 5xx) are ignored.
4359
+ *
4360
+ * After consecutive failures exceed the threshold, the monitor transitions
4361
+ * to 'degraded' or 'offline' state.
4570
4362
  *
4571
- * @param callback - Function to call when auth state changes
4363
+ * @param error - The error from the failed request
4364
+ *
4365
+ * @example
4366
+ * ```typescript
4367
+ * try {
4368
+ * await fetch('/api/data')
4369
+ * } catch (error) {
4370
+ * monitor.reportRequestFailure(error)
4371
+ * throw error
4372
+ * }
4373
+ * ```
4572
4374
  */
4573
- onAuthChange(callback: (token: string | null) => void): void;
4375
+ reportRequestFailure(error: unknown): void;
4574
4376
  /**
4575
- * Registers a callback to be called when connection issues are detected.
4377
+ * Reports a successful request.
4576
4378
  *
4577
- * This is a convenience method that filters connection change events to only
4578
- * fire when the connection degrades (offline or degraded states). Use this
4579
- * when you want to handle disconnects without being notified of recoveries.
4379
+ * This should be called from your request wrapper whenever an API call succeeds.
4380
+ * Resets the consecutive failure counter and transitions from 'degraded' to
4381
+ * 'online' if the connection has recovered.
4580
4382
  *
4581
- * For all connection state changes, use `client.on('connectionChange', ...)` instead.
4383
+ * @example
4384
+ * ```typescript
4385
+ * try {
4386
+ * const result = await fetch('/api/data')
4387
+ * monitor.reportRequestSuccess()
4388
+ * return result
4389
+ * } catch (error) {
4390
+ * monitor.reportRequestFailure(error)
4391
+ * throw error
4392
+ * }
4393
+ * ```
4394
+ */
4395
+ reportRequestSuccess(): void;
4396
+ private _detectInitialState;
4397
+ private _handleOnline;
4398
+ private _handleOffline;
4399
+ private _startHeartbeat;
4400
+ private _performHeartbeat;
4401
+ private _handleHeartbeatFailure;
4402
+ private _setState;
4403
+ }
4404
+
4405
+ /**
4406
+ * Connection Manager
4407
+ *
4408
+ * Manages connection monitoring and integrates it with the Playcademy client.
4409
+ * Handles event wiring, state management, and disconnect callbacks.
4410
+ *
4411
+ * In iframe mode, disables local monitoring and listens to platform connection
4412
+ * state broadcasts instead (avoids duplicate heartbeats).
4413
+ */
4414
+
4415
+ /**
4416
+ * Configuration for the ConnectionManager.
4417
+ */
4418
+ interface ConnectionManagerConfig {
4419
+ /** Base URL for API requests (used for heartbeat pings) */
4420
+ baseUrl: string;
4421
+ /** Authentication context (iframe vs standalone) for alert routing */
4422
+ authContext?: {
4423
+ isInIframe: boolean;
4424
+ };
4425
+ /** Handler to call when connection issues are detected */
4426
+ onDisconnect?: DisconnectHandler;
4427
+ /** Callback to emit connection change events to the client */
4428
+ onConnectionChange?: (state: ConnectionState, reason: string) => void;
4429
+ }
4430
+ /**
4431
+ * Manages connection monitoring for the Playcademy client.
4432
+ *
4433
+ * The ConnectionManager serves as an integration layer between the low-level
4434
+ * ConnectionMonitor and the PlaycademyClient. It handles:
4435
+ * - Event wiring and coordination
4436
+ * - Disconnect callbacks with context
4437
+ * - Platform-level alert integration
4438
+ * - Request success/failure tracking
4439
+ *
4440
+ * This class is used internally by PlaycademyClient and typically not
4441
+ * instantiated directly by game developers.
4442
+ *
4443
+ * @see {@link ConnectionMonitor} for the underlying monitoring implementation
4444
+ * @see {@link PlaycademyClient.onDisconnect} for the public API
4445
+ */
4446
+ declare class ConnectionManager {
4447
+ private monitor?;
4448
+ private authContext?;
4449
+ private disconnectHandler?;
4450
+ private connectionChangeCallback?;
4451
+ private currentState;
4452
+ private additionalDisconnectHandlers;
4453
+ /**
4454
+ * Creates a new ConnectionManager instance.
4582
4455
  *
4583
- * @param callback - Function to call when connection state changes to offline or degraded
4584
- * @returns Cleanup function to unregister the callback
4456
+ * @param config - Configuration options for the manager
4585
4457
  *
4586
4458
  * @example
4587
4459
  * ```typescript
4588
- * const cleanup = client.onDisconnect(({ state, reason, displayAlert }) => {
4589
- * console.log(`Connection ${state}: ${reason}`)
4590
- *
4591
- * if (state === 'offline') {
4592
- * // Save state and return to game lobby
4593
- * displayAlert?.('Connection lost. Your progress has been saved.', { type: 'error' })
4594
- * saveGameState()
4595
- * returnToLobby()
4596
- * } else if (state === 'degraded') {
4597
- * displayAlert?.('Slow connection detected.', { type: 'warning' })
4460
+ * const manager = new ConnectionManager({
4461
+ * baseUrl: 'https://api.playcademy.com',
4462
+ * authContext: { isInIframe: false },
4463
+ * onDisconnect: (context) => {
4464
+ * console.log(`Disconnected: ${context.state}`)
4465
+ * },
4466
+ * onConnectionChange: (state, reason) => {
4467
+ * console.log(`Connection changed: ${state}`)
4598
4468
  * }
4599
4469
  * })
4600
- *
4601
- * // Later: cleanup() to unregister
4602
4470
  * ```
4603
- *
4604
- * @see Connection monitoring documentation in SDK Browser docs for detailed usage examples
4605
- * @see {@link ConnectionManager.onDisconnect} for the underlying implementation
4606
4471
  */
4607
- onDisconnect(callback: (context: DisconnectContext) => void | Promise<void>): () => void;
4472
+ constructor(config: ConnectionManagerConfig);
4608
4473
  /**
4609
4474
  * Gets the current connection state.
4610
4475
  *
4611
- * Returns the last known connection state without triggering a new check.
4612
- * Use `checkConnection()` to force an immediate verification.
4613
- *
4614
- * @returns Current connection state ('online', 'offline', 'degraded') or 'unknown' if monitoring is disabled
4476
+ * @returns The current connection state ('online', 'offline', or 'degraded')
4615
4477
  *
4616
4478
  * @example
4617
4479
  * ```typescript
4618
- * const state = client.getConnectionState()
4480
+ * const state = manager.getState()
4619
4481
  * if (state === 'offline') {
4620
- * console.log('No connection available')
4482
+ * console.log('No connection')
4621
4483
  * }
4622
4484
  * ```
4623
- *
4624
- * @see {@link checkConnection} to trigger an immediate connection check
4625
- * @see {@link ConnectionManager.getState} for the underlying implementation
4626
4485
  */
4627
- getConnectionState(): ConnectionState | 'unknown';
4486
+ getState(): ConnectionState;
4628
4487
  /**
4629
4488
  * Manually triggers a connection check immediately.
4630
4489
  *
4631
- * Forces a heartbeat ping to verify connectivity right now, bypassing the normal
4632
- * interval. Useful when you need to verify connection status before a critical
4633
- * operation (e.g., saving important game state).
4490
+ * Forces a heartbeat ping to verify the current connection status.
4491
+ * Useful when you need to check connectivity before a critical operation.
4492
+ *
4493
+ * In iframe mode, this returns the last known state from platform.
4634
4494
  *
4635
- * @returns Promise resolving to the current connection state after verification
4495
+ * @returns Promise resolving to the current connection state
4636
4496
  *
4637
4497
  * @example
4638
4498
  * ```typescript
4639
- * // Check before critical operation
4640
- * const state = await client.checkConnection()
4641
- * if (state !== 'online') {
4642
- * alert('Please check your internet connection before saving')
4643
- * return
4499
+ * const state = await manager.checkNow()
4500
+ * if (state === 'online') {
4501
+ * await performCriticalOperation()
4644
4502
  * }
4645
- *
4646
- * await client.games.saveState(importantData)
4647
4503
  * ```
4648
- *
4649
- * @see {@link getConnectionState} to get the last known state without checking
4650
- * @see {@link ConnectionManager.checkNow} for the underlying implementation
4651
- */
4652
- checkConnection(): Promise<ConnectionState | 'unknown'>;
4653
- /**
4654
- * Sets the authentication context for the client.
4655
- * This is called during initialization to store environment info.
4656
- * @internal
4657
4504
  */
4658
- _setAuthContext(context: {
4659
- isInIframe: boolean;
4660
- }): void;
4505
+ checkNow(): Promise<ConnectionState>;
4661
4506
  /**
4662
- * Registers an event listener for client events.
4507
+ * Reports a successful API request to the connection monitor.
4663
4508
  *
4664
- * @param event - The event type to listen for
4665
- * @param callback - Function to call when the event is emitted
4666
- */
4667
- on<E extends keyof ClientEvents>(event: E, callback: (payload: ClientEvents[E]) => void): void;
4668
- /**
4669
- * Emits an event to all registered listeners.
4509
+ * This resets the consecutive failure counter and transitions from
4510
+ * 'degraded' to 'online' state if applicable.
4670
4511
  *
4671
- * @param event - The event type to emit
4672
- * @param payload - The event payload
4512
+ * Typically called automatically by the SDK's request wrapper.
4513
+ * No-op in iframe mode (platform handles monitoring).
4673
4514
  */
4674
- private emit;
4515
+ reportRequestSuccess(): void;
4675
4516
  /**
4676
- * Makes an authenticated HTTP request to the platform API.
4517
+ * Reports a failed API request to the connection monitor.
4677
4518
  *
4678
- * @param path - API endpoint path
4679
- * @param method - HTTP method
4680
- * @param options - Optional request configuration
4681
- * @param options.body - Request body
4682
- * @param options.headers - Additional headers
4683
- * @param options.raw - If true, returns raw Response instead of parsing
4684
- * @returns Promise resolving to the response data or raw Response
4685
- */
4686
- protected request<T>(path: string, method: Method, options?: {
4687
- body?: unknown;
4688
- headers?: Record<string, string>;
4689
- raw?: boolean;
4690
- }): Promise<T>;
4691
- /**
4692
- * Makes an authenticated HTTP request to the game's backend Worker.
4693
- * Uses gameUrl if set, otherwise falls back to platform API.
4519
+ * Only network errors are tracked (not 4xx/5xx HTTP responses).
4520
+ * After consecutive failures exceed the threshold, the state transitions
4521
+ * to 'degraded' or 'offline'.
4522
+ *
4523
+ * Typically called automatically by the SDK's request wrapper.
4524
+ * No-op in iframe mode (platform handles monitoring).
4694
4525
  *
4695
- * @param path - API endpoint path
4696
- * @param method - HTTP method
4697
- * @param body - Request body (optional)
4698
- * @param headers - Additional headers (optional)
4699
- * @param raw - If true, returns raw Response instead of parsing (optional)
4700
- * @returns Promise resolving to the response data or raw Response
4526
+ * @param error - The error from the failed request
4701
4527
  */
4702
- protected requestGameBackend<T>(path: string, method: Method, body?: unknown, headers?: Record<string, string>, raw?: boolean): Promise<T>;
4528
+ reportRequestFailure(error: unknown): void;
4703
4529
  /**
4704
- * Ensures a gameId is available, throwing an error if not.
4530
+ * Registers a callback to be called when connection issues are detected.
4531
+ *
4532
+ * The callback only fires for 'offline' and 'degraded' states, not when
4533
+ * recovering to 'online'. This provides a clean API for games to handle
4534
+ * disconnect scenarios without being notified of every state change.
4535
+ *
4536
+ * Works in both iframe and standalone modes transparently.
4537
+ *
4538
+ * @param callback - Function to call when connection degrades
4539
+ * @returns Cleanup function to unregister the callback
4540
+ *
4541
+ * @example
4542
+ * ```typescript
4543
+ * const cleanup = manager.onDisconnect(({ state, reason, displayAlert }) => {
4544
+ * if (state === 'offline') {
4545
+ * displayAlert?.('Connection lost. Saving your progress...', { type: 'error' })
4546
+ * saveGameState()
4547
+ * }
4548
+ * })
4705
4549
  *
4706
- * @returns The gameId
4707
- * @throws PlaycademyError if no gameId is configured
4550
+ * // Later: cleanup() to unregister
4551
+ * ```
4708
4552
  */
4709
- private _ensureGameId;
4553
+ onDisconnect(callback: DisconnectHandler): () => void;
4710
4554
  /**
4711
- * Detects and sets the authentication context (iframe vs standalone).
4712
- * Safe to call in any environment - isInIframe handles browser detection.
4555
+ * Stops connection monitoring and performs cleanup.
4556
+ *
4557
+ * Removes event listeners and clears heartbeat intervals.
4558
+ * Should be called when the client is being destroyed.
4713
4559
  */
4714
- private _detectAuthContext;
4560
+ stop(): void;
4715
4561
  /**
4716
- * Initializes connection monitoring if enabled.
4717
- * Safe to call in any environment - only runs in browser.
4562
+ * Sets up listener for platform connection state broadcasts (iframe mode only).
4718
4563
  */
4719
- private _initializeConnectionMonitor;
4564
+ private _setupPlatformListener;
4720
4565
  /**
4721
- * Initializes an internal game session for automatic session management.
4722
- * Only starts a session if:
4723
- * 1. A gameId is configured
4724
- * 2. No session already exists
4725
- * 3. autoStartSession is enabled (defaults to false)
4566
+ * Handles connection state changes from the monitor or platform.
4567
+ *
4568
+ * Coordinates between:
4569
+ * 1. Emitting events to the client (for client.on('connectionChange'))
4570
+ * 2. Calling the disconnect handler if configured
4571
+ * 3. Calling any additional handlers registered via onDisconnect()
4572
+ *
4573
+ * @param state - The new connection state
4574
+ * @param reason - Human-readable reason for the state change
4726
4575
  */
4727
- private _initializeInternalSession;
4728
- /** Identity provider connection methods (connect external accounts) */
4729
- identity: {
4730
- connect: (options: AuthOptions) => Promise<AuthResult>;
4731
- _getContext: () => {
4732
- isInIframe: boolean;
4733
- };
4734
- };
4735
- /** Runtime methods (getGameToken, exit) */
4736
- runtime: {
4737
- getGameToken: (gameId: string, options?: {
4738
- apply?: boolean;
4739
- }) => Promise<GameTokenResponse>;
4740
- exit: () => Promise<void>;
4741
- onInit: (handler: (context: GameContextPayload) => void) => void;
4742
- onTokenRefresh: (handler: (data: {
4743
- token: string;
4744
- exp: number;
4745
- }) => void) => void;
4746
- onPause: (handler: () => void) => void;
4747
- onResume: (handler: () => void) => void;
4748
- onForceExit: (handler: () => void) => void;
4749
- onOverlay: (handler: (isVisible: boolean) => void) => void;
4750
- ready: () => void;
4751
- sendTelemetry: (data: {
4752
- fps: number;
4753
- mem: number;
4754
- }) => void;
4755
- removeListener: (eventType: MessageEvents, handler: ((context: GameContextPayload) => void) | ((data: {
4756
- token: string;
4757
- exp: number;
4758
- }) => void) | (() => void) | ((isVisible: boolean) => void)) => void;
4759
- removeAllListeners: () => void;
4760
- getListenerCounts: () => Record<string, number>;
4761
- assets: {
4762
- url(pathOrStrings: string | TemplateStringsArray, ...values: unknown[]): string;
4763
- fetch: (path: string, options?: RequestInit) => Promise<Response>;
4764
- json: <T = unknown>(path: string) => Promise<T>;
4765
- blob: (path: string) => Promise<Blob>;
4766
- text: (path: string) => Promise<string>;
4767
- arrayBuffer: (path: string) => Promise<ArrayBuffer>;
4768
- };
4769
- };
4770
- /** User methods (me, inventory management) */
4771
- users: {
4772
- me: () => Promise<AuthenticatedUser>;
4773
- inventory: {
4774
- get: () => Promise<InventoryItemWithItem[]>;
4775
- add: (identifier: string, qty: number) => Promise<InventoryMutationResponse>;
4776
- remove: (identifier: string, qty: number) => Promise<InventoryMutationResponse>;
4777
- quantity: (identifier: string) => Promise<number>;
4778
- has: (identifier: string, minQuantity?: number) => Promise<boolean>;
4779
- };
4780
- };
4781
- /** TimeBack XP methods (today, total, history) */
4782
- timeback: {
4783
- startActivity: (metadata: _playcademy_timeback_types.ActivityData) => void;
4784
- pauseActivity: () => void;
4785
- resumeActivity: () => void;
4786
- endActivity: (data: _playcademy_timeback_types.EndActivityScoreData) => Promise<EndActivityResponse>;
4787
- };
4788
- /** Credits methods (credits management) */
4789
- credits: {
4790
- balance: () => Promise<number>;
4791
- add: (amount: number) => Promise<number>;
4792
- spend: (amount: number) => Promise<number>;
4793
- };
4794
- /** Platform-wide scores methods (submit, getUserScores) */
4795
- scores: {
4796
- submit: (gameId: string, score: number, metadata?: Record<string, unknown>) => Promise<ScoreSubmission>;
4797
- };
4798
- /** Realtime methods (token) */
4799
- realtime: {
4800
- token: {
4801
- get: () => Promise<RealtimeTokenResponse>;
4802
- };
4803
- open(channel?: string, url?: string): Promise<_playcademy_realtime_server_types.RealtimeChannel>;
4804
- };
4805
- /** Backend methods for calling custom game API routes */
4806
- backend: {
4807
- get<T = unknown>(path: string, headers?: Record<string, string>): Promise<T>;
4808
- post<T = unknown>(path: string, body?: unknown, headers?: Record<string, string>): Promise<T>;
4809
- put<T = unknown>(path: string, body?: unknown, headers?: Record<string, string>): Promise<T>;
4810
- patch<T = unknown>(path: string, body?: unknown, headers?: Record<string, string>): Promise<T>;
4811
- delete<T = unknown>(path: string, headers?: Record<string, string>): Promise<T>;
4812
- request<T = unknown>(path: string, method: Method, body?: unknown, headers?: Record<string, string>): Promise<T>;
4813
- download(path: string, method?: Method, body?: unknown, headers?: Record<string, string>): Promise<Response>;
4814
- url(pathOrStrings: string | TemplateStringsArray, ...values: unknown[]): string;
4815
- };
4816
- /** Auto-initializes a PlaycademyClient with context from the environment */
4817
- static init: typeof init;
4818
- /** Authenticates a user with email and password */
4819
- static login: typeof login;
4820
- /** Static identity utilities for OAuth operations */
4821
- static identity: {
4822
- parseOAuthState: typeof parseOAuthState;
4823
- };
4576
+ private _handleConnectionChange;
4824
4577
  }
4825
4578
 
4826
4579
  /**
4827
- * Core client configuration and lifecycle types
4828
- */
4829
- type TokenType = 'session' | 'apiKey' | 'gameJwt';
4830
- interface ClientConfig {
4831
- baseUrl: string;
4832
- gameUrl?: string;
4833
- token?: string;
4834
- tokenType?: TokenType;
4835
- gameId?: string;
4836
- autoStartSession?: boolean;
4837
- onDisconnect?: DisconnectHandler;
4838
- enableConnectionMonitoring?: boolean;
4839
- }
4840
- /**
4841
- * Handler called when connection state changes to offline or degraded.
4842
- * Games can implement this to handle disconnects gracefully.
4580
+ * @fileoverview Playcademy Messaging System
4581
+ *
4582
+ * This file implements a unified messaging system for the Playcademy platform that handles
4583
+ * communication between different contexts:
4584
+ *
4585
+ * 1. **Iframe-to-Parent Communication**: When games run inside iframes (production/development),
4586
+ * they need to communicate with the parent window using postMessage API
4587
+ *
4588
+ * 2. **Local Communication**: When games run in the same context (local development),
4589
+ * they use CustomEvents for internal messaging
4590
+ *
4591
+ * The system automatically detects the runtime environment and chooses the appropriate
4592
+ * transport method, abstracting this complexity from the developer.
4593
+ *
4594
+ * **Architecture Overview**:
4595
+ * - Games run in iframes for security and isolation
4596
+ * - Parent window (Playcademy shell) manages game lifecycle
4597
+ * - Messages flow bidirectionally between parent and iframe
4598
+ * - Local development mode simulates this architecture without iframes
4843
4599
  */
4844
- type DisconnectHandler = (context: DisconnectContext) => void | Promise<void>;
4600
+
4845
4601
  /**
4846
- * Context provided to disconnect handlers
4602
+ * Enumeration of all message types used in the Playcademy messaging system.
4603
+ *
4604
+ * **Message Flow Patterns**:
4605
+ *
4606
+ * **Parent → Game (Overworld → Game)**:
4607
+ * - INIT: Provides game with authentication token and configuration
4608
+ * - TOKEN_REFRESH: Updates game's authentication token before expiry
4609
+ * - PAUSE/RESUME: Controls game execution state
4610
+ * - FORCE_EXIT: Immediately terminates the game
4611
+ * - OVERLAY: Shows/hides UI overlays over the game
4612
+ *
4613
+ * **Game → Parent (Game → Overworld)**:
4614
+ * - READY: Game has loaded and is ready to receive messages
4615
+ * - EXIT: Game requests to be closed (user clicked exit, game ended, etc.)
4616
+ * - TELEMETRY: Game reports performance metrics (FPS, memory usage, etc.)
4847
4617
  */
4848
- interface DisconnectContext {
4849
- /** Current connection state */
4850
- state: 'offline' | 'degraded';
4851
- /** Reason for the disconnect */
4852
- reason: string;
4853
- /** Timestamp when disconnect was detected */
4854
- timestamp: number;
4855
- /** Utility to display a platform-level alert */
4856
- displayAlert: (message: string, options?: {
4857
- type?: 'info' | 'warning' | 'error';
4858
- duration?: number;
4859
- }) => void;
4860
- }
4861
- interface InitPayload {
4862
- /** Hub API base URL */
4863
- baseUrl: string;
4864
- /** Game deployment URL (serves both frontend assets and backend API) */
4865
- gameUrl?: string;
4866
- /** Short-lived game token */
4867
- token: string;
4868
- /** Game ID */
4869
- gameId: string;
4870
- /** Realtime WebSocket URL */
4871
- realtimeUrl?: string;
4618
+ declare enum MessageEvents {
4619
+ /**
4620
+ * Initializes the game with authentication context and configuration.
4621
+ * Sent immediately after game iframe loads.
4622
+ * Payload:
4623
+ * - `baseUrl`: string
4624
+ * - `token`: string
4625
+ * - `gameId`: string
4626
+ */
4627
+ INIT = "PLAYCADEMY_INIT",
4628
+ /**
4629
+ * Updates the game's authentication token before it expires.
4630
+ * Sent periodically to maintain valid authentication.
4631
+ * Payload:
4632
+ * - `token`: string
4633
+ * - `exp`: number
4634
+ */
4635
+ TOKEN_REFRESH = "PLAYCADEMY_TOKEN_REFRESH",
4636
+ /**
4637
+ * Pauses game execution (e.g., when user switches tabs).
4638
+ * Game should pause timers, animations, and user input.
4639
+ * Payload: void
4640
+ */
4641
+ PAUSE = "PLAYCADEMY_PAUSE",
4642
+ /**
4643
+ * Resumes game execution after being paused.
4644
+ * Game should restore timers, animations, and user input.
4645
+ * Payload: void
4646
+ */
4647
+ RESUME = "PLAYCADEMY_RESUME",
4648
+ /**
4649
+ * Forces immediate game termination (emergency exit).
4650
+ * Game should clean up resources and exit immediately.
4651
+ * Payload: void
4652
+ */
4653
+ FORCE_EXIT = "PLAYCADEMY_FORCE_EXIT",
4654
+ /**
4655
+ * Shows or hides UI overlays over the game.
4656
+ * Game may need to pause or adjust rendering accordingly.
4657
+ * Payload: boolean (true = show overlay, false = hide overlay)
4658
+ */
4659
+ OVERLAY = "PLAYCADEMY_OVERLAY",
4660
+ /**
4661
+ * Broadcasts connection state changes to games.
4662
+ * Sent by platform when network connectivity changes.
4663
+ * Payload:
4664
+ * - `state`: 'online' | 'offline' | 'degraded'
4665
+ * - `reason`: string
4666
+ */
4667
+ CONNECTION_STATE = "PLAYCADEMY_CONNECTION_STATE",
4668
+ /**
4669
+ * Game has finished loading and is ready to receive messages.
4670
+ * Sent once after game initialization is complete.
4671
+ * Payload: void
4672
+ */
4673
+ READY = "PLAYCADEMY_READY",
4674
+ /**
4675
+ * Game requests to be closed/exited.
4676
+ * Sent when user clicks exit button or game naturally ends.
4677
+ * Payload: void
4678
+ */
4679
+ EXIT = "PLAYCADEMY_EXIT",
4680
+ /**
4681
+ * Game reports performance telemetry data.
4682
+ * Sent periodically for monitoring and analytics.
4683
+ * Payload:
4684
+ * - `fps`: number
4685
+ * - `mem`: number
4686
+ */
4687
+ TELEMETRY = "PLAYCADEMY_TELEMETRY",
4688
+ /**
4689
+ * Game reports key events to parent.
4690
+ * Sent when certain keys are pressed within the game iframe.
4691
+ * Payload:
4692
+ * - `key`: string
4693
+ * - `code?`: string
4694
+ * - `type`: 'keydown' | 'keyup'
4695
+ */
4696
+ KEY_EVENT = "PLAYCADEMY_KEY_EVENT",
4697
+ /**
4698
+ * Game requests platform to display an alert.
4699
+ * Sent when connection issues are detected or other important events occur.
4700
+ * Payload:
4701
+ * - `message`: string
4702
+ * - `options`: `{ type?: 'info' | 'warning' | 'error', duration?: number }`
4703
+ */
4704
+ DISPLAY_ALERT = "PLAYCADEMY_DISPLAY_ALERT",
4705
+ /**
4706
+ * Notifies about authentication state changes.
4707
+ * Can be sent in both directions depending on auth flow.
4708
+ * Payload:
4709
+ * - `authenticated`: boolean
4710
+ * - `user`: UserInfo | null
4711
+ * - `error`: Error | null
4712
+ */
4713
+ AUTH_STATE_CHANGE = "PLAYCADEMY_AUTH_STATE_CHANGE",
4714
+ /**
4715
+ * OAuth callback data from popup/new-tab windows.
4716
+ * Sent from popup window back to parent after OAuth completes.
4717
+ * Payload:
4718
+ * - `code`: string (OAuth authorization code)
4719
+ * - `state`: string (OAuth state for CSRF protection)
4720
+ * - `error`: string | null (OAuth error if any)
4721
+ */
4722
+ AUTH_CALLBACK = "PLAYCADEMY_AUTH_CALLBACK"
4872
4723
  }
4873
4724
  /**
4874
- * Simplified user data passed to games via InitPayload
4875
- * This is a subset of AuthenticatedUser suitable for external game consumption
4725
+ * Type definition for message handler functions.
4726
+ * These functions are called when a specific message type is received.
4727
+ *
4728
+ * @template T - The type of payload data the handler expects
4729
+ * @param payload - The data sent with the message
4876
4730
  */
4877
- interface GameUser {
4878
- /** Playcademy user ID */
4879
- id: string;
4880
- /** Unique username */
4881
- username: string | null;
4882
- /** Display name */
4883
- name: string | null;
4884
- /** Email address */
4885
- email: string | null;
4886
- /** Profile image URL */
4887
- image: string | null;
4888
- /** Whether the user has a Timeback account */
4889
- hasTimebackAccount: boolean;
4890
- }
4891
- type GameContextPayload = {
4892
- token: string;
4893
- baseUrl: string;
4894
- realtimeUrl: string;
4895
- gameId: string;
4896
- forwardKeys?: string[];
4897
- };
4898
- type EventListeners = {
4899
- [E in keyof ClientEvents]?: Array<(payload: ClientEvents[E]) => void>;
4900
- };
4901
- interface ClientEvents {
4902
- authChange: {
4903
- token: string | null;
4904
- };
4905
- inventoryChange: {
4906
- itemId: string;
4907
- delta: number;
4908
- newTotal: number;
4909
- };
4910
- levelUp: {
4911
- oldLevel: number;
4912
- newLevel: number;
4913
- creditsAwarded: number;
4914
- };
4915
- xpGained: {
4916
- amount: number;
4917
- totalXP: number;
4918
- leveledUp: boolean;
4919
- };
4920
- connectionChange: {
4921
- state: 'online' | 'offline' | 'degraded';
4922
- reason: string;
4923
- };
4924
- }
4925
-
4926
- /**
4927
- * Event and message payload types for SDK messaging system
4928
- */
4929
-
4930
- interface DisplayAlertPayload {
4931
- message: string;
4932
- options?: {
4933
- type?: 'info' | 'warning' | 'error';
4934
- duration?: number;
4935
- };
4936
- }
4937
- /**
4938
- * Authentication state change event payload.
4939
- * Used when authentication state changes in the application.
4940
- */
4941
- interface AuthStateChangePayload {
4942
- /** Whether the user is currently authenticated */
4943
- authenticated: boolean;
4944
- /** User information if authenticated, null otherwise */
4945
- user: UserInfo | null;
4946
- /** Error information if authentication failed */
4947
- error: Error | null;
4948
- }
4949
- /**
4950
- * OAuth callback event payload.
4951
- * Used when OAuth flow completes in popup/new-tab windows.
4952
- */
4953
- interface AuthCallbackPayload {
4954
- /** OAuth authorization code */
4955
- code: string;
4956
- /** OAuth state parameter for CSRF protection */
4957
- state: string;
4958
- /** Error message if OAuth flow failed */
4959
- error: string | null;
4960
- }
4961
- /**
4962
- * Message sent from server callback to opener window.
4963
- * This is the standardized format from our server-first architecture.
4964
- */
4965
- interface AuthServerMessage {
4966
- /** Type of the message */
4967
- type: 'PLAYCADEMY_AUTH_STATE_CHANGE';
4968
- /** Whether the user is currently authenticated */
4969
- authenticated: boolean;
4970
- /** Whether the authentication was successful */
4971
- success: boolean;
4972
- /** Timestamp of the message */
4973
- ts: number;
4974
- /** User information if authentication was successful */
4975
- user?: UserInfo;
4976
- /** Error message if authentication failed */
4977
- error?: string;
4978
- }
4979
- /**
4980
- * Token refresh event payload.
4981
- * Sent when authentication token is updated.
4982
- */
4983
- interface TokenRefreshPayload {
4984
- /** New authentication token */
4985
- token: string;
4986
- /** Token expiration timestamp */
4987
- exp: number;
4988
- }
4731
+ type MessageHandler<T = unknown> = (payload: T) => void;
4989
4732
  /**
4990
- * Telemetry event payload.
4991
- * Performance metrics sent from the game.
4733
+ * Type mapping that defines the payload structure for each message type.
4734
+ * This ensures type safety when sending and receiving messages.
4735
+ *
4736
+ * **Usage Examples**:
4737
+ * ```typescript
4738
+ * // Type-safe message sending
4739
+ * messaging.send(MessageEvents.INIT, { baseUrl: '/api', token: 'abc', gameId: '123' })
4740
+ *
4741
+ * // Type-safe message handling
4742
+ * messaging.listen(MessageEvents.TOKEN_REFRESH, ({ token, exp }) => {
4743
+ * console.log(`New token expires at: ${new Date(exp)}`)
4744
+ * })
4745
+ * ```
4992
4746
  */
4993
- interface TelemetryPayload {
4994
- /** Frames per second */
4995
- fps: number;
4996
- /** Memory usage in MB */
4997
- mem: number;
4998
- }
4747
+ type MessageEventMap = {
4748
+ /** Game initialization context with API endpoint, auth token, and game ID */
4749
+ [MessageEvents.INIT]: GameContextPayload;
4750
+ /** Token refresh data with new token and expiration timestamp */
4751
+ [MessageEvents.TOKEN_REFRESH]: TokenRefreshPayload;
4752
+ /** Pause message has no payload data */
4753
+ [MessageEvents.PAUSE]: void;
4754
+ /** Resume message has no payload data */
4755
+ [MessageEvents.RESUME]: void;
4756
+ /** Force exit message has no payload data */
4757
+ [MessageEvents.FORCE_EXIT]: void;
4758
+ /** Overlay visibility state (true = show, false = hide) */
4759
+ [MessageEvents.OVERLAY]: boolean;
4760
+ /** Connection state change from platform */
4761
+ [MessageEvents.CONNECTION_STATE]: ConnectionStatePayload;
4762
+ /** Ready message has no payload data */
4763
+ [MessageEvents.READY]: void;
4764
+ /** Exit message has no payload data */
4765
+ [MessageEvents.EXIT]: void;
4766
+ /** Performance telemetry data */
4767
+ [MessageEvents.TELEMETRY]: TelemetryPayload;
4768
+ /** Key event data */
4769
+ [MessageEvents.KEY_EVENT]: KeyEventPayload;
4770
+ /** Display alert request from game */
4771
+ [MessageEvents.DISPLAY_ALERT]: DisplayAlertPayload;
4772
+ /** Authentication state change notification */
4773
+ [MessageEvents.AUTH_STATE_CHANGE]: AuthStateChangePayload;
4774
+ /** OAuth callback data from popup/new-tab windows */
4775
+ [MessageEvents.AUTH_CALLBACK]: AuthCallbackPayload;
4776
+ };
4999
4777
  /**
5000
- * Keyboard event payload.
5001
- * Key events forwarded from the game.
4778
+ * **PlaycademyMessaging Class**
4779
+ *
4780
+ * This is the core messaging system that handles all communication in the Playcademy platform.
4781
+ * It automatically detects the runtime environment and chooses the appropriate transport method.
4782
+ *
4783
+ * **Key Features**:
4784
+ * 1. **Automatic Transport Selection**: Detects iframe vs local context and uses appropriate method
4785
+ * 2. **Type Safety**: Full TypeScript support with payload type checking
4786
+ * 3. **Bidirectional Communication**: Handles both parent→game and game→parent messages
4787
+ * 4. **Event Cleanup**: Proper listener management to prevent memory leaks
4788
+ *
4789
+ * **Transport Methods**:
4790
+ * - **postMessage**: Used for iframe-to-parent communication (production/development)
4791
+ * - **CustomEvent**: Used for local same-context communication (local development)
4792
+ *
4793
+ * **Runtime Detection Logic**:
4794
+ * - If `window.self !== window.top`: We're in an iframe, use postMessage
4795
+ * - If `window.self === window.top`: We're in the same context, use CustomEvent
4796
+ *
4797
+ * **Example Usage**:
4798
+ * ```typescript
4799
+ * // Send a message (automatically chooses transport)
4800
+ * messaging.send(MessageEvents.READY, undefined)
4801
+ *
4802
+ * // Listen for messages (handles both transports)
4803
+ * messaging.listen(MessageEvents.INIT, (payload) => {
4804
+ * console.log('Game initialized with:', payload)
4805
+ * })
4806
+ *
4807
+ * // Clean up listeners
4808
+ * messaging.unlisten(MessageEvents.INIT, handler)
4809
+ * ```
5002
4810
  */
5003
- interface KeyEventPayload {
5004
- /** Key value (e.g., 'Escape', 'F1') */
5005
- key: string;
5006
- /** Key code (optional) */
5007
- code?: string;
5008
- /** Event type */
5009
- type: 'keydown' | 'keyup';
4811
+ declare class PlaycademyMessaging {
4812
+ /**
4813
+ * Internal storage for message listeners.
4814
+ *
4815
+ * **Structure Explanation**:
4816
+ * - Outer Map: MessageEvents → Map of handlers for that event type
4817
+ * - Inner Map: MessageHandler Object containing both listener types
4818
+ * - Object: { postMessage: EventListener, customEvent: EventListener }
4819
+ *
4820
+ * **Why Two Listeners Per Handler?**:
4821
+ * Since we don't know at registration time which transport will be used,
4822
+ * we register both a postMessage listener and a CustomEvent listener.
4823
+ * The appropriate one will be triggered based on the runtime context.
4824
+ */
4825
+ private listeners;
4826
+ /**
4827
+ * **Send Message Method**
4828
+ *
4829
+ * Sends a message using the appropriate transport method based on the runtime context.
4830
+ * This is the main public API for sending messages in the Playcademy system.
4831
+ *
4832
+ * **How It Works**:
4833
+ * 1. Analyzes the message type and current runtime context
4834
+ * 2. Determines if we're in an iframe and if this message should use postMessage
4835
+ * 3. Routes to the appropriate transport method (postMessage or CustomEvent)
4836
+ *
4837
+ * **Transport Selection Logic**:
4838
+ * - **postMessage**: Used when game is in iframe and sending to parent, OR when target window is specified
4839
+ * - **CustomEvent**: Used for local/same-context communication
4840
+ *
4841
+ * **Type Safety**:
4842
+ * The generic type parameter `K` ensures that the payload type matches
4843
+ * the expected type for the given message event.
4844
+ *
4845
+ * @template K - The message event type (ensures type safety)
4846
+ * @param type - The message event type to send
4847
+ * @param payload - The data to send with the message (type-checked)
4848
+ * @param options - Optional configuration for message sending
4849
+ *
4850
+ * @example
4851
+ * ```typescript
4852
+ * // Send game ready signal (no payload)
4853
+ * messaging.send(MessageEvents.READY, undefined)
4854
+ *
4855
+ * // Send telemetry data (typed payload)
4856
+ * messaging.send(MessageEvents.TELEMETRY, { fps: 60, mem: 128 })
4857
+ *
4858
+ * // Send to specific iframe window (parent to iframe communication)
4859
+ * messaging.send(MessageEvents.INIT, { baseUrl, token, gameId }, {
4860
+ * target: iframe.contentWindow,
4861
+ * origin: '*'
4862
+ * })
4863
+ *
4864
+ * // TypeScript will error if payload type is wrong
4865
+ * messaging.send(MessageEvents.INIT, "wrong type") // ❌ Error
4866
+ * ```
4867
+ */
4868
+ send<K extends MessageEvents>(type: K, payload: MessageEventMap[K], options?: {
4869
+ target?: Window;
4870
+ origin?: string;
4871
+ }): void;
4872
+ /**
4873
+ * **Listen for Messages Method**
4874
+ *
4875
+ * Registers a message listener that will be called when the specified message type is received.
4876
+ * This method automatically handles both postMessage and CustomEvent sources.
4877
+ *
4878
+ * **Why Register Two Listeners?**:
4879
+ * Since we don't know at registration time which transport method will be used to send
4880
+ * messages, we register listeners for both possible sources:
4881
+ * 1. **postMessage listener**: Handles messages from iframe-to-parent communication
4882
+ * 2. **CustomEvent listener**: Handles messages from local same-context communication
4883
+ *
4884
+ * **Message Processing**:
4885
+ * - **postMessage**: Extracts data from `event.data.payload` or `event.data`
4886
+ * - **CustomEvent**: Extracts data from `event.detail`
4887
+ *
4888
+ * **Type Safety**:
4889
+ * The handler function receives the correctly typed payload based on the message type.
4890
+ *
4891
+ * @template K - The message event type (ensures type safety)
4892
+ * @param type - The message event type to listen for
4893
+ * @param handler - Function to call when the message is received
4894
+ *
4895
+ * @example
4896
+ * ```typescript
4897
+ * // Listen for game initialization
4898
+ * messaging.listen(MessageEvents.INIT, (payload) => {
4899
+ * // payload is automatically typed as GameContextPayload
4900
+ * console.log(`Game ${payload.gameId} initialized`)
4901
+ * console.log(`API base URL: ${payload.baseUrl}`)
4902
+ * })
4903
+ *
4904
+ * // Listen for token refresh
4905
+ * messaging.listen(MessageEvents.TOKEN_REFRESH, ({ token, exp }) => {
4906
+ * // payload is automatically typed as { token: string; exp: number }
4907
+ * updateAuthToken(token)
4908
+ * scheduleTokenRefresh(exp)
4909
+ * })
4910
+ * ```
4911
+ */
4912
+ listen<K extends MessageEvents>(type: K, handler: MessageHandler<MessageEventMap[K]>): void;
4913
+ /**
4914
+ * **Remove Message Listener Method**
4915
+ *
4916
+ * Removes a previously registered message listener to prevent memory leaks and unwanted callbacks.
4917
+ * This method cleans up both the postMessage and CustomEvent listeners that were registered.
4918
+ *
4919
+ * **Why Clean Up Both Listeners?**:
4920
+ * When we registered the listener with `listen()`, we created two browser event listeners:
4921
+ * 1. A 'message' event listener for postMessage communication
4922
+ * 2. A custom event listener for local CustomEvent communication
4923
+ *
4924
+ * Both must be removed to prevent memory leaks and ensure the handler is completely unregistered.
4925
+ *
4926
+ * **Memory Management**:
4927
+ * - Removes both event listeners from the browser
4928
+ * - Cleans up internal tracking maps
4929
+ * - If no more handlers exist for a message type, removes the entire type entry
4930
+ *
4931
+ * **Safe to Call Multiple Times**:
4932
+ * This method is idempotent - calling it multiple times with the same handler is safe.
4933
+ *
4934
+ * @template K - The message event type (ensures type safety)
4935
+ * @param type - The message event type to stop listening for
4936
+ * @param handler - The exact handler function that was passed to listen()
4937
+ *
4938
+ * @example
4939
+ * ```typescript
4940
+ * // Register a handler
4941
+ * const handleInit = (payload) => console.log('Game initialized')
4942
+ * messaging.listen(MessageEvents.INIT, handleInit)
4943
+ *
4944
+ * // Later, remove the handler
4945
+ * messaging.unlisten(MessageEvents.INIT, handleInit)
4946
+ *
4947
+ * // Safe to call multiple times
4948
+ * messaging.unlisten(MessageEvents.INIT, handleInit) // No error
4949
+ * ```
4950
+ */
4951
+ unlisten<K extends MessageEvents>(type: K, handler: MessageHandler<MessageEventMap[K]>): void;
4952
+ /**
4953
+ * **Get Messaging Context Method**
4954
+ *
4955
+ * Analyzes the current runtime environment and message type to determine the appropriate
4956
+ * transport method and configuration for sending messages.
4957
+ *
4958
+ * **Runtime Environment Detection**:
4959
+ * The method detects whether the code is running in an iframe by comparing:
4960
+ * - `window.self`: Reference to the current window
4961
+ * - `window.top`: Reference to the topmost window in the hierarchy
4962
+ *
4963
+ * If they're different, we're in an iframe. If they're the same, we're in the top-level window.
4964
+ *
4965
+ * **Message Direction Analysis**:
4966
+ * Different message types flow in different directions:
4967
+ * - **Game → Parent**: READY, EXIT, TELEMETRY (use postMessage when in iframe)
4968
+ * - **Parent → Game**: INIT, TOKEN_REFRESH, PAUSE, etc. (use CustomEvent in local dev, or postMessage with target)
4969
+ *
4970
+ * **Cross-Context Communication**:
4971
+ * The messaging system supports cross-context targeting through the optional `target` parameter.
4972
+ * When a target window is specified, postMessage is used regardless of the current context.
4973
+ * This enables parent-to-iframe communication through the unified messaging API.
4974
+ *
4975
+ * **Transport Selection Logic**:
4976
+ * - **postMessage**: Used when game is in iframe AND sending to parent, OR when target window is specified
4977
+ * - **CustomEvent**: Used for all other cases (local development, same-context communication)
4978
+ *
4979
+ * **Security Considerations**:
4980
+ * The origin is currently set to '*' for development convenience, but should be
4981
+ * configurable for production security.
4982
+ *
4983
+ * @param eventType - The message event type being sent
4984
+ * @returns Configuration object with transport method and target information
4985
+ *
4986
+ * @example
4987
+ * ```typescript
4988
+ * // In iframe sending READY to parent
4989
+ * const context = getMessagingContext(MessageEvents.READY)
4990
+ * // Returns: { shouldUsePostMessage: true, target: window.parent, origin: '*' }
4991
+ *
4992
+ * // In same context sending INIT
4993
+ * const context = getMessagingContext(MessageEvents.INIT)
4994
+ * // Returns: { shouldUsePostMessage: false, target: undefined, origin: '*' }
4995
+ * ```
4996
+ */
4997
+ private getMessagingContext;
4998
+ /**
4999
+ * **Send Via PostMessage Method**
5000
+ *
5001
+ * Sends a message using the browser's postMessage API for iframe-to-parent communication.
5002
+ * This method is used when the game is running in an iframe and needs to communicate
5003
+ * with the parent window (the Playcademy shell).
5004
+ *
5005
+ * **PostMessage Protocol**:
5006
+ * The postMessage API is the standard way for iframes to communicate with their parent.
5007
+ * It's secure, cross-origin capable, and designed specifically for this use case.
5008
+ *
5009
+ * **Message Structure**:
5010
+ * The method creates a message object with the following structure:
5011
+ * - `type`: The message event type (e.g., 'PLAYCADEMY_READY')
5012
+ * - `payload`: The message data (if any)
5013
+ *
5014
+ * **Payload Handling**:
5015
+ * - **All payloads**: Wrapped in a `payload` property for consistency (e.g., { type, payload: data })
5016
+ * - **Undefined payloads**: Only the type is sent (e.g., { type })
5017
+ *
5018
+ * **Security**:
5019
+ * The origin parameter controls which domains can receive the message.
5020
+ * Currently set to '*' for development, but should be restricted in production.
5021
+ *
5022
+ * @template K - The message event type (ensures type safety)
5023
+ * @param type - The message event type to send
5024
+ * @param payload - The data to send with the message
5025
+ * @param target - The target window (defaults to parent window)
5026
+ * @param origin - The allowed origin for the message (defaults to '*')
5027
+ *
5028
+ * @example
5029
+ * ```typescript
5030
+ * // Send ready signal (no payload)
5031
+ * sendViaPostMessage(MessageEvents.READY, undefined)
5032
+ * // Sends: { type: 'PLAYCADEMY_READY' }
5033
+ *
5034
+ * // Send telemetry data (object payload)
5035
+ * sendViaPostMessage(MessageEvents.TELEMETRY, { fps: 60, mem: 128 })
5036
+ * // Sends: { type: 'PLAYCADEMY_TELEMETRY', payload: { fps: 60, mem: 128 } }
5037
+ *
5038
+ * // Send overlay state (primitive payload)
5039
+ * sendViaPostMessage(MessageEvents.OVERLAY, true)
5040
+ * // Sends: { type: 'PLAYCADEMY_OVERLAY', payload: true }
5041
+ * ```
5042
+ */
5043
+ private sendViaPostMessage;
5044
+ /**
5045
+ * **Send Via CustomEvent Method**
5046
+ *
5047
+ * Sends a message using the browser's CustomEvent API for local same-context communication.
5048
+ * This method is used when both the sender and receiver are in the same window context,
5049
+ * typically during local development or when the parent needs to send messages to the game.
5050
+ *
5051
+ * **CustomEvent Protocol**:
5052
+ * CustomEvent is a browser API for creating and dispatching custom events within the same
5053
+ * window context. It's simpler than postMessage but only works within the same origin/context.
5054
+ *
5055
+ * **Event Structure**:
5056
+ * - **type**: The event name (e.g., 'PLAYCADEMY_INIT')
5057
+ * - **detail**: The payload data attached to the event
5058
+ *
5059
+ * **When This Is Used**:
5060
+ * - Local development when game and shell run in the same context
5061
+ * - Parent-to-game communication (INIT, TOKEN_REFRESH, PAUSE, etc.)
5062
+ * - Any scenario where postMessage isn't needed
5063
+ *
5064
+ * **Event Bubbling**:
5065
+ * CustomEvents are dispatched on the window object and can be listened to by any
5066
+ * code in the same context using `addEventListener(type, handler)`.
5067
+ *
5068
+ * @template K - The message event type (ensures type safety)
5069
+ * @param type - The message event type to send (becomes the event name)
5070
+ * @param payload - The data to send with the message (stored in event.detail)
5071
+ *
5072
+ * @example
5073
+ * ```typescript
5074
+ * // Send initialization data
5075
+ * sendViaCustomEvent(MessageEvents.INIT, {
5076
+ * baseUrl: '/api',
5077
+ * token: 'abc123',
5078
+ * gameId: 'game-456'
5079
+ * })
5080
+ * // Creates: CustomEvent('PLAYCADEMY_INIT', { detail: { baseUrl, token, gameId } })
5081
+ *
5082
+ * // Send pause signal
5083
+ * sendViaCustomEvent(MessageEvents.PAUSE, undefined)
5084
+ * // Creates: CustomEvent('PLAYCADEMY_PAUSE', { detail: undefined })
5085
+ *
5086
+ * // Listeners can access the data via event.detail:
5087
+ * window.addEventListener('PLAYCADEMY_INIT', (event) => {
5088
+ * console.log(event.detail.gameId) // 'game-456'
5089
+ * })
5090
+ * ```
5091
+ */
5092
+ private sendViaCustomEvent;
5010
5093
  }
5011
5094
  /**
5012
- * Connection state payload.
5013
- * Broadcast from platform to games when connection changes.
5095
+ * **Playcademy Messaging Singleton**
5096
+ *
5097
+ * This is the main messaging instance used throughout the Playcademy platform.
5098
+ * It's exported as a singleton to ensure consistent communication across all parts
5099
+ * of the application.
5100
+ *
5101
+ * **Why a Singleton?**:
5102
+ * - Ensures all parts of the app use the same messaging instance
5103
+ * - Prevents conflicts between multiple messaging systems
5104
+ * - Simplifies the API - no need to pass instances around
5105
+ * - Maintains consistent event listener management
5106
+ *
5107
+ * **Usage in Different Contexts**:
5108
+ *
5109
+ * **In Games**:
5110
+ * ```typescript
5111
+ * import { messaging, MessageEvents } from '@playcademy/sdk'
5112
+ *
5113
+ * // Tell parent we're ready
5114
+ * messaging.send(MessageEvents.READY, undefined)
5115
+ *
5116
+ * // Listen for pause/resume
5117
+ * messaging.listen(MessageEvents.PAUSE, () => game.pause())
5118
+ * messaging.listen(MessageEvents.RESUME, () => game.resume())
5119
+ * ```
5120
+ *
5121
+ * **In Parent Shell**:
5122
+ * ```typescript
5123
+ * import { messaging, MessageEvents } from '@playcademy/sdk'
5124
+ *
5125
+ * // Send initialization data to game
5126
+ * messaging.send(MessageEvents.INIT, { baseUrl, token, gameId })
5127
+ *
5128
+ * // Listen for game events
5129
+ * messaging.listen(MessageEvents.EXIT, () => closeGame())
5130
+ * messaging.listen(MessageEvents.READY, () => showGame())
5131
+ * ```
5132
+ *
5133
+ * **Automatic Transport Selection**:
5134
+ * The messaging system automatically chooses the right transport method:
5135
+ * - Uses postMessage when game is in iframe sending to parent
5136
+ * - Uses CustomEvent for local development and parent-to-game communication
5137
+ *
5138
+ * **Type Safety**:
5139
+ * All message sending and receiving is fully type-safe with TypeScript.
5014
5140
  */
5015
- interface ConnectionStatePayload {
5016
- state: 'online' | 'offline' | 'degraded';
5017
- reason: string;
5018
- }
5141
+ declare const messaging: PlaycademyMessaging;
5019
5142
 
5020
5143
  /**
5021
- * SDK-specific API response types
5144
+ * Auto-initializes a PlaycademyClient with context from the environment.
5145
+ * Works in both iframe mode (production/development) and standalone mode (local dev).
5146
+ *
5147
+ * This is the recommended way to initialize the SDK as it automatically:
5148
+ * - Detects the runtime environment (iframe vs standalone)
5149
+ * - Configures the client with the appropriate context
5150
+ * - Sets up event listeners for token refresh
5151
+ * - Exposes the client for debugging in development mode
5152
+ *
5153
+ * @param options - Optional configuration overrides
5154
+ * @param options.baseUrl - Override the base URL for API requests
5155
+ * @returns Promise resolving to a fully initialized PlaycademyClient
5156
+ * @throws Error if not running in a browser context
5157
+ *
5158
+ * @example
5159
+ * ```typescript
5160
+ * // Default initialization
5161
+ * const client = await PlaycademyClient.init()
5162
+ *
5163
+ * // With custom base URL
5164
+ * const client = await PlaycademyClient.init({ baseUrl: 'https://custom.api.com' })
5165
+ * ```
5022
5166
  */
5023
- type LoginResponse = {
5024
- token: string;
5025
- };
5026
- type GameTokenResponse = {
5027
- token: string;
5028
- exp: number;
5029
- };
5030
- type StartSessionResponse = {
5031
- sessionId: string;
5032
- };
5033
- type InventoryMutationResponse = {
5034
- newTotal: number;
5035
- };
5167
+ declare function init<T extends PlaycademyClient = PlaycademyClient>(this: new (...args: ConstructorParameters<typeof PlaycademyClient>) => T, options?: {
5168
+ baseUrl?: string;
5169
+ allowedParentOrigins?: string[];
5170
+ onDisconnect?: DisconnectHandler;
5171
+ enableConnectionMonitoring?: boolean;
5172
+ }): Promise<T>;
5036
5173
 
5037
5174
  /**
5038
- * Realtime namespace types
5175
+ * Authenticates a user with email and password.
5176
+ *
5177
+ * This is a standalone authentication method that doesn't require an initialized client.
5178
+ * Use this for login flows before creating a client instance.
5179
+ *
5180
+ * @deprecated Use client.auth.login() instead for better error handling and automatic token management
5181
+ *
5182
+ * @param baseUrl - The base URL of the Playcademy API
5183
+ * @param email - User's email address
5184
+ * @param password - User's password
5185
+ * @returns Promise resolving to authentication response with token
5186
+ * @throws PlaycademyError if authentication fails or network error occurs
5187
+ *
5188
+ * @example
5189
+ * ```typescript
5190
+ * // Preferred approach:
5191
+ * const client = new PlaycademyClient({ baseUrl: '/api' })
5192
+ * const result = await client.auth.login({
5193
+ * email: 'user@example.com',
5194
+ * password: 'password'
5195
+ * })
5196
+ *
5197
+ * // Legacy approach (still works):
5198
+ * try {
5199
+ * const response = await PlaycademyClient.login('/api', 'user@example.com', 'password')
5200
+ * const client = new PlaycademyClient({ token: response.token })
5201
+ * } catch (error) {
5202
+ * console.error('Login failed:', error.message)
5203
+ * }
5204
+ * ```
5039
5205
  */
5206
+ declare function login(baseUrl: string, email: string, password: string): Promise<LoginResponse>;
5207
+
5040
5208
  /**
5041
- * Response type for the realtime token API
5209
+ * @fileoverview Authentication Strategy Pattern
5210
+ *
5211
+ * Provides different authentication strategies for the Playcademy SDK.
5212
+ * Each strategy knows how to add its authentication headers to requests.
5042
5213
  */
5043
- interface RealtimeTokenResponse {
5044
- token: string;
5045
- }
5046
5214
 
5047
5215
  /**
5048
- * Scores namespace types
5216
+ * Base interface for authentication strategies
5049
5217
  */
5050
- interface ScoreSubmission {
5051
- id: string;
5052
- score: number;
5053
- achievedAt: Date;
5218
+ interface AuthStrategy {
5219
+ /** Get the token value */
5220
+ getToken(): string | null;
5221
+ /** Get the token type */
5222
+ getType(): TokenType;
5223
+ /** Get authentication headers for a request */
5224
+ getHeaders(): Record<string, string>;
5054
5225
  }
5055
5226
 
5056
5227
  /**
5057
- * Users namespace types
5228
+ * Base Playcademy SDK client with shared infrastructure.
5229
+ * Provides authentication, HTTP requests, events, connection monitoring,
5230
+ * and fundamental namespaces used by all clients.
5231
+ *
5232
+ * Extended by PlaycademyClient (game SDK) and PlaycademyInternalClient (platform SDK).
5058
5233
  */
5059
- interface UserScore {
5060
- id: string;
5061
- score: number;
5062
- achievedAt: Date;
5063
- metadata?: Record<string, unknown>;
5064
- gameId: string;
5065
- gameTitle: string;
5066
- gameSlug: string;
5234
+ declare abstract class PlaycademyBaseClient {
5235
+ baseUrl: string;
5236
+ gameUrl?: string;
5237
+ protected authStrategy: AuthStrategy;
5238
+ protected gameId?: string;
5239
+ protected config: Partial<ClientConfig>;
5240
+ protected listeners: EventListeners;
5241
+ protected internalClientSessionId?: string;
5242
+ protected authContext?: {
5243
+ isInIframe: boolean;
5244
+ };
5245
+ protected initPayload?: InitPayload;
5246
+ protected connectionManager?: ConnectionManager;
5247
+ /**
5248
+ * Internal session manager for automatic session lifecycle.
5249
+ * @private
5250
+ * @internal
5251
+ */
5252
+ protected _sessionManager: {
5253
+ startSession: (gameId: string) => Promise<{
5254
+ sessionId: string;
5255
+ }>;
5256
+ endSession: (sessionId: string, gameId: string) => Promise<void>;
5257
+ };
5258
+ constructor(config?: Partial<ClientConfig>);
5259
+ /**
5260
+ * Gets the effective base URL for API requests.
5261
+ */
5262
+ getBaseUrl(): string;
5263
+ /**
5264
+ * Gets the effective game backend URL for integration requests.
5265
+ */
5266
+ protected getGameBackendUrl(): string;
5267
+ /**
5268
+ * Simple ping method for testing connectivity.
5269
+ */
5270
+ ping(): string;
5271
+ /**
5272
+ * Sets the authentication token for API requests.
5273
+ */
5274
+ setToken(token: string | null, tokenType?: TokenType): void;
5275
+ /**
5276
+ * Gets the current token type.
5277
+ */
5278
+ getTokenType(): TokenType;
5279
+ /**
5280
+ * Gets the current authentication token.
5281
+ */
5282
+ getToken(): string | null;
5283
+ /**
5284
+ * Checks if the client has a valid API token.
5285
+ */
5286
+ isAuthenticated(): boolean;
5287
+ /**
5288
+ * Registers a callback to be called when authentication state changes.
5289
+ */
5290
+ onAuthChange(callback: (token: string | null) => void): void;
5291
+ /**
5292
+ * Registers a callback to be called when connection issues are detected.
5293
+ */
5294
+ onDisconnect(callback: (context: DisconnectContext) => void | Promise<void>): () => void;
5295
+ /**
5296
+ * Gets the current connection state.
5297
+ */
5298
+ getConnectionState(): ConnectionState | 'unknown';
5299
+ /**
5300
+ * Manually triggers a connection check immediately.
5301
+ */
5302
+ checkConnection(): Promise<ConnectionState | 'unknown'>;
5303
+ /**
5304
+ * Sets the authentication context for the client.
5305
+ * @internal
5306
+ */
5307
+ _setAuthContext(context: {
5308
+ isInIframe: boolean;
5309
+ }): void;
5310
+ /**
5311
+ * Registers an event listener for client events.
5312
+ */
5313
+ on<E extends keyof ClientEvents>(event: E, callback: (payload: ClientEvents[E]) => void): void;
5314
+ /**
5315
+ * Emits an event to all registered listeners.
5316
+ */
5317
+ protected emit<E extends keyof ClientEvents>(event: E, payload: ClientEvents[E]): void;
5318
+ /**
5319
+ * Makes an authenticated HTTP request to the platform API.
5320
+ */
5321
+ protected request<T>(path: string, method: Method, options?: {
5322
+ body?: unknown;
5323
+ headers?: Record<string, string>;
5324
+ raw?: boolean;
5325
+ }): Promise<T>;
5326
+ /**
5327
+ * Makes an authenticated HTTP request to the game's backend Worker.
5328
+ */
5329
+ protected requestGameBackend<T>(path: string, method: Method, body?: unknown, headers?: Record<string, string>, raw?: boolean): Promise<T>;
5330
+ /**
5331
+ * Ensures a gameId is available, throwing an error if not.
5332
+ */
5333
+ protected _ensureGameId(): string;
5334
+ /**
5335
+ * Detects and sets the authentication context (iframe vs standalone).
5336
+ */
5337
+ private _detectAuthContext;
5338
+ /**
5339
+ * Initializes connection monitoring if enabled.
5340
+ */
5341
+ private _initializeConnectionMonitor;
5342
+ /**
5343
+ * Initializes an internal game session for automatic session management.
5344
+ */
5345
+ private _initializeInternalSession;
5346
+ /**
5347
+ * Current user data and inventory management.
5348
+ * - `me()` - Get authenticated user profile
5349
+ * - `inventory.get()` - List user's items
5350
+ * - `inventory.add(slug, qty)` - Award items to user
5351
+ */
5352
+ users: {
5353
+ me: () => Promise<AuthenticatedUser>;
5354
+ inventory: {
5355
+ get: () => Promise<InventoryItemWithItem[]>;
5356
+ add: (identifier: string, qty: number) => Promise<InventoryMutationResponse>;
5357
+ remove: (identifier: string, qty: number) => Promise<InventoryMutationResponse>;
5358
+ quantity: (identifier: string) => Promise<number>;
5359
+ has: (identifier: string, minQuantity?: number) => Promise<boolean>;
5360
+ };
5361
+ };
5067
5362
  }
5068
5363
 
5069
5364
  /**
5070
- * Authentication namespace types
5365
+ * Playcademy SDK client for game developers.
5366
+ * Provides namespaced access to platform features for games running inside Cademy.
5071
5367
  */
5072
-
5073
- type AuthProviderType = (typeof AUTH_PROVIDER_IDS)[keyof typeof AUTH_PROVIDER_IDS];
5074
- interface AuthOptions {
5075
- /** The identity provider to use for authentication */
5076
- provider: AuthProviderType;
5077
- /** The OAuth callback URL where your server handles the callback */
5078
- callbackUrl: string;
5079
- /** Authentication mode - auto detects best option based on context */
5080
- mode?: 'auto' | 'popup' | 'redirect';
5081
- /** Callback for authentication state changes */
5082
- onStateChange?: (state: AuthStateUpdate) => void;
5083
- /** Custom OAuth configuration (for users embedding the SDK outside of the Playcademy platform) */
5084
- oauth?: {
5085
- clientId: string;
5086
- authorizationEndpoint?: string;
5087
- tokenEndpoint?: string;
5088
- scope?: string;
5368
+ declare class PlaycademyClient extends PlaycademyBaseClient {
5369
+ /**
5370
+ * Connect external identity providers to the user's Playcademy account.
5371
+ * - `connect(provider)` - Link Discord, Google, etc. via OAuth popup
5372
+ */
5373
+ identity: {
5374
+ connect: (options: AuthOptions) => Promise<AuthResult>;
5375
+ _getContext: () => {
5376
+ isInIframe: boolean;
5377
+ };
5089
5378
  };
5090
5379
  /**
5091
- * Optional custom data to encode in OAuth state parameter.
5092
- * By default, the SDK automatically includes playcademy_user_id and game_id.
5093
- * Use this to add additional custom data if needed.
5380
+ * Game runtime lifecycle and asset loading.
5381
+ * - `exit()` - Return to Cademy hub
5382
+ * - `getGameToken()` - Get short-lived auth token
5383
+ * - `assets.url()`, `assets.json()`, `assets.fetch()` - Load game assets
5384
+ * - `on('pause')`, `on('resume')` - Handle visibility changes
5094
5385
  */
5095
- stateData?: Record<string, string>;
5096
- }
5097
- interface AuthStateUpdate {
5098
- /** Current status of the authentication flow */
5099
- status: 'opening_popup' | 'exchanging_token' | 'complete' | 'error';
5100
- /** Human-readable message about the current state */
5101
- message: string;
5102
- /** Error details if status is 'error' */
5103
- error?: Error;
5104
- }
5105
- interface AuthResult {
5106
- /** Whether authentication was successful */
5107
- success: boolean;
5108
- /** User information if authentication was successful */
5109
- user?: UserInfo;
5110
- /** Error if authentication failed */
5111
- error?: Error;
5112
- }
5113
- /**
5114
- * Better-auth sign-in response
5115
- */
5116
- interface BetterAuthSignInResponse {
5117
- token: string;
5118
- user: {
5119
- id: string;
5120
- email: string;
5386
+ runtime: {
5387
+ getGameToken: (gameId: string, options?: {
5388
+ apply?: boolean;
5389
+ }) => Promise<GameTokenResponse>;
5390
+ exit: () => Promise<void>;
5391
+ onInit: (handler: (context: GameContextPayload) => void) => void;
5392
+ onTokenRefresh: (handler: (data: {
5393
+ token: string;
5394
+ exp: number;
5395
+ }) => void) => void;
5396
+ onPause: (handler: () => void) => void;
5397
+ onResume: (handler: () => void) => void;
5398
+ onForceExit: (handler: () => void) => void;
5399
+ onOverlay: (handler: (isVisible: boolean) => void) => void;
5400
+ ready: () => void;
5401
+ sendTelemetry: (data: {
5402
+ fps: number;
5403
+ mem: number;
5404
+ }) => void;
5405
+ removeListener: (eventType: MessageEvents, handler: ((context: GameContextPayload) => void) | ((data: {
5406
+ token: string;
5407
+ exp: number;
5408
+ }) => void) | (() => void) | ((isVisible: boolean) => void)) => void;
5409
+ removeAllListeners: () => void;
5410
+ getListenerCounts: () => Record<string, number>;
5411
+ assets: {
5412
+ url(pathOrStrings: string | TemplateStringsArray, ...values: unknown[]): string;
5413
+ fetch: (path: string, options?: RequestInit) => Promise<Response>;
5414
+ json: <T = unknown>(path: string) => Promise<T>;
5415
+ blob: (path: string) => Promise<Blob>;
5416
+ text: (path: string) => Promise<string>;
5417
+ arrayBuffer: (path: string) => Promise<ArrayBuffer>;
5418
+ };
5121
5419
  };
5122
- expiresAt: string;
5123
- }
5124
- /**
5125
- * Better-auth API key creation response
5126
- */
5127
- interface BetterAuthApiKeyResponse {
5128
- apiKey: string;
5129
- key: {
5130
- id: string;
5131
- name: string | null;
5132
- expiresAt: string | null;
5133
- createdAt: string;
5420
+ /**
5421
+ * TimeBack integration for activity tracking and user context.
5422
+ *
5423
+ * User context (cached from init, refreshable):
5424
+ * - `user.role` - User's role (student, parent, teacher, etc.)
5425
+ * - `user.enrollments` - Courses the player is enrolled in for this game
5426
+ * - `user.organizations` - Schools/districts the player belongs to
5427
+ * - `user.fetch()` - Refresh user context from server
5428
+ *
5429
+ * Activity tracking:
5430
+ * - `startActivity(metadata)` - Begin tracking an activity
5431
+ * - `pauseActivity()` / `resumeActivity()` - Pause/resume timer
5432
+ * - `endActivity(scoreData)` - Submit activity results to TimeBack
5433
+ */
5434
+ timeback: {
5435
+ readonly user: TimebackUser;
5436
+ startActivity: (metadata: _playcademy_timeback_types.ActivityData) => void;
5437
+ pauseActivity: () => void;
5438
+ resumeActivity: () => void;
5439
+ endActivity: (data: _playcademy_timeback_types.EndActivityScoreData) => Promise<EndActivityResponse>;
5134
5440
  };
5135
- }
5136
- /**
5137
- * Better-auth API key list item
5138
- */
5139
- interface BetterAuthApiKey {
5140
- id: string;
5141
- name: string | null;
5142
- start: string;
5143
- enabled: boolean;
5144
- expiresAt: string | null;
5145
- createdAt: string;
5146
- updatedAt: string;
5147
- lastRequest: string | null;
5148
- requestCount: number;
5149
- }
5150
-
5151
- /**
5152
- * Character namespace types
5153
- */
5154
- interface CharacterComponentsOptions {
5155
5441
  /**
5156
- * Optional level filter for components
5157
- * When provided, only components available at this level or below are returned
5442
+ * Playcademy Credits (platform currency) management.
5443
+ * - `get()` - Get user's credit balance
5444
+ * - `add(amount)` - Award credits to user
5158
5445
  */
5159
- level?: number;
5446
+ credits: {
5447
+ balance: () => Promise<number>;
5448
+ add: (amount: number) => Promise<number>;
5449
+ spend: (amount: number) => Promise<number>;
5450
+ };
5160
5451
  /**
5161
- * Whether to bypass the cache and force a fresh API request
5162
- * Default: false (use cache when available)
5452
+ * Game score submission and leaderboards.
5453
+ * - `submit(gameId, score, metadata?)` - Record a game score
5163
5454
  */
5164
- skipCache?: boolean;
5165
- }
5166
- interface CreateCharacterData {
5167
- bodyComponentId: string;
5168
- eyesComponentId: string;
5169
- hairstyleComponentId: string;
5170
- outfitComponentId: string;
5171
- }
5172
- interface UpdateCharacterData {
5173
- bodyComponentId?: string;
5174
- eyesComponentId?: string;
5175
- hairstyleComponentId?: string;
5176
- outfitComponentId?: string;
5455
+ scores: {
5456
+ submit: (gameId: string, score: number, metadata?: Record<string, unknown>) => Promise<ScoreSubmission>;
5457
+ };
5458
+ /**
5459
+ * Realtime multiplayer authentication.
5460
+ * - `getToken()` - Get token for WebSocket/realtime connections
5461
+ */
5462
+ realtime: {
5463
+ token: {
5464
+ get: () => Promise<RealtimeTokenResponse>;
5465
+ };
5466
+ };
5467
+ /**
5468
+ * Make requests to your game's custom backend API routes.
5469
+ * - `get(path)`, `post(path, body)`, `put()`, `delete()` - HTTP methods
5470
+ * - Routes are relative to your game's deployment (e.g., '/hello' → your-game.playcademy.gg/api/hello)
5471
+ */
5472
+ backend: {
5473
+ get<T = unknown>(path: string, headers?: Record<string, string>): Promise<T>;
5474
+ post<T = unknown>(path: string, body?: unknown, headers?: Record<string, string>): Promise<T>;
5475
+ put<T = unknown>(path: string, body?: unknown, headers?: Record<string, string>): Promise<T>;
5476
+ patch<T = unknown>(path: string, body?: unknown, headers?: Record<string, string>): Promise<T>;
5477
+ delete<T = unknown>(path: string, headers?: Record<string, string>): Promise<T>;
5478
+ request<T = unknown>(path: string, method: Method, body?: unknown, headers?: Record<string, string>): Promise<T>;
5479
+ download(path: string, method?: Method, body?: unknown, headers?: Record<string, string>): Promise<Response>;
5480
+ url(pathOrStrings: string | TemplateStringsArray, ...values: unknown[]): string;
5481
+ };
5482
+ /** Auto-initializes a PlaycademyClient with context from the environment */
5483
+ static init: typeof init;
5484
+ /** Authenticates a user with email and password */
5485
+ static login: typeof login;
5486
+ /** Static identity utilities for OAuth operations */
5487
+ static identity: {
5488
+ parseOAuthState: typeof parseOAuthState;
5489
+ };
5177
5490
  }
5178
5491
 
5179
5492
  /**
5180
- * Developer namespace types
5493
+ * Type definitions for the game timeback namespace.
5494
+ *
5495
+ * Re-exports core types from @playcademy/data for SDK consumers,
5496
+ * plus SDK-specific types like TimebackInitContext.
5181
5497
  */
5498
+
5182
5499
  /**
5183
- * Bucket file metadata
5500
+ * A TimeBack enrollment for the current game session.
5501
+ * Alias for UserEnrollment without the optional gameId.
5184
5502
  */
5185
- interface BucketFile {
5186
- key: string;
5187
- size: number;
5188
- uploaded: string;
5189
- contentType?: string;
5190
- }
5191
- type DevUploadEvent = {
5192
- type: 'init';
5193
- } | {
5194
- type: 's3Progress';
5195
- loaded: number;
5196
- total: number;
5197
- percent: number;
5198
- } | {
5199
- type: 'finalizeStart';
5200
- } | {
5201
- type: 'finalizeProgress';
5202
- percent: number;
5203
- currentFileLabel?: string;
5204
- } | {
5205
- type: 'finalizeStatus';
5206
- message: string;
5207
- } | {
5208
- type: 'close';
5209
- };
5210
- type DevUploadHooks = {
5211
- onEvent?: (e: DevUploadEvent) => void;
5212
- onClose?: () => void;
5213
- };
5214
-
5503
+ type TimebackEnrollment = Omit<UserEnrollment, 'gameId'>;
5215
5504
  /**
5216
- * Platform TimeBack Types
5217
- *
5218
- * Types for TimeBack enrollment and student data on the platform side.
5219
- * These are used by the cademy hub to determine which games users have access to.
5505
+ * A TimeBack organization (school/district) for the current user.
5506
+ * Alias for UserOrganization.
5220
5507
  */
5221
-
5508
+ type TimebackOrganization = UserOrganization;
5222
5509
  /**
5223
- * Enrollment data for a student in a specific game/course.
5224
- * Represents the mapping between a TimeBack course and a Playcademy game.
5510
+ * TimeBack context passed during game initialization.
5511
+ * This is sent from the platform (cademy) to the game iframe via postMessage.
5225
5512
  */
5226
- interface StudentEnrollment {
5227
- /** The Playcademy game ID the student is enrolled in */
5228
- gameId: string;
5229
- /** The grade level for this enrollment */
5230
- grade: number;
5231
- /** The subject area (e.g., 'Math', 'Science') */
5232
- subject: string;
5233
- /** The TimeBack course ID */
5234
- courseId: string;
5513
+ interface TimebackInitContext {
5514
+ /** User's TimeBack ID */
5515
+ id: string;
5516
+ /** User's role in TimeBack (student, parent, teacher, etc.) */
5517
+ role: TimebackUserRole;
5518
+ /** User's enrollments for this game (one per grade/subject combo) */
5519
+ enrollments: TimebackEnrollment[];
5520
+ /** User's organizations (schools/districts) */
5521
+ organizations: TimebackOrganization[];
5235
5522
  }
5236
5523
  /**
5237
- * Response from the enrollments endpoint
5524
+ * TimeBack user context with current data (may be stale from init).
5525
+ * Use `fetch()` to get fresh data from the server.
5238
5526
  */
5239
- interface EnrollmentsResponse {
5240
- enrollments: StudentEnrollment[];
5527
+ interface TimebackUserContext {
5528
+ /** User's TimeBack ID */
5529
+ id: string | undefined;
5530
+ /** User's role in TimeBack (student, parent, teacher, etc.) */
5531
+ role: TimebackUserRole | undefined;
5532
+ /** User's enrollments for this game */
5533
+ enrollments: TimebackEnrollment[];
5534
+ /** User's organizations (schools/districts) */
5535
+ organizations: TimebackOrganization[];
5241
5536
  }
5242
5537
  /**
5243
- * Combined response type for xp.summary() method
5538
+ * TimeBack user object with both cached getters and fetch method.
5244
5539
  */
5245
- interface XpSummaryResponse {
5246
- today: TodayXpResponse;
5247
- total: TotalXpResponse;
5540
+ interface TimebackUser extends TimebackUserContext {
5541
+ /**
5542
+ * Fetch TimeBack data from the server (cached for 5 min).
5543
+ * Updates the cached values so subsequent property access returns fresh data.
5544
+ * @param options - Cache options (pass { force: true } to bypass cache)
5545
+ * @returns Promise resolving to fresh user context
5546
+ */
5547
+ fetch(options?: {
5548
+ force?: boolean;
5549
+ }): Promise<TimebackUserContext>;
5550
+ }
5551
+
5552
+ /**
5553
+ * Core client configuration and lifecycle types
5554
+ */
5555
+
5556
+ type TokenType = 'session' | 'apiKey' | 'gameJwt';
5557
+ interface ClientConfig {
5558
+ baseUrl: string;
5559
+ gameUrl?: string;
5560
+ token?: string;
5561
+ tokenType?: TokenType;
5562
+ gameId?: string;
5563
+ autoStartSession?: boolean;
5564
+ onDisconnect?: DisconnectHandler;
5565
+ enableConnectionMonitoring?: boolean;
5248
5566
  }
5249
-
5250
5567
  /**
5251
- * @fileoverview Server SDK Type Definitions
5252
- *
5253
- * TypeScript type definitions for the server-side Playcademy SDK.
5254
- * Includes configuration types, client state, and re-exported TimeBack types.
5568
+ * Handler called when connection state changes to offline or degraded.
5569
+ * Games can implement this to handle disconnects gracefully.
5255
5570
  */
5256
-
5571
+ type DisconnectHandler = (context: DisconnectContext) => void | Promise<void>;
5257
5572
  /**
5258
- * Base configuration for TimeBack integration (shared across all courses).
5259
- * References upstream TimeBack types from @playcademy/timeback.
5260
- *
5261
- * All fields are optional and support template variables: {grade}, {subject}, {gameSlug}
5573
+ * Context provided to disconnect handlers
5262
5574
  */
5263
- interface TimebackBaseConfig {
5264
- /** Organization configuration (shared across all courses) */
5265
- organization?: Partial<OrganizationConfig>;
5266
- /** Course defaults (can be overridden per-course) */
5267
- course?: Partial<CourseConfig>;
5268
- /** Component defaults */
5269
- component?: Partial<ComponentConfig>;
5270
- /** Resource defaults */
5271
- resource?: Partial<ResourceConfig>;
5272
- /** ComponentResource defaults */
5273
- componentResource?: Partial<ComponentResourceConfig>;
5575
+ interface DisconnectContext {
5576
+ /** Current connection state */
5577
+ state: 'offline' | 'degraded';
5578
+ /** Reason for the disconnect */
5579
+ reason: string;
5580
+ /** Timestamp when disconnect was detected */
5581
+ timestamp: number;
5582
+ /** Utility to display a platform-level alert */
5583
+ displayAlert: (message: string, options?: {
5584
+ type?: 'info' | 'warning' | 'error';
5585
+ duration?: number;
5586
+ }) => void;
5274
5587
  }
5275
- /**
5276
- * Extended course configuration that merges TimebackCourseConfig with per-course overrides.
5277
- * Used in playcademy.config.* to allow per-course customization.
5278
- */
5279
- interface TimebackCourseConfigWithOverrides extends TimebackCourseConfig {
5280
- title?: string;
5281
- courseCode?: string;
5282
- level?: string;
5283
- metadata?: CourseConfig['metadata'];
5284
- totalXp?: number | null;
5285
- masterableUnits?: number | null;
5588
+ interface InitPayload {
5589
+ /** Hub API base URL */
5590
+ baseUrl: string;
5591
+ /** Game deployment URL (serves both frontend assets and backend API) */
5592
+ gameUrl?: string;
5593
+ /** Short-lived game token */
5594
+ token: string;
5595
+ /** Game ID */
5596
+ gameId: string;
5597
+ /** Realtime WebSocket URL */
5598
+ realtimeUrl?: string;
5599
+ /** Timeback context (if user has a Timeback account) */
5600
+ timeback?: TimebackInitContext;
5286
5601
  }
5287
5602
  /**
5288
- * TimeBack integration configuration for Playcademy config file.
5289
- *
5290
- * Supports two levels of customization:
5291
- * 1. `base`: Shared defaults for all courses (organization, course, component, resource, componentResource)
5292
- * 2. Per-course overrides in the `courses` array (title, courseCode, level, gradingScheme, metadata)
5293
- *
5294
- * Template variables ({grade}, {subject}, {gameSlug}) can be used in string fields.
5603
+ * Simplified user data passed to games via InitPayload
5604
+ * This is a subset of AuthenticatedUser suitable for external game consumption
5295
5605
  */
5296
- interface TimebackIntegrationConfig {
5297
- /** Multi-grade course configuration (array of grade/subject/totalXp with optional per-course overrides) */
5298
- courses: TimebackCourseConfigWithOverrides[];
5299
- /** Optional base configuration (shared across all courses, can be overridden per-course) */
5300
- base?: TimebackBaseConfig;
5606
+ interface GameUser {
5607
+ /** Playcademy user ID */
5608
+ id: string;
5609
+ /** Unique username */
5610
+ username: string | null;
5611
+ /** Display name */
5612
+ name: string | null;
5613
+ /** Email address */
5614
+ email: string | null;
5615
+ /** Profile image URL */
5616
+ image: string | null;
5617
+ /** Whether the user has a Timeback account */
5618
+ hasTimebackAccount: boolean;
5301
5619
  }
5302
- /**
5303
- * Custom API routes integration
5304
- */
5305
- interface CustomRoutesIntegration {
5306
- /** Directory for custom API routes (defaults to 'server/api') */
5307
- directory?: string;
5620
+ type GameContextPayload = {
5621
+ token: string;
5622
+ baseUrl: string;
5623
+ realtimeUrl: string;
5624
+ gameId: string;
5625
+ forwardKeys?: string[];
5626
+ };
5627
+ type EventListeners = {
5628
+ [E in keyof ClientEvents]?: Array<(payload: ClientEvents[E]) => void>;
5629
+ };
5630
+ interface ClientEvents {
5631
+ authChange: {
5632
+ token: string | null;
5633
+ };
5634
+ inventoryChange: {
5635
+ itemId: string;
5636
+ delta: number;
5637
+ newTotal: number;
5638
+ };
5639
+ levelUp: {
5640
+ oldLevel: number;
5641
+ newLevel: number;
5642
+ creditsAwarded: number;
5643
+ };
5644
+ xpGained: {
5645
+ amount: number;
5646
+ totalXP: number;
5647
+ leveledUp: boolean;
5648
+ };
5649
+ connectionChange: {
5650
+ state: 'online' | 'offline' | 'degraded';
5651
+ reason: string;
5652
+ };
5308
5653
  }
5654
+
5309
5655
  /**
5310
- * Database integration
5656
+ * Event and message payload types for SDK messaging system
5311
5657
  */
5312
- interface DatabaseIntegration {
5313
- /** Database directory (defaults to 'db') */
5314
- directory?: string;
5658
+
5659
+ interface DisplayAlertPayload {
5660
+ message: string;
5661
+ options?: {
5662
+ type?: 'info' | 'warning' | 'error';
5663
+ duration?: number;
5664
+ };
5315
5665
  }
5316
5666
  /**
5317
- * Integrations configuration
5318
- * All backend features (database, custom routes, external services) are configured here
5667
+ * Authentication state change event payload.
5668
+ * Used when authentication state changes in the application.
5319
5669
  */
5320
- interface IntegrationsConfig {
5321
- /** TimeBack integration (optional) */
5322
- timeback?: TimebackIntegrationConfig | null;
5323
- /** Custom API routes (optional) */
5324
- customRoutes?: CustomRoutesIntegration | boolean;
5325
- /** Database (optional) */
5326
- database?: DatabaseIntegration | boolean;
5327
- /** Key-Value storage (optional) */
5328
- kv?: boolean;
5329
- /** Bucket storage (optional) */
5330
- bucket?: boolean;
5331
- /** Authentication (optional) */
5332
- auth?: boolean;
5670
+ interface AuthStateChangePayload {
5671
+ /** Whether the user is currently authenticated */
5672
+ authenticated: boolean;
5673
+ /** User information if authenticated, null otherwise */
5674
+ user: UserInfo | null;
5675
+ /** Error information if authentication failed */
5676
+ error: Error | null;
5333
5677
  }
5334
5678
  /**
5335
- * Unified Playcademy configuration
5336
- * Used for playcademy.config.{js,json}
5679
+ * OAuth callback event payload.
5680
+ * Used when OAuth flow completes in popup/new-tab windows.
5337
5681
  */
5338
- interface PlaycademyConfig {
5339
- /** Game name */
5340
- name: string;
5341
- /** Game description */
5342
- description?: string;
5343
- /** Game emoji icon */
5344
- emoji?: string;
5345
- /** Build command to run before deployment */
5346
- buildCommand?: string[];
5347
- /** Path to build output */
5348
- buildPath?: string;
5349
- /** Game type */
5350
- gameType?: 'hosted' | 'external';
5351
- /** External URL (for external games) */
5352
- externalUrl?: string;
5353
- /** Game platform */
5354
- platform?: 'web' | 'unity' | 'godot';
5355
- /** Integrations (database, custom routes, external services) */
5356
- integrations?: IntegrationsConfig;
5682
+ interface AuthCallbackPayload {
5683
+ /** OAuth authorization code */
5684
+ code: string;
5685
+ /** OAuth state parameter for CSRF protection */
5686
+ state: string;
5687
+ /** Error message if OAuth flow failed */
5688
+ error: string | null;
5357
5689
  }
5358
-
5359
5690
  /**
5360
- * Configuration options for initializing a PlaycademyClient instance.
5361
- *
5362
- * @example
5363
- * ```typescript
5364
- * const config: PlaycademyServerClientConfig = {
5365
- * apiKey: process.env.PLAYCADEMY_API_KEY!,
5366
- * gameId: 'my-math-game',
5367
- * configPath: './playcademy.config.js'
5368
- * }
5369
- * ```
5691
+ * Message sent from server callback to opener window.
5692
+ * This is the standardized format from our server-first architecture.
5370
5693
  */
5371
- interface PlaycademyServerClientConfig {
5372
- /**
5373
- * Playcademy API key for server-to-server authentication.
5374
- * Obtain from the Playcademy developer dashboard.
5375
- */
5376
- apiKey: string;
5377
- /**
5378
- * Optional path to playcademy.config.js file.
5379
- * If not provided, searches current directory and up to 3 parent directories.
5380
- * Ignored if `config` is provided directly.
5381
- *
5382
- * @example './config/playcademy.config.js'
5383
- */
5384
- configPath?: string;
5385
- /**
5386
- * Optional config object (for edge environments without filesystem).
5387
- * If provided, skips filesystem-based config loading.
5388
- *
5389
- * @example { name: 'My Game', integrations: { timeback: {...} } }
5390
- */
5391
- config?: PlaycademyConfig;
5392
- /**
5393
- * Optional base URL for Playcademy API.
5394
- * Defaults to environment variables or 'https://hub.playcademy.net'.
5395
- *
5396
- * @example 'http://localhost:3000' for local development
5397
- */
5398
- baseUrl?: string;
5399
- /**
5400
- * Optional game ID.
5401
- * If not provided, will attempt to fetch from API using the API token.
5402
- *
5403
- * @example 'my-math-game'
5404
- */
5405
- gameId?: string;
5694
+ interface AuthServerMessage {
5695
+ /** Type of the message */
5696
+ type: 'PLAYCADEMY_AUTH_STATE_CHANGE';
5697
+ /** Whether the user is currently authenticated */
5698
+ authenticated: boolean;
5699
+ /** Whether the authentication was successful */
5700
+ success: boolean;
5701
+ /** Timestamp of the message */
5702
+ ts: number;
5703
+ /** User information if authentication was successful */
5704
+ user?: UserInfo;
5705
+ /** Error message if authentication failed */
5706
+ error?: string;
5707
+ }
5708
+ /**
5709
+ * Token refresh event payload.
5710
+ * Sent when authentication token is updated.
5711
+ */
5712
+ interface TokenRefreshPayload {
5713
+ /** New authentication token */
5714
+ token: string;
5715
+ /** Token expiration timestamp */
5716
+ exp: number;
5406
5717
  }
5407
5718
  /**
5408
- * Internal state maintained by the PlaycademyClient instance.
5409
- *
5410
- * @internal
5719
+ * Telemetry event payload.
5720
+ * Performance metrics sent from the game.
5411
5721
  */
5412
- interface PlaycademyServerClientState {
5413
- /** API key for authentication */
5414
- apiKey: string;
5415
- /** Base URL for API requests */
5416
- baseUrl: string;
5417
- /** Game identifier */
5418
- gameId: string;
5419
- /** Loaded game configuration from playcademy.config.js */
5420
- config: PlaycademyConfig;
5421
- /**
5422
- * TimeBack course ID fetched from the Playcademy API.
5423
- * Used for all TimeBack event recording.
5424
- */
5425
- courseId?: string;
5722
+ interface TelemetryPayload {
5723
+ /** Frames per second */
5724
+ fps: number;
5725
+ /** Memory usage in MB */
5726
+ mem: number;
5426
5727
  }
5427
-
5428
5728
  /**
5429
- * Resource bindings for backend deployment
5430
- * Provider-agnostic abstraction for cloud resources
5729
+ * Keyboard event payload.
5730
+ * Key events forwarded from the game.
5431
5731
  */
5432
- interface BackendResourceBindings {
5433
- /** SQL database instances to create and bind (maps to D1 on Cloudflare) */
5434
- database?: string[];
5435
- /** Key-value store namespaces to create and bind (maps to KV on Cloudflare) */
5436
- keyValue?: string[];
5437
- /** Object storage buckets to bind (maps to R2 on Cloudflare) */
5438
- bucket?: string[];
5732
+ interface KeyEventPayload {
5733
+ /** Key value (e.g., 'Escape', 'F1') */
5734
+ key: string;
5735
+ /** Key code (optional) */
5736
+ code?: string;
5737
+ /** Event type */
5738
+ type: 'keydown' | 'keyup';
5439
5739
  }
5440
5740
  /**
5441
- * Backend deployment bundle for uploading to Playcademy platform
5741
+ * Connection state payload.
5742
+ * Broadcast from platform to games when connection changes.
5442
5743
  */
5443
- interface BackendDeploymentBundle {
5444
- /** Bundled JavaScript code ready for deployment */
5445
- code: string;
5446
- /** Game configuration */
5447
- config: PlaycademyConfig;
5448
- /** Optional resource bindings (database, storage, etc.) */
5449
- bindings?: BackendResourceBindings;
5450
- /** Optional schema information for database setup */
5451
- schema?: SchemaInfo;
5452
- /** Optional game secrets */
5453
- secrets?: Record<string, string>;
5744
+ interface ConnectionStatePayload {
5745
+ state: 'online' | 'offline' | 'degraded';
5746
+ reason: string;
5454
5747
  }
5455
5748
 
5456
5749
  /**
5457
- * Connection Manager
5458
- *
5459
- * Manages connection monitoring and integrates it with the Playcademy client.
5460
- * Handles event wiring, state management, and disconnect callbacks.
5461
- *
5462
- * In iframe mode, disables local monitoring and listens to platform connection
5463
- * state broadcasts instead (avoids duplicate heartbeats).
5750
+ * SDK-specific API response types
5464
5751
  */
5752
+ type LoginResponse = {
5753
+ token: string;
5754
+ };
5755
+ type GameTokenResponse = {
5756
+ token: string;
5757
+ exp: number;
5758
+ };
5759
+ type StartSessionResponse = {
5760
+ sessionId: string;
5761
+ };
5762
+ type InventoryMutationResponse = {
5763
+ newTotal: number;
5764
+ };
5465
5765
 
5466
5766
  /**
5467
- * Configuration for the ConnectionManager.
5767
+ * Realtime namespace types
5468
5768
  */
5469
- interface ConnectionManagerConfig {
5470
- /** Base URL for API requests (used for heartbeat pings) */
5471
- baseUrl: string;
5472
- /** Authentication context (iframe vs standalone) for alert routing */
5473
- authContext?: {
5474
- isInIframe: boolean;
5475
- };
5476
- /** Handler to call when connection issues are detected */
5477
- onDisconnect?: DisconnectHandler;
5478
- /** Callback to emit connection change events to the client */
5479
- onConnectionChange?: (state: ConnectionState, reason: string) => void;
5769
+ /**
5770
+ * Response type for the realtime token API
5771
+ */
5772
+ interface RealtimeTokenResponse {
5773
+ token: string;
5480
5774
  }
5775
+
5481
5776
  /**
5482
- * Manages connection monitoring for the Playcademy client.
5483
- *
5484
- * The ConnectionManager serves as an integration layer between the low-level
5485
- * ConnectionMonitor and the PlaycademyClient. It handles:
5486
- * - Event wiring and coordination
5487
- * - Disconnect callbacks with context
5488
- * - Platform-level alert integration
5489
- * - Request success/failure tracking
5490
- *
5491
- * This class is used internally by PlaycademyClient and typically not
5492
- * instantiated directly by game developers.
5493
- *
5494
- * @see {@link ConnectionMonitor} for the underlying monitoring implementation
5495
- * @see {@link PlaycademyClient.onDisconnect} for the public API
5777
+ * Scores namespace types
5496
5778
  */
5497
- declare class ConnectionManager {
5498
- private monitor?;
5499
- private authContext?;
5500
- private disconnectHandler?;
5501
- private connectionChangeCallback?;
5502
- private currentState;
5503
- private additionalDisconnectHandlers;
5504
- /**
5505
- * Creates a new ConnectionManager instance.
5506
- *
5507
- * @param config - Configuration options for the manager
5508
- *
5509
- * @example
5510
- * ```typescript
5511
- * const manager = new ConnectionManager({
5512
- * baseUrl: 'https://api.playcademy.com',
5513
- * authContext: { isInIframe: false },
5514
- * onDisconnect: (context) => {
5515
- * console.log(`Disconnected: ${context.state}`)
5516
- * },
5517
- * onConnectionChange: (state, reason) => {
5518
- * console.log(`Connection changed: ${state}`)
5519
- * }
5520
- * })
5521
- * ```
5522
- */
5523
- constructor(config: ConnectionManagerConfig);
5524
- /**
5525
- * Gets the current connection state.
5526
- *
5527
- * @returns The current connection state ('online', 'offline', or 'degraded')
5528
- *
5529
- * @example
5530
- * ```typescript
5531
- * const state = manager.getState()
5532
- * if (state === 'offline') {
5533
- * console.log('No connection')
5534
- * }
5535
- * ```
5536
- */
5537
- getState(): ConnectionState;
5538
- /**
5539
- * Manually triggers a connection check immediately.
5540
- *
5541
- * Forces a heartbeat ping to verify the current connection status.
5542
- * Useful when you need to check connectivity before a critical operation.
5543
- *
5544
- * In iframe mode, this returns the last known state from platform.
5545
- *
5546
- * @returns Promise resolving to the current connection state
5547
- *
5548
- * @example
5549
- * ```typescript
5550
- * const state = await manager.checkNow()
5551
- * if (state === 'online') {
5552
- * await performCriticalOperation()
5553
- * }
5554
- * ```
5555
- */
5556
- checkNow(): Promise<ConnectionState>;
5557
- /**
5558
- * Reports a successful API request to the connection monitor.
5559
- *
5560
- * This resets the consecutive failure counter and transitions from
5561
- * 'degraded' to 'online' state if applicable.
5562
- *
5563
- * Typically called automatically by the SDK's request wrapper.
5564
- * No-op in iframe mode (platform handles monitoring).
5565
- */
5566
- reportRequestSuccess(): void;
5567
- /**
5568
- * Reports a failed API request to the connection monitor.
5569
- *
5570
- * Only network errors are tracked (not 4xx/5xx HTTP responses).
5571
- * After consecutive failures exceed the threshold, the state transitions
5572
- * to 'degraded' or 'offline'.
5573
- *
5574
- * Typically called automatically by the SDK's request wrapper.
5575
- * No-op in iframe mode (platform handles monitoring).
5576
- *
5577
- * @param error - The error from the failed request
5578
- */
5579
- reportRequestFailure(error: unknown): void;
5580
- /**
5581
- * Registers a callback to be called when connection issues are detected.
5582
- *
5583
- * The callback only fires for 'offline' and 'degraded' states, not when
5584
- * recovering to 'online'. This provides a clean API for games to handle
5585
- * disconnect scenarios without being notified of every state change.
5586
- *
5587
- * Works in both iframe and standalone modes transparently.
5588
- *
5589
- * @param callback - Function to call when connection degrades
5590
- * @returns Cleanup function to unregister the callback
5591
- *
5592
- * @example
5593
- * ```typescript
5594
- * const cleanup = manager.onDisconnect(({ state, reason, displayAlert }) => {
5595
- * if (state === 'offline') {
5596
- * displayAlert?.('Connection lost. Saving your progress...', { type: 'error' })
5597
- * saveGameState()
5598
- * }
5599
- * })
5600
- *
5601
- * // Later: cleanup() to unregister
5602
- * ```
5603
- */
5604
- onDisconnect(callback: DisconnectHandler): () => void;
5605
- /**
5606
- * Stops connection monitoring and performs cleanup.
5607
- *
5608
- * Removes event listeners and clears heartbeat intervals.
5609
- * Should be called when the client is being destroyed.
5610
- */
5611
- stop(): void;
5612
- /**
5613
- * Sets up listener for platform connection state broadcasts (iframe mode only).
5614
- */
5615
- private _setupPlatformListener;
5616
- /**
5617
- * Handles connection state changes from the monitor or platform.
5618
- *
5619
- * Coordinates between:
5620
- * 1. Emitting events to the client (for client.on('connectionChange'))
5621
- * 2. Calling the disconnect handler if configured
5622
- * 3. Calling any additional handlers registered via onDisconnect()
5623
- *
5624
- * @param state - The new connection state
5625
- * @param reason - Human-readable reason for the state change
5626
- */
5627
- private _handleConnectionChange;
5779
+ interface ScoreSubmission {
5780
+ id: string;
5781
+ score: number;
5782
+ achievedAt: Date;
5628
5783
  }
5629
5784
 
5630
5785
  /**
5631
- * @fileoverview Playcademy Messaging System
5632
- *
5633
- * This file implements a unified messaging system for the Playcademy platform that handles
5634
- * communication between different contexts:
5635
- *
5636
- * 1. **Iframe-to-Parent Communication**: When games run inside iframes (production/development),
5637
- * they need to communicate with the parent window using postMessage API
5638
- *
5639
- * 2. **Local Communication**: When games run in the same context (local development),
5640
- * they use CustomEvents for internal messaging
5641
- *
5642
- * The system automatically detects the runtime environment and chooses the appropriate
5643
- * transport method, abstracting this complexity from the developer.
5644
- *
5645
- * **Architecture Overview**:
5646
- * - Games run in iframes for security and isolation
5647
- * - Parent window (Playcademy shell) manages game lifecycle
5648
- * - Messages flow bidirectionally between parent and iframe
5649
- * - Local development mode simulates this architecture without iframes
5786
+ * Users namespace types
5650
5787
  */
5788
+ interface UserScore {
5789
+ id: string;
5790
+ score: number;
5791
+ achievedAt: Date;
5792
+ metadata?: Record<string, unknown>;
5793
+ gameId: string;
5794
+ gameTitle: string;
5795
+ gameSlug: string;
5796
+ }
5651
5797
 
5652
5798
  /**
5653
- * Enumeration of all message types used in the Playcademy messaging system.
5654
- *
5655
- * **Message Flow Patterns**:
5656
- *
5657
- * **Parent → Game (Overworld → Game)**:
5658
- * - INIT: Provides game with authentication token and configuration
5659
- * - TOKEN_REFRESH: Updates game's authentication token before expiry
5660
- * - PAUSE/RESUME: Controls game execution state
5661
- * - FORCE_EXIT: Immediately terminates the game
5662
- * - OVERLAY: Shows/hides UI overlays over the game
5663
- *
5664
- * **Game → Parent (Game → Overworld)**:
5665
- * - READY: Game has loaded and is ready to receive messages
5666
- * - EXIT: Game requests to be closed (user clicked exit, game ended, etc.)
5667
- * - TELEMETRY: Game reports performance metrics (FPS, memory usage, etc.)
5799
+ * Authentication namespace types
5668
5800
  */
5669
- declare enum MessageEvents {
5670
- /**
5671
- * Initializes the game with authentication context and configuration.
5672
- * Sent immediately after game iframe loads.
5673
- * Payload:
5674
- * - `baseUrl`: string
5675
- * - `token`: string
5676
- * - `gameId`: string
5677
- */
5678
- INIT = "PLAYCADEMY_INIT",
5679
- /**
5680
- * Updates the game's authentication token before it expires.
5681
- * Sent periodically to maintain valid authentication.
5682
- * Payload:
5683
- * - `token`: string
5684
- * - `exp`: number
5685
- */
5686
- TOKEN_REFRESH = "PLAYCADEMY_TOKEN_REFRESH",
5687
- /**
5688
- * Pauses game execution (e.g., when user switches tabs).
5689
- * Game should pause timers, animations, and user input.
5690
- * Payload: void
5691
- */
5692
- PAUSE = "PLAYCADEMY_PAUSE",
5693
- /**
5694
- * Resumes game execution after being paused.
5695
- * Game should restore timers, animations, and user input.
5696
- * Payload: void
5697
- */
5698
- RESUME = "PLAYCADEMY_RESUME",
5699
- /**
5700
- * Forces immediate game termination (emergency exit).
5701
- * Game should clean up resources and exit immediately.
5702
- * Payload: void
5703
- */
5704
- FORCE_EXIT = "PLAYCADEMY_FORCE_EXIT",
5705
- /**
5706
- * Shows or hides UI overlays over the game.
5707
- * Game may need to pause or adjust rendering accordingly.
5708
- * Payload: boolean (true = show overlay, false = hide overlay)
5709
- */
5710
- OVERLAY = "PLAYCADEMY_OVERLAY",
5711
- /**
5712
- * Broadcasts connection state changes to games.
5713
- * Sent by platform when network connectivity changes.
5714
- * Payload:
5715
- * - `state`: 'online' | 'offline' | 'degraded'
5716
- * - `reason`: string
5717
- */
5718
- CONNECTION_STATE = "PLAYCADEMY_CONNECTION_STATE",
5719
- /**
5720
- * Game has finished loading and is ready to receive messages.
5721
- * Sent once after game initialization is complete.
5722
- * Payload: void
5723
- */
5724
- READY = "PLAYCADEMY_READY",
5725
- /**
5726
- * Game requests to be closed/exited.
5727
- * Sent when user clicks exit button or game naturally ends.
5728
- * Payload: void
5729
- */
5730
- EXIT = "PLAYCADEMY_EXIT",
5731
- /**
5732
- * Game reports performance telemetry data.
5733
- * Sent periodically for monitoring and analytics.
5734
- * Payload:
5735
- * - `fps`: number
5736
- * - `mem`: number
5737
- */
5738
- TELEMETRY = "PLAYCADEMY_TELEMETRY",
5801
+
5802
+ type AuthProviderType = (typeof AUTH_PROVIDER_IDS)[keyof typeof AUTH_PROVIDER_IDS];
5803
+ interface AuthOptions {
5804
+ /** The identity provider to use for authentication */
5805
+ provider: AuthProviderType;
5806
+ /** The OAuth callback URL where your server handles the callback */
5807
+ callbackUrl: string;
5808
+ /** Authentication mode - auto detects best option based on context */
5809
+ mode?: 'auto' | 'popup' | 'redirect';
5810
+ /** Callback for authentication state changes */
5811
+ onStateChange?: (state: AuthStateUpdate) => void;
5812
+ /** Custom OAuth configuration (for users embedding the SDK outside of the Playcademy platform) */
5813
+ oauth?: {
5814
+ clientId: string;
5815
+ authorizationEndpoint?: string;
5816
+ tokenEndpoint?: string;
5817
+ scope?: string;
5818
+ };
5739
5819
  /**
5740
- * Game reports key events to parent.
5741
- * Sent when certain keys are pressed within the game iframe.
5742
- * Payload:
5743
- * - `key`: string
5744
- * - `code?`: string
5745
- * - `type`: 'keydown' | 'keyup'
5820
+ * Optional custom data to encode in OAuth state parameter.
5821
+ * By default, the SDK automatically includes playcademy_user_id and game_id.
5822
+ * Use this to add additional custom data if needed.
5746
5823
  */
5747
- KEY_EVENT = "PLAYCADEMY_KEY_EVENT",
5824
+ stateData?: Record<string, string>;
5825
+ }
5826
+ interface AuthStateUpdate {
5827
+ /** Current status of the authentication flow */
5828
+ status: 'opening_popup' | 'exchanging_token' | 'complete' | 'error';
5829
+ /** Human-readable message about the current state */
5830
+ message: string;
5831
+ /** Error details if status is 'error' */
5832
+ error?: Error;
5833
+ }
5834
+ interface AuthResult {
5835
+ /** Whether authentication was successful */
5836
+ success: boolean;
5837
+ /** User information if authentication was successful */
5838
+ user?: UserInfo;
5839
+ /** Error if authentication failed */
5840
+ error?: Error;
5841
+ }
5842
+ /**
5843
+ * Better-auth sign-in response
5844
+ */
5845
+ interface BetterAuthSignInResponse {
5846
+ token: string;
5847
+ user: {
5848
+ id: string;
5849
+ email: string;
5850
+ };
5851
+ expiresAt: string;
5852
+ }
5853
+ /**
5854
+ * Better-auth API key creation response
5855
+ */
5856
+ interface BetterAuthApiKeyResponse {
5857
+ apiKey: string;
5858
+ key: {
5859
+ id: string;
5860
+ name: string | null;
5861
+ expiresAt: string | null;
5862
+ createdAt: string;
5863
+ };
5864
+ }
5865
+ /**
5866
+ * Better-auth API key list item
5867
+ */
5868
+ interface BetterAuthApiKey {
5869
+ id: string;
5870
+ name: string | null;
5871
+ start: string;
5872
+ enabled: boolean;
5873
+ expiresAt: string | null;
5874
+ createdAt: string;
5875
+ updatedAt: string;
5876
+ lastRequest: string | null;
5877
+ requestCount: number;
5878
+ }
5879
+
5880
+ /**
5881
+ * Character namespace types
5882
+ */
5883
+ interface CharacterComponentsOptions {
5748
5884
  /**
5749
- * Game requests platform to display an alert.
5750
- * Sent when connection issues are detected or other important events occur.
5751
- * Payload:
5752
- * - `message`: string
5753
- * - `options`: `{ type?: 'info' | 'warning' | 'error', duration?: number }`
5885
+ * Optional level filter for components
5886
+ * When provided, only components available at this level or below are returned
5754
5887
  */
5755
- DISPLAY_ALERT = "PLAYCADEMY_DISPLAY_ALERT",
5888
+ level?: number;
5756
5889
  /**
5757
- * Notifies about authentication state changes.
5758
- * Can be sent in both directions depending on auth flow.
5759
- * Payload:
5760
- * - `authenticated`: boolean
5761
- * - `user`: UserInfo | null
5762
- * - `error`: Error | null
5890
+ * Whether to bypass the cache and force a fresh API request
5891
+ * Default: false (use cache when available)
5763
5892
  */
5764
- AUTH_STATE_CHANGE = "PLAYCADEMY_AUTH_STATE_CHANGE",
5893
+ skipCache?: boolean;
5894
+ }
5895
+ interface CreateCharacterData {
5896
+ bodyComponentId: string;
5897
+ eyesComponentId: string;
5898
+ hairstyleComponentId: string;
5899
+ outfitComponentId: string;
5900
+ }
5901
+ interface UpdateCharacterData {
5902
+ bodyComponentId?: string;
5903
+ eyesComponentId?: string;
5904
+ hairstyleComponentId?: string;
5905
+ outfitComponentId?: string;
5906
+ }
5907
+
5908
+ /**
5909
+ * Developer namespace types
5910
+ */
5911
+ /**
5912
+ * Bucket file metadata
5913
+ */
5914
+ interface BucketFile {
5915
+ key: string;
5916
+ size: number;
5917
+ uploaded: string;
5918
+ contentType?: string;
5919
+ }
5920
+ type DevUploadEvent = {
5921
+ type: 'init';
5922
+ } | {
5923
+ type: 's3Progress';
5924
+ loaded: number;
5925
+ total: number;
5926
+ percent: number;
5927
+ } | {
5928
+ type: 'finalizeStart';
5929
+ } | {
5930
+ type: 'finalizeProgress';
5931
+ percent: number;
5932
+ currentFileLabel?: string;
5933
+ } | {
5934
+ type: 'finalizeStatus';
5935
+ message: string;
5936
+ } | {
5937
+ type: 'close';
5938
+ };
5939
+ type DevUploadHooks = {
5940
+ onEvent?: (e: DevUploadEvent) => void;
5941
+ onClose?: () => void;
5942
+ };
5943
+
5944
+ /**
5945
+ * Platform TimeBack Types
5946
+ *
5947
+ * Types for TimeBack data on the platform side.
5948
+ * Unlike game types, these include gameId for cross-game enrollment views.
5949
+ */
5950
+
5951
+ /**
5952
+ * Platform TimeBack user context.
5953
+ * Unlike game context, enrollments include gameId for cross-game views.
5954
+ */
5955
+ interface PlatformTimebackUserContext {
5956
+ /** User's TimeBack ID */
5957
+ id: string | undefined;
5958
+ /** User's role in TimeBack (student, parent, teacher, etc.) */
5959
+ role: TimebackUserRole | undefined;
5960
+ /** User's enrollments across ALL games (includes gameId) */
5961
+ enrollments: UserEnrollment[];
5962
+ /** User's organizations (schools/districts) */
5963
+ organizations: UserOrganization[];
5964
+ }
5965
+ /**
5966
+ * Platform TimeBack user object with both cached getters and fetch method.
5967
+ */
5968
+ interface PlatformTimebackUser extends PlatformTimebackUserContext {
5765
5969
  /**
5766
- * OAuth callback data from popup/new-tab windows.
5767
- * Sent from popup window back to parent after OAuth completes.
5768
- * Payload:
5769
- * - `code`: string (OAuth authorization code)
5770
- * - `state`: string (OAuth state for CSRF protection)
5771
- * - `error`: string | null (OAuth error if any)
5970
+ * Fetch TimeBack data from the server (cached for 5 min).
5971
+ * Updates the cached values so subsequent property access returns fresh data.
5972
+ * @param options - Cache options (pass { force: true } to bypass cache)
5973
+ * @returns Promise resolving to user context with full enrollment data
5772
5974
  */
5773
- AUTH_CALLBACK = "PLAYCADEMY_AUTH_CALLBACK"
5975
+ fetch(options?: {
5976
+ force?: boolean;
5977
+ }): Promise<PlatformTimebackUserContext>;
5774
5978
  }
5775
5979
  /**
5776
- * Type definition for message handler functions.
5777
- * These functions are called when a specific message type is received.
5778
- *
5779
- * @template T - The type of payload data the handler expects
5780
- * @param payload - The data sent with the message
5980
+ * Combined response type for xp.summary() method
5781
5981
  */
5782
- type MessageHandler<T = unknown> = (payload: T) => void;
5982
+ interface XpSummaryResponse {
5983
+ today: TodayXpResponse;
5984
+ total: TotalXpResponse;
5985
+ }
5986
+
5783
5987
  /**
5784
- * Type mapping that defines the payload structure for each message type.
5785
- * This ensures type safety when sending and receiving messages.
5786
- *
5787
- * **Usage Examples**:
5788
- * ```typescript
5789
- * // Type-safe message sending
5790
- * messaging.send(MessageEvents.INIT, { baseUrl: '/api', token: 'abc', gameId: '123' })
5988
+ * @fileoverview Server SDK Type Definitions
5791
5989
  *
5792
- * // Type-safe message handling
5793
- * messaging.listen(MessageEvents.TOKEN_REFRESH, ({ token, exp }) => {
5794
- * console.log(`New token expires at: ${new Date(exp)}`)
5795
- * })
5796
- * ```
5990
+ * TypeScript type definitions for the server-side Playcademy SDK.
5991
+ * Includes configuration types, client state, and re-exported TimeBack types.
5797
5992
  */
5798
- type MessageEventMap = {
5799
- /** Game initialization context with API endpoint, auth token, and game ID */
5800
- [MessageEvents.INIT]: GameContextPayload;
5801
- /** Token refresh data with new token and expiration timestamp */
5802
- [MessageEvents.TOKEN_REFRESH]: TokenRefreshPayload;
5803
- /** Pause message has no payload data */
5804
- [MessageEvents.PAUSE]: void;
5805
- /** Resume message has no payload data */
5806
- [MessageEvents.RESUME]: void;
5807
- /** Force exit message has no payload data */
5808
- [MessageEvents.FORCE_EXIT]: void;
5809
- /** Overlay visibility state (true = show, false = hide) */
5810
- [MessageEvents.OVERLAY]: boolean;
5811
- /** Connection state change from platform */
5812
- [MessageEvents.CONNECTION_STATE]: ConnectionStatePayload;
5813
- /** Ready message has no payload data */
5814
- [MessageEvents.READY]: void;
5815
- /** Exit message has no payload data */
5816
- [MessageEvents.EXIT]: void;
5817
- /** Performance telemetry data */
5818
- [MessageEvents.TELEMETRY]: TelemetryPayload;
5819
- /** Key event data */
5820
- [MessageEvents.KEY_EVENT]: KeyEventPayload;
5821
- /** Display alert request from game */
5822
- [MessageEvents.DISPLAY_ALERT]: DisplayAlertPayload;
5823
- /** Authentication state change notification */
5824
- [MessageEvents.AUTH_STATE_CHANGE]: AuthStateChangePayload;
5825
- /** OAuth callback data from popup/new-tab windows */
5826
- [MessageEvents.AUTH_CALLBACK]: AuthCallbackPayload;
5827
- };
5993
+
5828
5994
  /**
5829
- * **PlaycademyMessaging Class**
5830
- *
5831
- * This is the core messaging system that handles all communication in the Playcademy platform.
5832
- * It automatically detects the runtime environment and chooses the appropriate transport method.
5833
- *
5834
- * **Key Features**:
5835
- * 1. **Automatic Transport Selection**: Detects iframe vs local context and uses appropriate method
5836
- * 2. **Type Safety**: Full TypeScript support with payload type checking
5837
- * 3. **Bidirectional Communication**: Handles both parent→game and game→parent messages
5838
- * 4. **Event Cleanup**: Proper listener management to prevent memory leaks
5839
- *
5840
- * **Transport Methods**:
5841
- * - **postMessage**: Used for iframe-to-parent communication (production/development)
5842
- * - **CustomEvent**: Used for local same-context communication (local development)
5843
- *
5844
- * **Runtime Detection Logic**:
5845
- * - If `window.self !== window.top`: We're in an iframe, use postMessage
5846
- * - If `window.self === window.top`: We're in the same context, use CustomEvent
5995
+ * Base configuration for TimeBack integration (shared across all courses).
5996
+ * References upstream TimeBack types from @playcademy/timeback.
5847
5997
  *
5848
- * **Example Usage**:
5849
- * ```typescript
5850
- * // Send a message (automatically chooses transport)
5851
- * messaging.send(MessageEvents.READY, undefined)
5998
+ * All fields are optional and support template variables: {grade}, {subject}, {gameSlug}
5999
+ */
6000
+ interface TimebackBaseConfig {
6001
+ /** Organization configuration (shared across all courses) */
6002
+ organization?: Partial<OrganizationConfig>;
6003
+ /** Course defaults (can be overridden per-course) */
6004
+ course?: Partial<CourseConfig>;
6005
+ /** Component defaults */
6006
+ component?: Partial<ComponentConfig>;
6007
+ /** Resource defaults */
6008
+ resource?: Partial<ResourceConfig>;
6009
+ /** ComponentResource defaults */
6010
+ componentResource?: Partial<ComponentResourceConfig>;
6011
+ }
6012
+ /**
6013
+ * Extended course configuration that merges TimebackCourseConfig with per-course overrides.
6014
+ * Used in playcademy.config.* to allow per-course customization.
6015
+ */
6016
+ interface TimebackCourseConfigWithOverrides extends TimebackCourseConfig {
6017
+ title?: string;
6018
+ courseCode?: string;
6019
+ level?: string;
6020
+ metadata?: CourseConfig['metadata'];
6021
+ totalXp?: number | null;
6022
+ masterableUnits?: number | null;
6023
+ }
6024
+ /**
6025
+ * TimeBack integration configuration for Playcademy config file.
5852
6026
  *
5853
- * // Listen for messages (handles both transports)
5854
- * messaging.listen(MessageEvents.INIT, (payload) => {
5855
- * console.log('Game initialized with:', payload)
5856
- * })
6027
+ * Supports two levels of customization:
6028
+ * 1. `base`: Shared defaults for all courses (organization, course, component, resource, componentResource)
6029
+ * 2. Per-course overrides in the `courses` array (title, courseCode, level, gradingScheme, metadata)
5857
6030
  *
5858
- * // Clean up listeners
5859
- * messaging.unlisten(MessageEvents.INIT, handler)
5860
- * ```
6031
+ * Template variables ({grade}, {subject}, {gameSlug}) can be used in string fields.
5861
6032
  */
5862
- declare class PlaycademyMessaging {
5863
- /**
5864
- * Internal storage for message listeners.
5865
- *
5866
- * **Structure Explanation**:
5867
- * - Outer Map: MessageEvents → Map of handlers for that event type
5868
- * - Inner Map: MessageHandler → Object containing both listener types
5869
- * - Object: { postMessage: EventListener, customEvent: EventListener }
5870
- *
5871
- * **Why Two Listeners Per Handler?**:
5872
- * Since we don't know at registration time which transport will be used,
5873
- * we register both a postMessage listener and a CustomEvent listener.
5874
- * The appropriate one will be triggered based on the runtime context.
5875
- */
5876
- private listeners;
5877
- /**
5878
- * **Send Message Method**
5879
- *
5880
- * Sends a message using the appropriate transport method based on the runtime context.
5881
- * This is the main public API for sending messages in the Playcademy system.
5882
- *
5883
- * **How It Works**:
5884
- * 1. Analyzes the message type and current runtime context
5885
- * 2. Determines if we're in an iframe and if this message should use postMessage
5886
- * 3. Routes to the appropriate transport method (postMessage or CustomEvent)
5887
- *
5888
- * **Transport Selection Logic**:
5889
- * - **postMessage**: Used when game is in iframe and sending to parent, OR when target window is specified
5890
- * - **CustomEvent**: Used for local/same-context communication
5891
- *
5892
- * **Type Safety**:
5893
- * The generic type parameter `K` ensures that the payload type matches
5894
- * the expected type for the given message event.
5895
- *
5896
- * @template K - The message event type (ensures type safety)
5897
- * @param type - The message event type to send
5898
- * @param payload - The data to send with the message (type-checked)
5899
- * @param options - Optional configuration for message sending
5900
- *
5901
- * @example
5902
- * ```typescript
5903
- * // Send game ready signal (no payload)
5904
- * messaging.send(MessageEvents.READY, undefined)
5905
- *
5906
- * // Send telemetry data (typed payload)
5907
- * messaging.send(MessageEvents.TELEMETRY, { fps: 60, mem: 128 })
5908
- *
5909
- * // Send to specific iframe window (parent to iframe communication)
5910
- * messaging.send(MessageEvents.INIT, { baseUrl, token, gameId }, {
5911
- * target: iframe.contentWindow,
5912
- * origin: '*'
5913
- * })
5914
- *
5915
- * // TypeScript will error if payload type is wrong
5916
- * messaging.send(MessageEvents.INIT, "wrong type") // ❌ Error
5917
- * ```
5918
- */
5919
- send<K extends MessageEvents>(type: K, payload: MessageEventMap[K], options?: {
5920
- target?: Window;
5921
- origin?: string;
5922
- }): void;
6033
+ interface TimebackIntegrationConfig {
6034
+ /** Multi-grade course configuration (array of grade/subject/totalXp with optional per-course overrides) */
6035
+ courses: TimebackCourseConfigWithOverrides[];
6036
+ /** Optional base configuration (shared across all courses, can be overridden per-course) */
6037
+ base?: TimebackBaseConfig;
6038
+ }
6039
+ /**
6040
+ * Custom API routes integration
6041
+ */
6042
+ interface CustomRoutesIntegration {
6043
+ /** Directory for custom API routes (defaults to 'server/api') */
6044
+ directory?: string;
6045
+ }
6046
+ /**
6047
+ * Database integration
6048
+ */
6049
+ interface DatabaseIntegration {
6050
+ /** Database directory (defaults to 'db') */
6051
+ directory?: string;
6052
+ }
6053
+ /**
6054
+ * Integrations configuration
6055
+ * All backend features (database, custom routes, external services) are configured here
6056
+ */
6057
+ interface IntegrationsConfig {
6058
+ /** TimeBack integration (optional) */
6059
+ timeback?: TimebackIntegrationConfig | null;
6060
+ /** Custom API routes (optional) */
6061
+ customRoutes?: CustomRoutesIntegration | boolean;
6062
+ /** Database (optional) */
6063
+ database?: DatabaseIntegration | boolean;
6064
+ /** Key-Value storage (optional) */
6065
+ kv?: boolean;
6066
+ /** Bucket storage (optional) */
6067
+ bucket?: boolean;
6068
+ /** Authentication (optional) */
6069
+ auth?: boolean;
6070
+ }
6071
+ /**
6072
+ * Unified Playcademy configuration
6073
+ * Used for playcademy.config.{js,json}
6074
+ */
6075
+ interface PlaycademyConfig {
6076
+ /** Game name */
6077
+ name: string;
6078
+ /** Game description */
6079
+ description?: string;
6080
+ /** Game emoji icon */
6081
+ emoji?: string;
6082
+ /** Build command to run before deployment */
6083
+ buildCommand?: string[];
6084
+ /** Path to build output */
6085
+ buildPath?: string;
6086
+ /** Game type */
6087
+ gameType?: 'hosted' | 'external';
6088
+ /** External URL (for external games) */
6089
+ externalUrl?: string;
6090
+ /** Game platform */
6091
+ platform?: 'web' | 'unity' | 'godot';
6092
+ /** Integrations (database, custom routes, external services) */
6093
+ integrations?: IntegrationsConfig;
6094
+ }
6095
+
6096
+ /**
6097
+ * Configuration options for initializing a PlaycademyClient instance.
6098
+ *
6099
+ * @example
6100
+ * ```typescript
6101
+ * const config: PlaycademyServerClientConfig = {
6102
+ * apiKey: process.env.PLAYCADEMY_API_KEY!,
6103
+ * gameId: 'my-math-game',
6104
+ * configPath: './playcademy.config.js'
6105
+ * }
6106
+ * ```
6107
+ */
6108
+ interface PlaycademyServerClientConfig {
5923
6109
  /**
5924
- * **Listen for Messages Method**
5925
- *
5926
- * Registers a message listener that will be called when the specified message type is received.
5927
- * This method automatically handles both postMessage and CustomEvent sources.
5928
- *
5929
- * **Why Register Two Listeners?**:
5930
- * Since we don't know at registration time which transport method will be used to send
5931
- * messages, we register listeners for both possible sources:
5932
- * 1. **postMessage listener**: Handles messages from iframe-to-parent communication
5933
- * 2. **CustomEvent listener**: Handles messages from local same-context communication
5934
- *
5935
- * **Message Processing**:
5936
- * - **postMessage**: Extracts data from `event.data.payload` or `event.data`
5937
- * - **CustomEvent**: Extracts data from `event.detail`
5938
- *
5939
- * **Type Safety**:
5940
- * The handler function receives the correctly typed payload based on the message type.
5941
- *
5942
- * @template K - The message event type (ensures type safety)
5943
- * @param type - The message event type to listen for
5944
- * @param handler - Function to call when the message is received
5945
- *
5946
- * @example
5947
- * ```typescript
5948
- * // Listen for game initialization
5949
- * messaging.listen(MessageEvents.INIT, (payload) => {
5950
- * // payload is automatically typed as GameContextPayload
5951
- * console.log(`Game ${payload.gameId} initialized`)
5952
- * console.log(`API base URL: ${payload.baseUrl}`)
5953
- * })
5954
- *
5955
- * // Listen for token refresh
5956
- * messaging.listen(MessageEvents.TOKEN_REFRESH, ({ token, exp }) => {
5957
- * // payload is automatically typed as { token: string; exp: number }
5958
- * updateAuthToken(token)
5959
- * scheduleTokenRefresh(exp)
5960
- * })
5961
- * ```
6110
+ * Playcademy API key for server-to-server authentication.
6111
+ * Obtain from the Playcademy developer dashboard.
5962
6112
  */
5963
- listen<K extends MessageEvents>(type: K, handler: MessageHandler<MessageEventMap[K]>): void;
6113
+ apiKey: string;
5964
6114
  /**
5965
- * **Remove Message Listener Method**
5966
- *
5967
- * Removes a previously registered message listener to prevent memory leaks and unwanted callbacks.
5968
- * This method cleans up both the postMessage and CustomEvent listeners that were registered.
5969
- *
5970
- * **Why Clean Up Both Listeners?**:
5971
- * When we registered the listener with `listen()`, we created two browser event listeners:
5972
- * 1. A 'message' event listener for postMessage communication
5973
- * 2. A custom event listener for local CustomEvent communication
5974
- *
5975
- * Both must be removed to prevent memory leaks and ensure the handler is completely unregistered.
5976
- *
5977
- * **Memory Management**:
5978
- * - Removes both event listeners from the browser
5979
- * - Cleans up internal tracking maps
5980
- * - If no more handlers exist for a message type, removes the entire type entry
5981
- *
5982
- * **Safe to Call Multiple Times**:
5983
- * This method is idempotent - calling it multiple times with the same handler is safe.
5984
- *
5985
- * @template K - The message event type (ensures type safety)
5986
- * @param type - The message event type to stop listening for
5987
- * @param handler - The exact handler function that was passed to listen()
5988
- *
5989
- * @example
5990
- * ```typescript
5991
- * // Register a handler
5992
- * const handleInit = (payload) => console.log('Game initialized')
5993
- * messaging.listen(MessageEvents.INIT, handleInit)
5994
- *
5995
- * // Later, remove the handler
5996
- * messaging.unlisten(MessageEvents.INIT, handleInit)
6115
+ * Optional path to playcademy.config.js file.
6116
+ * If not provided, searches current directory and up to 3 parent directories.
6117
+ * Ignored if `config` is provided directly.
5997
6118
  *
5998
- * // Safe to call multiple times
5999
- * messaging.unlisten(MessageEvents.INIT, handleInit) // No error
6000
- * ```
6119
+ * @example './config/playcademy.config.js'
6001
6120
  */
6002
- unlisten<K extends MessageEvents>(type: K, handler: MessageHandler<MessageEventMap[K]>): void;
6121
+ configPath?: string;
6003
6122
  /**
6004
- * **Get Messaging Context Method**
6005
- *
6006
- * Analyzes the current runtime environment and message type to determine the appropriate
6007
- * transport method and configuration for sending messages.
6008
- *
6009
- * **Runtime Environment Detection**:
6010
- * The method detects whether the code is running in an iframe by comparing:
6011
- * - `window.self`: Reference to the current window
6012
- * - `window.top`: Reference to the topmost window in the hierarchy
6013
- *
6014
- * If they're different, we're in an iframe. If they're the same, we're in the top-level window.
6015
- *
6016
- * **Message Direction Analysis**:
6017
- * Different message types flow in different directions:
6018
- * - **Game → Parent**: READY, EXIT, TELEMETRY (use postMessage when in iframe)
6019
- * - **Parent → Game**: INIT, TOKEN_REFRESH, PAUSE, etc. (use CustomEvent in local dev, or postMessage with target)
6020
- *
6021
- * **Cross-Context Communication**:
6022
- * The messaging system supports cross-context targeting through the optional `target` parameter.
6023
- * When a target window is specified, postMessage is used regardless of the current context.
6024
- * This enables parent-to-iframe communication through the unified messaging API.
6025
- *
6026
- * **Transport Selection Logic**:
6027
- * - **postMessage**: Used when game is in iframe AND sending to parent, OR when target window is specified
6028
- * - **CustomEvent**: Used for all other cases (local development, same-context communication)
6029
- *
6030
- * **Security Considerations**:
6031
- * The origin is currently set to '*' for development convenience, but should be
6032
- * configurable for production security.
6033
- *
6034
- * @param eventType - The message event type being sent
6035
- * @returns Configuration object with transport method and target information
6036
- *
6037
- * @example
6038
- * ```typescript
6039
- * // In iframe sending READY to parent
6040
- * const context = getMessagingContext(MessageEvents.READY)
6041
- * // Returns: { shouldUsePostMessage: true, target: window.parent, origin: '*' }
6123
+ * Optional config object (for edge environments without filesystem).
6124
+ * If provided, skips filesystem-based config loading.
6042
6125
  *
6043
- * // In same context sending INIT
6044
- * const context = getMessagingContext(MessageEvents.INIT)
6045
- * // Returns: { shouldUsePostMessage: false, target: undefined, origin: '*' }
6046
- * ```
6126
+ * @example { name: 'My Game', integrations: { timeback: {...} } }
6047
6127
  */
6048
- private getMessagingContext;
6128
+ config?: PlaycademyConfig;
6049
6129
  /**
6050
- * **Send Via PostMessage Method**
6051
- *
6052
- * Sends a message using the browser's postMessage API for iframe-to-parent communication.
6053
- * This method is used when the game is running in an iframe and needs to communicate
6054
- * with the parent window (the Playcademy shell).
6055
- *
6056
- * **PostMessage Protocol**:
6057
- * The postMessage API is the standard way for iframes to communicate with their parent.
6058
- * It's secure, cross-origin capable, and designed specifically for this use case.
6059
- *
6060
- * **Message Structure**:
6061
- * The method creates a message object with the following structure:
6062
- * - `type`: The message event type (e.g., 'PLAYCADEMY_READY')
6063
- * - `payload`: The message data (if any)
6064
- *
6065
- * **Payload Handling**:
6066
- * - **All payloads**: Wrapped in a `payload` property for consistency (e.g., { type, payload: data })
6067
- * - **Undefined payloads**: Only the type is sent (e.g., { type })
6068
- *
6069
- * **Security**:
6070
- * The origin parameter controls which domains can receive the message.
6071
- * Currently set to '*' for development, but should be restricted in production.
6072
- *
6073
- * @template K - The message event type (ensures type safety)
6074
- * @param type - The message event type to send
6075
- * @param payload - The data to send with the message
6076
- * @param target - The target window (defaults to parent window)
6077
- * @param origin - The allowed origin for the message (defaults to '*')
6078
- *
6079
- * @example
6080
- * ```typescript
6081
- * // Send ready signal (no payload)
6082
- * sendViaPostMessage(MessageEvents.READY, undefined)
6083
- * // Sends: { type: 'PLAYCADEMY_READY' }
6084
- *
6085
- * // Send telemetry data (object payload)
6086
- * sendViaPostMessage(MessageEvents.TELEMETRY, { fps: 60, mem: 128 })
6087
- * // Sends: { type: 'PLAYCADEMY_TELEMETRY', payload: { fps: 60, mem: 128 } }
6130
+ * Optional base URL for Playcademy API.
6131
+ * Defaults to environment variables or 'https://hub.playcademy.net'.
6088
6132
  *
6089
- * // Send overlay state (primitive payload)
6090
- * sendViaPostMessage(MessageEvents.OVERLAY, true)
6091
- * // Sends: { type: 'PLAYCADEMY_OVERLAY', payload: true }
6092
- * ```
6133
+ * @example 'http://localhost:3000' for local development
6093
6134
  */
6094
- private sendViaPostMessage;
6135
+ baseUrl?: string;
6095
6136
  /**
6096
- * **Send Via CustomEvent Method**
6097
- *
6098
- * Sends a message using the browser's CustomEvent API for local same-context communication.
6099
- * This method is used when both the sender and receiver are in the same window context,
6100
- * typically during local development or when the parent needs to send messages to the game.
6101
- *
6102
- * **CustomEvent Protocol**:
6103
- * CustomEvent is a browser API for creating and dispatching custom events within the same
6104
- * window context. It's simpler than postMessage but only works within the same origin/context.
6105
- *
6106
- * **Event Structure**:
6107
- * - **type**: The event name (e.g., 'PLAYCADEMY_INIT')
6108
- * - **detail**: The payload data attached to the event
6109
- *
6110
- * **When This Is Used**:
6111
- * - Local development when game and shell run in the same context
6112
- * - Parent-to-game communication (INIT, TOKEN_REFRESH, PAUSE, etc.)
6113
- * - Any scenario where postMessage isn't needed
6114
- *
6115
- * **Event Bubbling**:
6116
- * CustomEvents are dispatched on the window object and can be listened to by any
6117
- * code in the same context using `addEventListener(type, handler)`.
6118
- *
6119
- * @template K - The message event type (ensures type safety)
6120
- * @param type - The message event type to send (becomes the event name)
6121
- * @param payload - The data to send with the message (stored in event.detail)
6122
- *
6123
- * @example
6124
- * ```typescript
6125
- * // Send initialization data
6126
- * sendViaCustomEvent(MessageEvents.INIT, {
6127
- * baseUrl: '/api',
6128
- * token: 'abc123',
6129
- * gameId: 'game-456'
6130
- * })
6131
- * // Creates: CustomEvent('PLAYCADEMY_INIT', { detail: { baseUrl, token, gameId } })
6132
- *
6133
- * // Send pause signal
6134
- * sendViaCustomEvent(MessageEvents.PAUSE, undefined)
6135
- * // Creates: CustomEvent('PLAYCADEMY_PAUSE', { detail: undefined })
6137
+ * Optional game ID.
6138
+ * If not provided, will attempt to fetch from API using the API token.
6136
6139
  *
6137
- * // Listeners can access the data via event.detail:
6138
- * window.addEventListener('PLAYCADEMY_INIT', (event) => {
6139
- * console.log(event.detail.gameId) // 'game-456'
6140
- * })
6141
- * ```
6140
+ * @example 'my-math-game'
6142
6141
  */
6143
- private sendViaCustomEvent;
6142
+ gameId?: string;
6144
6143
  }
6145
6144
  /**
6146
- * **Playcademy Messaging Singleton**
6147
- *
6148
- * This is the main messaging instance used throughout the Playcademy platform.
6149
- * It's exported as a singleton to ensure consistent communication across all parts
6150
- * of the application.
6151
- *
6152
- * **Why a Singleton?**:
6153
- * - Ensures all parts of the app use the same messaging instance
6154
- * - Prevents conflicts between multiple messaging systems
6155
- * - Simplifies the API - no need to pass instances around
6156
- * - Maintains consistent event listener management
6157
- *
6158
- * **Usage in Different Contexts**:
6159
- *
6160
- * **In Games**:
6161
- * ```typescript
6162
- * import { messaging, MessageEvents } from '@playcademy/sdk'
6163
- *
6164
- * // Tell parent we're ready
6165
- * messaging.send(MessageEvents.READY, undefined)
6166
- *
6167
- * // Listen for pause/resume
6168
- * messaging.listen(MessageEvents.PAUSE, () => game.pause())
6169
- * messaging.listen(MessageEvents.RESUME, () => game.resume())
6170
- * ```
6171
- *
6172
- * **In Parent Shell**:
6173
- * ```typescript
6174
- * import { messaging, MessageEvents } from '@playcademy/sdk'
6175
- *
6176
- * // Send initialization data to game
6177
- * messaging.send(MessageEvents.INIT, { baseUrl, token, gameId })
6178
- *
6179
- * // Listen for game events
6180
- * messaging.listen(MessageEvents.EXIT, () => closeGame())
6181
- * messaging.listen(MessageEvents.READY, () => showGame())
6182
- * ```
6145
+ * Internal state maintained by the PlaycademyClient instance.
6183
6146
  *
6184
- * **Automatic Transport Selection**:
6185
- * The messaging system automatically chooses the right transport method:
6186
- * - Uses postMessage when game is in iframe sending to parent
6187
- * - Uses CustomEvent for local development and parent-to-game communication
6147
+ * @internal
6148
+ */
6149
+ interface PlaycademyServerClientState {
6150
+ /** API key for authentication */
6151
+ apiKey: string;
6152
+ /** Base URL for API requests */
6153
+ baseUrl: string;
6154
+ /** Game identifier */
6155
+ gameId: string;
6156
+ /** Loaded game configuration from playcademy.config.js */
6157
+ config: PlaycademyConfig;
6158
+ /**
6159
+ * TimeBack course ID fetched from the Playcademy API.
6160
+ * Used for all TimeBack event recording.
6161
+ */
6162
+ courseId?: string;
6163
+ }
6164
+
6165
+ /**
6166
+ * Resource bindings for backend deployment
6167
+ * Provider-agnostic abstraction for cloud resources
6168
+ */
6169
+ interface BackendResourceBindings {
6170
+ /** SQL database instances to create and bind (maps to D1 on Cloudflare) */
6171
+ database?: string[];
6172
+ /** Key-value store namespaces to create and bind (maps to KV on Cloudflare) */
6173
+ keyValue?: string[];
6174
+ /** Object storage buckets to bind (maps to R2 on Cloudflare) */
6175
+ bucket?: string[];
6176
+ }
6177
+ /**
6178
+ * Backend deployment bundle for uploading to Playcademy platform
6179
+ */
6180
+ interface BackendDeploymentBundle {
6181
+ /** Bundled JavaScript code ready for deployment */
6182
+ code: string;
6183
+ /** Game configuration */
6184
+ config: PlaycademyConfig;
6185
+ /** Optional resource bindings (database, storage, etc.) */
6186
+ bindings?: BackendResourceBindings;
6187
+ /** Optional schema information for database setup */
6188
+ schema?: SchemaInfo;
6189
+ /** Optional game secrets */
6190
+ secrets?: Record<string, string>;
6191
+ }
6192
+
6193
+ /**
6194
+ * OAuth 2.0 implementation for the Playcademy SDK
6195
+ */
6196
+
6197
+ /**
6198
+ * Parses an OAuth state parameter to extract CSRF token and any encoded data.
6188
6199
  *
6189
- * **Type Safety**:
6190
- * All message sending and receiving is fully type-safe with TypeScript.
6200
+ * @param state - The OAuth state parameter to parse
6201
+ * @returns Object containing CSRF token and optional decoded data
6191
6202
  */
6192
- declare const messaging: PlaycademyMessaging;
6203
+ declare function parseOAuthState(state: string): {
6204
+ csrfToken: string;
6205
+ data?: Record<string, string>;
6206
+ };
6207
+
6208
+ /**
6209
+ * Cache configuration types for runtime customization
6210
+ */
6211
+ /**
6212
+ * Runtime configuration for TTL cache behavior
6213
+ */
6214
+ interface TTLCacheConfig {
6215
+ /** Time-to-live in milliseconds. Set to 0 to disable caching for this call. */
6216
+ ttl?: number;
6217
+ /** Force refresh, bypassing cache */
6218
+ force?: boolean;
6219
+ /** Skip cache and fetch fresh data (alias for force) */
6220
+ skipCache?: boolean;
6221
+ }
6222
+ /**
6223
+ * Runtime configuration for cooldown cache behavior
6224
+ */
6225
+ interface CooldownCacheConfig {
6226
+ /** Cooldown period in milliseconds. Set to 0 to disable cooldown for this call. */
6227
+ cooldown?: number;
6228
+ /** Force refresh, bypassing cooldown */
6229
+ force?: boolean;
6230
+ }
6193
6231
 
6194
6232
  /**
6195
6233
  * Internal Playcademy SDK client with all namespaces.
6196
- * For use by CLI, platform, and admin tools.
6234
+ * For use by Cademy platform, CLI, and admin tools.
6197
6235
  *
6198
- * Extends the public client (8 namespaces) with 13 additional internal namespaces.
6236
+ * Extends PlaycademyBaseClient directly (not PlaycademyClient) to allow
6237
+ * independent namespace implementations without type conflicts.
6199
6238
  */
6200
- declare class PlaycademyInternalClient extends PlaycademyClient {
6201
- /** Platform API authentication methods (login, logout, API keys) */
6239
+ declare class PlaycademyInternalClient extends PlaycademyBaseClient {
6240
+ /**
6241
+ * Connect external identity providers to the user's Playcademy account.
6242
+ * - `connect(provider)` - Link Discord, Google, etc. via OAuth popup
6243
+ */
6244
+ identity: {
6245
+ connect: (options: AuthOptions) => Promise<AuthResult>;
6246
+ _getContext: () => {
6247
+ isInIframe: boolean;
6248
+ };
6249
+ };
6250
+ /**
6251
+ * Game runtime lifecycle and asset loading.
6252
+ * - `exit()` - Return to Cademy hub
6253
+ * - `getGameToken()` - Get short-lived auth token
6254
+ * - `assets.url()`, `assets.json()`, `assets.fetch()` - Load game assets
6255
+ */
6256
+ runtime: {
6257
+ getGameToken: (gameId: string, options?: {
6258
+ apply?: boolean;
6259
+ }) => Promise<GameTokenResponse>;
6260
+ exit: () => Promise<void>;
6261
+ onInit: (handler: (context: GameContextPayload) => void) => void;
6262
+ onTokenRefresh: (handler: (data: {
6263
+ token: string;
6264
+ exp: number;
6265
+ }) => void) => void;
6266
+ onPause: (handler: () => void) => void;
6267
+ onResume: (handler: () => void) => void;
6268
+ onForceExit: (handler: () => void) => void;
6269
+ onOverlay: (handler: (isVisible: boolean) => void) => void;
6270
+ ready: () => void;
6271
+ sendTelemetry: (data: {
6272
+ fps: number;
6273
+ mem: number;
6274
+ }) => void;
6275
+ removeListener: (eventType: MessageEvents, handler: ((context: GameContextPayload) => void) | ((data: {
6276
+ token: string;
6277
+ exp: number;
6278
+ }) => void) | (() => void) | ((isVisible: boolean) => void)) => void;
6279
+ removeAllListeners: () => void;
6280
+ getListenerCounts: () => Record<string, number>;
6281
+ assets: {
6282
+ url(pathOrStrings: string | TemplateStringsArray, ...values: unknown[]): string;
6283
+ fetch: (path: string, options?: RequestInit) => Promise<Response>;
6284
+ json: <T = unknown>(path: string) => Promise<T>;
6285
+ blob: (path: string) => Promise<Blob>;
6286
+ text: (path: string) => Promise<string>;
6287
+ arrayBuffer: (path: string) => Promise<ArrayBuffer>;
6288
+ };
6289
+ };
6290
+ /**
6291
+ * Playcademy Credits (platform currency) management.
6292
+ * - `get()` - Get user's credit balance
6293
+ * - `add(amount)` - Award credits to user
6294
+ */
6295
+ credits: {
6296
+ balance: () => Promise<number>;
6297
+ add: (amount: number) => Promise<number>;
6298
+ spend: (amount: number) => Promise<number>;
6299
+ };
6300
+ /**
6301
+ * Realtime multiplayer authentication.
6302
+ * - `getToken()` - Get token for WebSocket/realtime connections
6303
+ */
6304
+ realtime: {
6305
+ token: {
6306
+ get: () => Promise<RealtimeTokenResponse>;
6307
+ };
6308
+ };
6309
+ /**
6310
+ * Make requests to your game's custom backend API routes.
6311
+ * - `get(path)`, `post(path, body)`, `put()`, `delete()` - HTTP methods
6312
+ */
6313
+ backend: {
6314
+ get<T = unknown>(path: string, headers?: Record<string, string>): Promise<T>;
6315
+ post<T = unknown>(path: string, body?: unknown, headers?: Record<string, string>): Promise<T>;
6316
+ put<T = unknown>(path: string, body?: unknown, headers?: Record<string, string>): Promise<T>;
6317
+ patch<T = unknown>(path: string, body?: unknown, headers?: Record<string, string>): Promise<T>;
6318
+ delete<T = unknown>(path: string, headers?: Record<string, string>): Promise<T>;
6319
+ request<T = unknown>(path: string, method: Method, body?: unknown, headers?: Record<string, string>): Promise<T>;
6320
+ download(path: string, method?: Method, body?: unknown, headers?: Record<string, string>): Promise<Response>;
6321
+ url(pathOrStrings: string | TemplateStringsArray, ...values: unknown[]): string;
6322
+ };
6323
+ /**
6324
+ * Platform authentication (better-auth integration).
6325
+ * - `login(email, password)` - Authenticate user
6326
+ * - `logout()` - End session
6327
+ * - `getSession()` - Get current session
6328
+ */
6202
6329
  auth: {
6203
6330
  login: (credentials: {
6204
6331
  email: string;
@@ -6217,14 +6344,26 @@ declare class PlaycademyInternalClient extends PlaycademyClient {
6217
6344
  apiKeys: {
6218
6345
  create: (options?: {
6219
6346
  name?: string;
6220
- expiresIn?: number | null;
6347
+ expiresIn
6348
+ /**
6349
+ * Platform-wide achievements system.
6350
+ * - `list()` - Get all achievements
6351
+ * - `getUserAchievements()` - Get user's earned achievements
6352
+ * - `award(achievementId)` - Grant achievement to user
6353
+ */
6354
+ ?: number | null;
6221
6355
  permissions?: Record<string, string[]>;
6222
6356
  }) => Promise<BetterAuthApiKeyResponse>;
6223
6357
  list: () => Promise<BetterAuthApiKey[]>;
6224
6358
  revoke: (keyId: string) => Promise<void>;
6225
6359
  };
6226
6360
  };
6227
- /** Administrative operations (pause games, manage items/currencies) */
6361
+ /**
6362
+ * Administrative operations for platform management.
6363
+ * - `items.list()`, `items.create()` - Manage item catalog
6364
+ * - `currencies.list()` - Manage currencies
6365
+ * - `games.pause()`, `games.unpause()` - Control game availability
6366
+ */
6228
6367
  admin: {
6229
6368
  games: {
6230
6369
  pauseGame: (gameId: string) => Promise<void>;
@@ -6234,7 +6373,6 @@ declare class PlaycademyInternalClient extends PlaycademyClient {
6234
6373
  create: (props: InsertItemInput) => Promise<{
6235
6374
  gameId: string | null;
6236
6375
  id: string;
6237
- createdAt: Date;
6238
6376
  slug: string;
6239
6377
  displayName: string;
6240
6378
  description: string | null;
@@ -6242,11 +6380,11 @@ declare class PlaycademyInternalClient extends PlaycademyClient {
6242
6380
  isPlaceable: boolean;
6243
6381
  imageUrl: string | null;
6244
6382
  metadata: unknown;
6383
+ createdAt: Date;
6245
6384
  }>;
6246
6385
  get: (itemId: string) => Promise<{
6247
6386
  gameId: string | null;
6248
6387
  id: string;
6249
- createdAt: Date;
6250
6388
  slug: string;
6251
6389
  displayName: string;
6252
6390
  description: string | null;
@@ -6254,11 +6392,11 @@ declare class PlaycademyInternalClient extends PlaycademyClient {
6254
6392
  isPlaceable: boolean;
6255
6393
  imageUrl: string | null;
6256
6394
  metadata: unknown;
6395
+ createdAt: Date;
6257
6396
  }>;
6258
6397
  list: () => Promise<{
6259
6398
  gameId: string | null;
6260
6399
  id: string;
6261
- createdAt: Date;
6262
6400
  slug: string;
6263
6401
  displayName: string;
6264
6402
  description: string | null;
@@ -6266,11 +6404,11 @@ declare class PlaycademyInternalClient extends PlaycademyClient {
6266
6404
  isPlaceable: boolean;
6267
6405
  imageUrl: string | null;
6268
6406
  metadata: unknown;
6407
+ createdAt: Date;
6269
6408
  }[]>;
6270
6409
  update: (itemId: string, props: UpdateItemInput) => Promise<{
6271
6410
  gameId: string | null;
6272
6411
  id: string;
6273
- createdAt: Date;
6274
6412
  slug: string;
6275
6413
  displayName: string;
6276
6414
  description: string | null;
@@ -6278,6 +6416,7 @@ declare class PlaycademyInternalClient extends PlaycademyClient {
6278
6416
  isPlaceable: boolean;
6279
6417
  imageUrl: string | null;
6280
6418
  metadata: unknown;
6419
+ createdAt: Date;
6281
6420
  }>;
6282
6421
  delete: (itemId: string) => Promise<void>;
6283
6422
  };
@@ -6372,7 +6511,12 @@ declare class PlaycademyInternalClient extends PlaycademyClient {
6372
6511
  delete: (listingId: string) => Promise<void>;
6373
6512
  };
6374
6513
  };
6375
- /** Developer operations (publish games, uploads, API keys) */
6514
+ /**
6515
+ * Developer operations for game publishing.
6516
+ * - `publish(gameId)` - Deploy game to production
6517
+ * - `uploads.getSignedUrl()` - Get upload URLs for assets
6518
+ * - `apiKeys.create()`, `apiKeys.list()` - Manage API keys
6519
+ */
6376
6520
  dev: {
6377
6521
  status: {
6378
6522
  apply: () => Promise<void>;
@@ -6439,7 +6583,13 @@ declare class PlaycademyInternalClient extends PlaycademyClient {
6439
6583
  };
6440
6584
  };
6441
6585
  };
6442
- /** Game directory, session management, tokens */
6586
+ /**
6587
+ * Game directory and session management.
6588
+ * - `list()` - Get all games
6589
+ * - `fetch(id)` - Get game by ID
6590
+ * - `sessions.start()`, `sessions.end()` - Manage play sessions
6591
+ * - `getGameToken(gameId)` - Get short-lived game auth token
6592
+ */
6443
6593
  games: {
6444
6594
  fetch: (gameIdOrSlug: string, options?: TTLCacheConfig) => Promise<FetchedGame>;
6445
6595
  list: (options?: TTLCacheConfig) => Promise<Array<Game>>;
@@ -6459,7 +6609,11 @@ declare class PlaycademyInternalClient extends PlaycademyClient {
6459
6609
  }) => Promise<LeaderboardEntry[]>;
6460
6610
  };
6461
6611
  };
6462
- /** Avatar customization (overworld) */
6612
+ /**
6613
+ * Avatar customization for the overworld.
6614
+ * - `get()` - Get user's avatar configuration
6615
+ * - `update(config)` - Update avatar appearance
6616
+ */
6463
6617
  character: {
6464
6618
  get: (userId?: string) => Promise<PlayerCharacter | null>;
6465
6619
  create: (characterData: CreateCharacterData) => Promise<PlayerCharacter>;
@@ -6477,7 +6631,12 @@ declare class PlaycademyInternalClient extends PlaycademyClient {
6477
6631
  list: () => Promise<PlayerCharacterAccessory[]>;
6478
6632
  };
6479
6633
  };
6480
- /** Platform-wide achievements (overworld) */
6634
+ /**
6635
+ * Platform-wide achievements system.
6636
+ * - `list()` - Get all achievements
6637
+ * - `getUserAchievements()` - Get user's earned achievements
6638
+ * - `award(achievementId)` - Grant achievement to user
6639
+ */
6481
6640
  achievements: {
6482
6641
  list: (options?: TTLCacheConfig) => Promise<AchievementCurrent[]>;
6483
6642
  history: {
@@ -6489,12 +6648,20 @@ declare class PlaycademyInternalClient extends PlaycademyClient {
6489
6648
  submit: (achievementId: string) => Promise<AchievementProgressResponse>;
6490
6649
  };
6491
6650
  };
6492
- /** Cross-game leaderboards (overworld) */
6651
+ /**
6652
+ * Cross-game leaderboards for the overworld.
6653
+ * - `get(leaderboardId)` - Fetch leaderboard rankings
6654
+ * - `submit(leaderboardId, score)` - Submit score to leaderboard
6655
+ */
6493
6656
  leaderboard: {
6494
6657
  fetch: (options?: LeaderboardOptions) => Promise<GameLeaderboardEntry[]>;
6495
6658
  getUserRank: (gameId: string, userId: string) => Promise<UserRankResponse>;
6496
6659
  };
6497
- /** Platform-wide XP/level system (overworld) */
6660
+ /**
6661
+ * Platform-wide XP and leveling system.
6662
+ * - `get()` - Get user's current level and XP
6663
+ * - `addXp(amount)` - Award XP to user
6664
+ */
6498
6665
  levels: {
6499
6666
  get: () => Promise<UserLevel>;
6500
6667
  progress: (options?: CooldownCacheConfig) => Promise<{
@@ -6508,11 +6675,19 @@ declare class PlaycademyInternalClient extends PlaycademyClient {
6508
6675
  get: (level: number) => Promise<LevelConfig | null>;
6509
6676
  };
6510
6677
  };
6511
- /** Shop listings and purchases (overworld) */
6678
+ /**
6679
+ * Shop listings and in-game purchases.
6680
+ * - `list()` - Get available shop items
6681
+ * - `purchase(itemId)` - Buy an item with credits
6682
+ */
6512
6683
  shop: {
6513
6684
  view: () => Promise<ShopViewResponse>;
6514
6685
  };
6515
- /** System notifications (overworld) */
6686
+ /**
6687
+ * System notifications for the platform.
6688
+ * - `list()` - Get user's notifications
6689
+ * - `markRead(notificationId)` - Mark notification as read
6690
+ */
6516
6691
  notifications: {
6517
6692
  list: (queryOptions?: {
6518
6693
  status?: NotificationStatus;
@@ -6530,7 +6705,11 @@ declare class PlaycademyInternalClient extends PlaycademyClient {
6530
6705
  }, cacheOptions?: TTLCacheConfig) => Promise<NotificationStats>;
6531
6706
  };
6532
6707
  };
6533
- /** Overworld map management */
6708
+ /**
6709
+ * Overworld map management.
6710
+ * - `get()` - Get map data and unlocked areas
6711
+ * - `unlock(areaId)` - Unlock a new map area
6712
+ */
6534
6713
  maps: {
6535
6714
  get: (identifier: string, options?: TTLCacheConfig) => Promise<MapData>;
6536
6715
  elements: (mapId: string, options?: TTLCacheConfig) => Promise<MapElementWithGame[]>;
@@ -6540,27 +6719,53 @@ declare class PlaycademyInternalClient extends PlaycademyClient {
6540
6719
  delete: (mapId: string, objectId: string) => Promise<void>;
6541
6720
  };
6542
6721
  };
6543
- /** Asset/sprite management */
6722
+ /**
6723
+ * Asset and sprite management for the overworld.
6724
+ * - `list()` - Get available sprites
6725
+ * - `upload(file)` - Upload new sprite asset
6726
+ */
6544
6727
  sprites: {
6545
6728
  templates: {
6546
6729
  get: (slug: string) => Promise<SpriteTemplateData>;
6547
6730
  };
6548
6731
  };
6549
- /** Analytics and metrics */
6732
+ /**
6733
+ * Analytics and metrics tracking.
6734
+ * - `track(event, properties)` - Record analytics event
6735
+ * - `getMetrics()` - Fetch platform metrics
6736
+ */
6550
6737
  telemetry: {
6551
6738
  pushMetrics: (metrics: Record<string, number>) => Promise<void>;
6552
6739
  };
6553
- /** Scores methods */
6740
+ /**
6741
+ * Score submission and user score queries.
6742
+ * - `submit(gameId, score, metadata?)` - Record a game score
6743
+ * - `getByUser(userId)` - Get all scores for a user
6744
+ */
6554
6745
  scores: {
6555
6746
  getByUser: (gameId: string, userId: string, options?: {
6556
- limit
6557
- /** Cross-game leaderboards (overworld) */
6558
- ?: number;
6747
+ limit?: number;
6559
6748
  }) => Promise<UserScore$1[]>;
6560
6749
  submit: (gameId: string, score: number, metadata?: Record<string, unknown>) => Promise<ScoreSubmission>;
6561
6750
  };
6562
- /** TimeBack (platform version with enrollments, game methods throw) */
6751
+ /**
6752
+ * TimeBack integration for user context, XP tracking, and course management.
6753
+ *
6754
+ * User context:
6755
+ * - `user.role`, `user.enrollments`, `user.organizations` - Cached data
6756
+ * - `user.fetch()` - Refresh from server
6757
+ *
6758
+ * XP tracking:
6759
+ * - `xp.today()`, `xp.total()`, `xp.history()`, `xp.summary()` - Query XP data
6760
+ *
6761
+ * Student lookup (platform-only):
6762
+ * - `students.get(timebackId)` - Fetch any student's full timeback data
6763
+ *
6764
+ * Management (CLI/admin):
6765
+ * - `management.setup()`, `management.verify()`, `management.get()` - Configure integrations
6766
+ */
6563
6767
  timeback: {
6768
+ readonly user: PlatformTimebackUser;
6564
6769
  startActivity: (_metadata: _playcademy_timeback_types.ActivityData) => void;
6565
6770
  pauseActivity: () => void;
6566
6771
  resumeActivity: () => void;
@@ -6587,12 +6792,20 @@ declare class PlaycademyInternalClient extends PlaycademyClient {
6587
6792
  timezone?: string;
6588
6793
  }) => Promise<XpSummaryResponse>;
6589
6794
  };
6590
- enrollments: {
6591
- get: (timebackId: string, options?: TTLCacheConfig) => Promise<StudentEnrollment[]>;
6795
+ students: {
6796
+ get: (timebackId: string, options?: TTLCacheConfig) => Promise<PlatformTimebackUserContext>;
6592
6797
  clearCache: (timebackId?: string) => void;
6593
6798
  };
6594
6799
  };
6800
+ /** Auto-initializes a PlaycademyInternalClient with context from the environment */
6801
+ static init: typeof init;
6802
+ /** Authenticates a user with email and password */
6803
+ static login: typeof login;
6804
+ /** Static identity utilities for OAuth operations */
6805
+ static identity: {
6806
+ parseOAuthState: typeof parseOAuthState;
6807
+ };
6595
6808
  }
6596
6809
 
6597
6810
  export { ApiError, ConnectionManager, ConnectionMonitor, MessageEvents, PlaycademyInternalClient as PlaycademyClient, PlaycademyError, PlaycademyInternalClient, extractApiErrorInfo, messaging };
6598
- export type { Achievement, AchievementCurrent, AchievementProgressResponse, ApiErrorInfo, AuthCallbackPayload, AuthOptions, AuthProviderType, AuthResult, AuthServerMessage, AuthStateChangePayload, AuthStateUpdate, AuthenticatedUser, BetterAuthApiKey, BetterAuthApiKeyResponse, BetterAuthSignInResponse, BucketFile, CharacterComponent, CharacterComponentWithSpriteUrl, CharacterComponentsOptions, ClientConfig, ClientEvents, ConnectionMonitorConfig, ConnectionState, ConnectionStatePayload, CreateCharacterData, Currency, DevUploadEvent, DevUploadHooks, DeveloperStatusResponse, DisconnectContext, DisconnectHandler, DisplayAlertPayload, EnrollmentsResponse, ErrorResponseBody, EventListeners, ExternalGame, FetchedGame, Game, GameContextPayload, GameLeaderboardEntry, GameSession, GameStateData, GameTokenResponse, GameUser, HostedGame, InitPayload, InventoryItem, InventoryItemWithItem, InventoryMutationResponse, Item, KeyEventPayload, LeaderboardEntry, LevelConfig, LoginResponse, ManifestV1, Map, MapElement, MapElementWithGame, MapObject, MapObjectWithItem, PlaycademyServerClientConfig, PlaycademyServerClientState, PlayerCharacter, RealtimeTokenResponse, ScoreSubmission, ShopCurrency, ShopDisplayItem, ShopViewResponse, SpriteTemplate, SpriteTemplateData, StartSessionResponse, StudentEnrollment, TelemetryPayload, TodayXpResponse, TokenRefreshPayload, TokenType, TotalXpResponse, UpdateCharacterData, User, UserInfo, UserLevel, UserLevelWithConfig, UserRank, UserRankResponse, UserRoleEnumType, UserScore, XpHistoryResponse, XpSummaryResponse };
6811
+ export type { Achievement, AchievementCurrent, AchievementProgressResponse, ApiErrorInfo, AuthCallbackPayload, AuthOptions, AuthProviderType, AuthResult, AuthServerMessage, AuthStateChangePayload, AuthStateUpdate, AuthenticatedUser, BetterAuthApiKey, BetterAuthApiKeyResponse, BetterAuthSignInResponse, BucketFile, CharacterComponent, CharacterComponentWithSpriteUrl, CharacterComponentsOptions, ClientConfig, ClientEvents, ConnectionMonitorConfig, ConnectionState, ConnectionStatePayload, CreateCharacterData, Currency, DevUploadEvent, DevUploadHooks, DeveloperStatusResponse, DisconnectContext, DisconnectHandler, DisplayAlertPayload, ErrorResponseBody, EventListeners, ExternalGame, FetchedGame, Game, GameContextPayload, GameLeaderboardEntry, GameSession, GameStateData, GameTokenResponse, GameUser, HostedGame, InitPayload, InventoryItem, InventoryItemWithItem, InventoryMutationResponse, Item, KeyEventPayload, LeaderboardEntry, LevelConfig, LoginResponse, ManifestV1, Map, MapElement, MapElementWithGame, MapObject, MapObjectWithItem, PlatformTimebackUser, PlatformTimebackUserContext, PlaycademyServerClientConfig, PlaycademyServerClientState, PlayerCharacter, RealtimeTokenResponse, ScoreSubmission, ShopCurrency, ShopDisplayItem, ShopViewResponse, SpriteTemplate, SpriteTemplateData, StartSessionResponse, TelemetryPayload, TimebackEnrollment, TimebackInitContext, TimebackOrganization, TimebackUser, TimebackUserContext, TimebackUserRole, TodayXpResponse, TokenRefreshPayload, TokenType, TotalXpResponse, UpdateCharacterData, User, UserEnrollment, UserInfo, UserLevel, UserLevelWithConfig, UserOrganization, UserRank, UserRankResponse, UserRoleEnumType, UserScore, UserTimebackData, XpHistoryResponse, XpSummaryResponse };