@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.
- package/README.md +20 -17
- package/dist/index.d.ts +247 -202
- package/dist/index.js +1280 -1381
- package/dist/internal.d.ts +2337 -2124
- package/dist/internal.js +2455 -2600
- package/dist/server.d.ts +6 -5
- package/dist/server.js +14 -1
- package/dist/types.d.ts +432 -220
- package/package.json +3 -3
package/dist/internal.d.ts
CHANGED
|
@@ -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
|
|
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
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
*/
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
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
|
-
*
|
|
3978
|
-
* Returned by the /users/me endpoint with additional auth context.
|
|
3692
|
+
* TimeBack enrollment information for a game.
|
|
3979
3693
|
*/
|
|
3980
|
-
type
|
|
3981
|
-
|
|
3982
|
-
|
|
3694
|
+
type UserEnrollment = {
|
|
3695
|
+
gameId?: string;
|
|
3696
|
+
courseId: string;
|
|
3697
|
+
grade: number;
|
|
3698
|
+
subject: string;
|
|
3699
|
+
orgId?: string;
|
|
3983
3700
|
};
|
|
3984
|
-
|
|
3985
3701
|
/**
|
|
3986
|
-
*
|
|
3987
|
-
* Types that combine data from multiple domains
|
|
3702
|
+
* TimeBack user role (matches OneRoster spec).
|
|
3988
3703
|
*/
|
|
3989
|
-
type
|
|
3704
|
+
type TimebackUserRole = 'administrator' | 'aide' | 'guardian' | 'parent' | 'proctor' | 'relative' | 'student' | 'teacher';
|
|
3990
3705
|
/**
|
|
3991
|
-
*
|
|
3706
|
+
* Organization type (matches OneRoster spec).
|
|
3992
3707
|
*/
|
|
3993
|
-
|
|
3994
|
-
|
|
3995
|
-
|
|
3996
|
-
|
|
3997
|
-
|
|
3998
|
-
|
|
3999
|
-
|
|
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
|
-
*
|
|
4391
|
-
|
|
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
|
-
*
|
|
4394
|
-
|
|
4395
|
-
|
|
4396
|
-
*
|
|
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
|
-
*
|
|
4400
|
-
*
|
|
4401
|
-
*
|
|
4402
|
-
|
|
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
|
-
* @
|
|
4405
|
-
|
|
4406
|
-
|
|
4407
|
-
|
|
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
|
-
*
|
|
4410
|
-
*
|
|
4411
|
-
* ```
|
|
4226
|
+
* @param state - The new connection state
|
|
4227
|
+
* @param reason - Human-readable reason for the state change
|
|
4412
4228
|
*/
|
|
4413
|
-
|
|
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
|
-
*
|
|
4232
|
+
* Connection Monitor
|
|
4422
4233
|
*
|
|
4423
|
-
*
|
|
4424
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
4429
|
-
*
|
|
4430
|
-
*
|
|
4431
|
-
*
|
|
4432
|
-
*
|
|
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
|
-
*
|
|
4437
|
-
*
|
|
4438
|
-
*
|
|
4439
|
-
*
|
|
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
|
-
*
|
|
4444
|
-
*
|
|
4445
|
-
*
|
|
4446
|
-
*
|
|
4447
|
-
*
|
|
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
|
|
4453
|
-
|
|
4454
|
-
|
|
4455
|
-
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
* @
|
|
4507
|
-
* @
|
|
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
|
-
|
|
4293
|
+
constructor(config: ConnectionMonitorConfig);
|
|
4510
4294
|
/**
|
|
4511
|
-
*
|
|
4295
|
+
* Starts monitoring the connection state.
|
|
4512
4296
|
*
|
|
4513
|
-
*
|
|
4297
|
+
* Sets up event listeners and begins heartbeat checks based on configuration.
|
|
4298
|
+
* Idempotent - safe to call multiple times.
|
|
4514
4299
|
*/
|
|
4515
|
-
|
|
4300
|
+
start(): void;
|
|
4516
4301
|
/**
|
|
4517
|
-
*
|
|
4518
|
-
* Emits an 'authChange' event when the token changes.
|
|
4302
|
+
* Stops monitoring the connection state and cleans up resources.
|
|
4519
4303
|
*
|
|
4520
|
-
*
|
|
4521
|
-
*
|
|
4304
|
+
* Removes event listeners and clears heartbeat intervals.
|
|
4305
|
+
* Idempotent - safe to call multiple times.
|
|
4522
4306
|
*/
|
|
4523
|
-
|
|
4307
|
+
stop(): void;
|
|
4524
4308
|
/**
|
|
4525
|
-
*
|
|
4309
|
+
* Registers a callback to be notified of all connection state changes.
|
|
4526
4310
|
*
|
|
4527
|
-
*
|
|
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
|
-
* @
|
|
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
|
-
*
|
|
4538
|
-
*
|
|
4539
|
-
*
|
|
4540
|
-
*
|
|
4541
|
-
*
|
|
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
|
-
|
|
4329
|
+
onChange(callback: ConnectionChangeCallback): () => void;
|
|
4546
4330
|
/**
|
|
4547
|
-
*
|
|
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
|
|
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
|
-
*
|
|
4560
|
-
*
|
|
4561
|
-
*
|
|
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
|
-
|
|
4352
|
+
checkNow(): Promise<ConnectionState>;
|
|
4568
4353
|
/**
|
|
4569
|
-
*
|
|
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
|
|
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
|
-
|
|
4375
|
+
reportRequestFailure(error: unknown): void;
|
|
4574
4376
|
/**
|
|
4575
|
-
*
|
|
4377
|
+
* Reports a successful request.
|
|
4576
4378
|
*
|
|
4577
|
-
* This
|
|
4578
|
-
*
|
|
4579
|
-
*
|
|
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
|
-
*
|
|
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
|
|
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
|
|
4589
|
-
*
|
|
4590
|
-
*
|
|
4591
|
-
*
|
|
4592
|
-
*
|
|
4593
|
-
*
|
|
4594
|
-
*
|
|
4595
|
-
*
|
|
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
|
-
|
|
4472
|
+
constructor(config: ConnectionManagerConfig);
|
|
4608
4473
|
/**
|
|
4609
4474
|
* Gets the current connection state.
|
|
4610
4475
|
*
|
|
4611
|
-
*
|
|
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 =
|
|
4480
|
+
* const state = manager.getState()
|
|
4619
4481
|
* if (state === 'offline') {
|
|
4620
|
-
* console.log('No connection
|
|
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
|
-
|
|
4486
|
+
getState(): ConnectionState;
|
|
4628
4487
|
/**
|
|
4629
4488
|
* Manually triggers a connection check immediately.
|
|
4630
4489
|
*
|
|
4631
|
-
* Forces a heartbeat ping to verify
|
|
4632
|
-
*
|
|
4633
|
-
*
|
|
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
|
|
4495
|
+
* @returns Promise resolving to the current connection state
|
|
4636
4496
|
*
|
|
4637
4497
|
* @example
|
|
4638
4498
|
* ```typescript
|
|
4639
|
-
*
|
|
4640
|
-
*
|
|
4641
|
-
*
|
|
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
|
-
|
|
4659
|
-
isInIframe: boolean;
|
|
4660
|
-
}): void;
|
|
4505
|
+
checkNow(): Promise<ConnectionState>;
|
|
4661
4506
|
/**
|
|
4662
|
-
*
|
|
4507
|
+
* Reports a successful API request to the connection monitor.
|
|
4663
4508
|
*
|
|
4664
|
-
*
|
|
4665
|
-
*
|
|
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
|
-
*
|
|
4672
|
-
*
|
|
4512
|
+
* Typically called automatically by the SDK's request wrapper.
|
|
4513
|
+
* No-op in iframe mode (platform handles monitoring).
|
|
4673
4514
|
*/
|
|
4674
|
-
|
|
4515
|
+
reportRequestSuccess(): void;
|
|
4675
4516
|
/**
|
|
4676
|
-
*
|
|
4517
|
+
* Reports a failed API request to the connection monitor.
|
|
4677
4518
|
*
|
|
4678
|
-
*
|
|
4679
|
-
*
|
|
4680
|
-
*
|
|
4681
|
-
*
|
|
4682
|
-
*
|
|
4683
|
-
*
|
|
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
|
|
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
|
-
|
|
4528
|
+
reportRequestFailure(error: unknown): void;
|
|
4703
4529
|
/**
|
|
4704
|
-
*
|
|
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
|
-
*
|
|
4707
|
-
*
|
|
4550
|
+
* // Later: cleanup() to unregister
|
|
4551
|
+
* ```
|
|
4708
4552
|
*/
|
|
4709
|
-
|
|
4553
|
+
onDisconnect(callback: DisconnectHandler): () => void;
|
|
4710
4554
|
/**
|
|
4711
|
-
*
|
|
4712
|
-
*
|
|
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
|
-
|
|
4560
|
+
stop(): void;
|
|
4715
4561
|
/**
|
|
4716
|
-
*
|
|
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
|
|
4564
|
+
private _setupPlatformListener;
|
|
4720
4565
|
/**
|
|
4721
|
-
*
|
|
4722
|
-
*
|
|
4723
|
-
*
|
|
4724
|
-
*
|
|
4725
|
-
*
|
|
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
|
|
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
|
-
*
|
|
4828
|
-
|
|
4829
|
-
|
|
4830
|
-
|
|
4831
|
-
|
|
4832
|
-
|
|
4833
|
-
|
|
4834
|
-
|
|
4835
|
-
|
|
4836
|
-
|
|
4837
|
-
|
|
4838
|
-
|
|
4839
|
-
|
|
4840
|
-
|
|
4841
|
-
*
|
|
4842
|
-
* Games
|
|
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
|
-
|
|
4600
|
+
|
|
4845
4601
|
/**
|
|
4846
|
-
*
|
|
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
|
-
|
|
4849
|
-
/**
|
|
4850
|
-
|
|
4851
|
-
|
|
4852
|
-
|
|
4853
|
-
|
|
4854
|
-
|
|
4855
|
-
|
|
4856
|
-
|
|
4857
|
-
|
|
4858
|
-
|
|
4859
|
-
|
|
4860
|
-
|
|
4861
|
-
|
|
4862
|
-
|
|
4863
|
-
|
|
4864
|
-
|
|
4865
|
-
|
|
4866
|
-
/**
|
|
4867
|
-
|
|
4868
|
-
|
|
4869
|
-
|
|
4870
|
-
|
|
4871
|
-
|
|
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
|
-
*
|
|
4875
|
-
*
|
|
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
|
-
|
|
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
|
-
*
|
|
4991
|
-
*
|
|
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
|
-
|
|
4994
|
-
/**
|
|
4995
|
-
|
|
4996
|
-
/**
|
|
4997
|
-
|
|
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
|
-
*
|
|
5001
|
-
*
|
|
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
|
-
|
|
5004
|
-
/**
|
|
5005
|
-
|
|
5006
|
-
|
|
5007
|
-
|
|
5008
|
-
|
|
5009
|
-
|
|
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
|
-
*
|
|
5013
|
-
*
|
|
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
|
-
|
|
5016
|
-
state: 'online' | 'offline' | 'degraded';
|
|
5017
|
-
reason: string;
|
|
5018
|
-
}
|
|
5141
|
+
declare const messaging: PlaycademyMessaging;
|
|
5019
5142
|
|
|
5020
5143
|
/**
|
|
5021
|
-
*
|
|
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
|
-
|
|
5024
|
-
|
|
5025
|
-
|
|
5026
|
-
|
|
5027
|
-
|
|
5028
|
-
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
5216
|
+
* Base interface for authentication strategies
|
|
5049
5217
|
*/
|
|
5050
|
-
interface
|
|
5051
|
-
|
|
5052
|
-
|
|
5053
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
5060
|
-
|
|
5061
|
-
|
|
5062
|
-
|
|
5063
|
-
|
|
5064
|
-
|
|
5065
|
-
|
|
5066
|
-
|
|
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
|
-
*
|
|
5365
|
+
* Playcademy SDK client for game developers.
|
|
5366
|
+
* Provides namespaced access to platform features for games running inside Cademy.
|
|
5071
5367
|
*/
|
|
5072
|
-
|
|
5073
|
-
|
|
5074
|
-
|
|
5075
|
-
|
|
5076
|
-
|
|
5077
|
-
|
|
5078
|
-
|
|
5079
|
-
|
|
5080
|
-
|
|
5081
|
-
|
|
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
|
-
*
|
|
5092
|
-
*
|
|
5093
|
-
*
|
|
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
|
-
|
|
5096
|
-
|
|
5097
|
-
|
|
5098
|
-
|
|
5099
|
-
|
|
5100
|
-
|
|
5101
|
-
|
|
5102
|
-
|
|
5103
|
-
|
|
5104
|
-
}
|
|
5105
|
-
|
|
5106
|
-
|
|
5107
|
-
|
|
5108
|
-
|
|
5109
|
-
|
|
5110
|
-
|
|
5111
|
-
|
|
5112
|
-
|
|
5113
|
-
|
|
5114
|
-
|
|
5115
|
-
|
|
5116
|
-
|
|
5117
|
-
|
|
5118
|
-
|
|
5119
|
-
|
|
5120
|
-
|
|
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
|
-
|
|
5123
|
-
|
|
5124
|
-
|
|
5125
|
-
|
|
5126
|
-
|
|
5127
|
-
|
|
5128
|
-
|
|
5129
|
-
|
|
5130
|
-
|
|
5131
|
-
|
|
5132
|
-
|
|
5133
|
-
|
|
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
|
-
*
|
|
5157
|
-
*
|
|
5442
|
+
* Playcademy Credits (platform currency) management.
|
|
5443
|
+
* - `get()` - Get user's credit balance
|
|
5444
|
+
* - `add(amount)` - Award credits to user
|
|
5158
5445
|
*/
|
|
5159
|
-
|
|
5446
|
+
credits: {
|
|
5447
|
+
balance: () => Promise<number>;
|
|
5448
|
+
add: (amount: number) => Promise<number>;
|
|
5449
|
+
spend: (amount: number) => Promise<number>;
|
|
5450
|
+
};
|
|
5160
5451
|
/**
|
|
5161
|
-
*
|
|
5162
|
-
*
|
|
5452
|
+
* Game score submission and leaderboards.
|
|
5453
|
+
* - `submit(gameId, score, metadata?)` - Record a game score
|
|
5163
5454
|
*/
|
|
5164
|
-
|
|
5165
|
-
|
|
5166
|
-
|
|
5167
|
-
|
|
5168
|
-
|
|
5169
|
-
|
|
5170
|
-
|
|
5171
|
-
|
|
5172
|
-
|
|
5173
|
-
|
|
5174
|
-
|
|
5175
|
-
|
|
5176
|
-
|
|
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
|
-
*
|
|
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
|
-
*
|
|
5500
|
+
* A TimeBack enrollment for the current game session.
|
|
5501
|
+
* Alias for UserEnrollment without the optional gameId.
|
|
5184
5502
|
*/
|
|
5185
|
-
|
|
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
|
-
*
|
|
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
|
-
*
|
|
5224
|
-
*
|
|
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
|
|
5227
|
-
/**
|
|
5228
|
-
|
|
5229
|
-
/**
|
|
5230
|
-
|
|
5231
|
-
/**
|
|
5232
|
-
|
|
5233
|
-
/**
|
|
5234
|
-
|
|
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
|
-
*
|
|
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
|
|
5240
|
-
|
|
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
|
-
*
|
|
5538
|
+
* TimeBack user object with both cached getters and fetch method.
|
|
5244
5539
|
*/
|
|
5245
|
-
interface
|
|
5246
|
-
|
|
5247
|
-
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
|
5264
|
-
/**
|
|
5265
|
-
|
|
5266
|
-
/**
|
|
5267
|
-
|
|
5268
|
-
/**
|
|
5269
|
-
|
|
5270
|
-
/**
|
|
5271
|
-
|
|
5272
|
-
|
|
5273
|
-
|
|
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
|
-
|
|
5277
|
-
|
|
5278
|
-
*/
|
|
5279
|
-
|
|
5280
|
-
|
|
5281
|
-
|
|
5282
|
-
|
|
5283
|
-
|
|
5284
|
-
|
|
5285
|
-
|
|
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
|
-
*
|
|
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
|
|
5297
|
-
/**
|
|
5298
|
-
|
|
5299
|
-
/**
|
|
5300
|
-
|
|
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
|
-
|
|
5304
|
-
|
|
5305
|
-
|
|
5306
|
-
|
|
5307
|
-
|
|
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
|
-
*
|
|
5656
|
+
* Event and message payload types for SDK messaging system
|
|
5311
5657
|
*/
|
|
5312
|
-
|
|
5313
|
-
|
|
5314
|
-
|
|
5658
|
+
|
|
5659
|
+
interface DisplayAlertPayload {
|
|
5660
|
+
message: string;
|
|
5661
|
+
options?: {
|
|
5662
|
+
type?: 'info' | 'warning' | 'error';
|
|
5663
|
+
duration?: number;
|
|
5664
|
+
};
|
|
5315
5665
|
}
|
|
5316
5666
|
/**
|
|
5317
|
-
*
|
|
5318
|
-
*
|
|
5667
|
+
* Authentication state change event payload.
|
|
5668
|
+
* Used when authentication state changes in the application.
|
|
5319
5669
|
*/
|
|
5320
|
-
interface
|
|
5321
|
-
/**
|
|
5322
|
-
|
|
5323
|
-
/**
|
|
5324
|
-
|
|
5325
|
-
/**
|
|
5326
|
-
|
|
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
|
-
*
|
|
5336
|
-
* Used
|
|
5679
|
+
* OAuth callback event payload.
|
|
5680
|
+
* Used when OAuth flow completes in popup/new-tab windows.
|
|
5337
5681
|
*/
|
|
5338
|
-
interface
|
|
5339
|
-
/**
|
|
5340
|
-
|
|
5341
|
-
/**
|
|
5342
|
-
|
|
5343
|
-
/**
|
|
5344
|
-
|
|
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
|
-
*
|
|
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
|
|
5372
|
-
/**
|
|
5373
|
-
|
|
5374
|
-
|
|
5375
|
-
|
|
5376
|
-
|
|
5377
|
-
|
|
5378
|
-
|
|
5379
|
-
|
|
5380
|
-
|
|
5381
|
-
|
|
5382
|
-
|
|
5383
|
-
|
|
5384
|
-
|
|
5385
|
-
|
|
5386
|
-
|
|
5387
|
-
|
|
5388
|
-
|
|
5389
|
-
|
|
5390
|
-
|
|
5391
|
-
|
|
5392
|
-
/**
|
|
5393
|
-
|
|
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
|
-
*
|
|
5409
|
-
*
|
|
5410
|
-
* @internal
|
|
5719
|
+
* Telemetry event payload.
|
|
5720
|
+
* Performance metrics sent from the game.
|
|
5411
5721
|
*/
|
|
5412
|
-
interface
|
|
5413
|
-
/**
|
|
5414
|
-
|
|
5415
|
-
/**
|
|
5416
|
-
|
|
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
|
-
*
|
|
5430
|
-
*
|
|
5729
|
+
* Keyboard event payload.
|
|
5730
|
+
* Key events forwarded from the game.
|
|
5431
5731
|
*/
|
|
5432
|
-
interface
|
|
5433
|
-
/**
|
|
5434
|
-
|
|
5435
|
-
/** Key
|
|
5436
|
-
|
|
5437
|
-
/**
|
|
5438
|
-
|
|
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
|
-
*
|
|
5741
|
+
* Connection state payload.
|
|
5742
|
+
* Broadcast from platform to games when connection changes.
|
|
5442
5743
|
*/
|
|
5443
|
-
interface
|
|
5444
|
-
|
|
5445
|
-
|
|
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
|
-
*
|
|
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
|
-
*
|
|
5767
|
+
* Realtime namespace types
|
|
5468
5768
|
*/
|
|
5469
|
-
|
|
5470
|
-
|
|
5471
|
-
|
|
5472
|
-
|
|
5473
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
5498
|
-
|
|
5499
|
-
|
|
5500
|
-
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
|
|
5670
|
-
|
|
5671
|
-
|
|
5672
|
-
|
|
5673
|
-
|
|
5674
|
-
|
|
5675
|
-
|
|
5676
|
-
|
|
5677
|
-
|
|
5678
|
-
|
|
5679
|
-
|
|
5680
|
-
|
|
5681
|
-
|
|
5682
|
-
|
|
5683
|
-
|
|
5684
|
-
|
|
5685
|
-
|
|
5686
|
-
|
|
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
|
-
*
|
|
5741
|
-
*
|
|
5742
|
-
*
|
|
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
|
-
|
|
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
|
-
*
|
|
5750
|
-
*
|
|
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
|
-
|
|
5888
|
+
level?: number;
|
|
5756
5889
|
/**
|
|
5757
|
-
*
|
|
5758
|
-
*
|
|
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
|
-
|
|
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
|
-
*
|
|
5767
|
-
*
|
|
5768
|
-
*
|
|
5769
|
-
*
|
|
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
|
-
|
|
5975
|
+
fetch(options?: {
|
|
5976
|
+
force?: boolean;
|
|
5977
|
+
}): Promise<PlatformTimebackUserContext>;
|
|
5774
5978
|
}
|
|
5775
5979
|
/**
|
|
5776
|
-
*
|
|
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
|
-
|
|
5982
|
+
interface XpSummaryResponse {
|
|
5983
|
+
today: TodayXpResponse;
|
|
5984
|
+
total: TotalXpResponse;
|
|
5985
|
+
}
|
|
5986
|
+
|
|
5783
5987
|
/**
|
|
5784
|
-
*
|
|
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
|
-
*
|
|
5793
|
-
*
|
|
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
|
-
|
|
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
|
-
*
|
|
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
|
-
*
|
|
5849
|
-
|
|
5850
|
-
|
|
5851
|
-
|
|
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
|
-
*
|
|
5854
|
-
*
|
|
5855
|
-
*
|
|
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
|
-
*
|
|
5859
|
-
* messaging.unlisten(MessageEvents.INIT, handler)
|
|
5860
|
-
* ```
|
|
6031
|
+
* Template variables ({grade}, {subject}, {gameSlug}) can be used in string fields.
|
|
5861
6032
|
*/
|
|
5862
|
-
|
|
5863
|
-
/**
|
|
5864
|
-
|
|
5865
|
-
|
|
5866
|
-
|
|
5867
|
-
|
|
5868
|
-
|
|
5869
|
-
|
|
5870
|
-
|
|
5871
|
-
|
|
5872
|
-
|
|
5873
|
-
|
|
5874
|
-
|
|
5875
|
-
|
|
5876
|
-
|
|
5877
|
-
|
|
5878
|
-
|
|
5879
|
-
|
|
5880
|
-
|
|
5881
|
-
|
|
5882
|
-
|
|
5883
|
-
|
|
5884
|
-
|
|
5885
|
-
|
|
5886
|
-
|
|
5887
|
-
|
|
5888
|
-
|
|
5889
|
-
|
|
5890
|
-
|
|
5891
|
-
|
|
5892
|
-
|
|
5893
|
-
|
|
5894
|
-
|
|
5895
|
-
|
|
5896
|
-
|
|
5897
|
-
|
|
5898
|
-
|
|
5899
|
-
|
|
5900
|
-
|
|
5901
|
-
|
|
5902
|
-
|
|
5903
|
-
|
|
5904
|
-
|
|
5905
|
-
|
|
5906
|
-
|
|
5907
|
-
|
|
5908
|
-
|
|
5909
|
-
|
|
5910
|
-
|
|
5911
|
-
|
|
5912
|
-
|
|
5913
|
-
|
|
5914
|
-
|
|
5915
|
-
|
|
5916
|
-
|
|
5917
|
-
|
|
5918
|
-
|
|
5919
|
-
|
|
5920
|
-
|
|
5921
|
-
|
|
5922
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
6113
|
+
apiKey: string;
|
|
5964
6114
|
/**
|
|
5965
|
-
*
|
|
5966
|
-
*
|
|
5967
|
-
*
|
|
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
|
-
*
|
|
5999
|
-
* messaging.unlisten(MessageEvents.INIT, handleInit) // No error
|
|
6000
|
-
* ```
|
|
6119
|
+
* @example './config/playcademy.config.js'
|
|
6001
6120
|
*/
|
|
6002
|
-
|
|
6121
|
+
configPath?: string;
|
|
6003
6122
|
/**
|
|
6004
|
-
*
|
|
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
|
-
*
|
|
6044
|
-
* const context = getMessagingContext(MessageEvents.INIT)
|
|
6045
|
-
* // Returns: { shouldUsePostMessage: false, target: undefined, origin: '*' }
|
|
6046
|
-
* ```
|
|
6126
|
+
* @example { name: 'My Game', integrations: { timeback: {...} } }
|
|
6047
6127
|
*/
|
|
6048
|
-
|
|
6128
|
+
config?: PlaycademyConfig;
|
|
6049
6129
|
/**
|
|
6050
|
-
*
|
|
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
|
-
*
|
|
6090
|
-
* sendViaPostMessage(MessageEvents.OVERLAY, true)
|
|
6091
|
-
* // Sends: { type: 'PLAYCADEMY_OVERLAY', payload: true }
|
|
6092
|
-
* ```
|
|
6133
|
+
* @example 'http://localhost:3000' for local development
|
|
6093
6134
|
*/
|
|
6094
|
-
|
|
6135
|
+
baseUrl?: string;
|
|
6095
6136
|
/**
|
|
6096
|
-
*
|
|
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
|
-
*
|
|
6138
|
-
* window.addEventListener('PLAYCADEMY_INIT', (event) => {
|
|
6139
|
-
* console.log(event.detail.gameId) // 'game-456'
|
|
6140
|
-
* })
|
|
6141
|
-
* ```
|
|
6140
|
+
* @example 'my-math-game'
|
|
6142
6141
|
*/
|
|
6143
|
-
|
|
6142
|
+
gameId?: string;
|
|
6144
6143
|
}
|
|
6145
6144
|
/**
|
|
6146
|
-
*
|
|
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
|
-
*
|
|
6185
|
-
|
|
6186
|
-
|
|
6187
|
-
|
|
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
|
-
*
|
|
6190
|
-
*
|
|
6200
|
+
* @param state - The OAuth state parameter to parse
|
|
6201
|
+
* @returns Object containing CSRF token and optional decoded data
|
|
6191
6202
|
*/
|
|
6192
|
-
declare
|
|
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
|
|
6234
|
+
* For use by Cademy platform, CLI, and admin tools.
|
|
6197
6235
|
*
|
|
6198
|
-
* Extends
|
|
6236
|
+
* Extends PlaycademyBaseClient directly (not PlaycademyClient) to allow
|
|
6237
|
+
* independent namespace implementations without type conflicts.
|
|
6199
6238
|
*/
|
|
6200
|
-
declare class PlaycademyInternalClient extends
|
|
6201
|
-
/**
|
|
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
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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
|
-
|
|
6591
|
-
get: (timebackId: string, options?: TTLCacheConfig) => Promise<
|
|
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,
|
|
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 };
|