@playcademy/sdk 0.9.1-beta.2 → 0.10.0
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 +109 -602
- package/dist/index.d.ts +19 -1068
- package/dist/index.js +110 -754
- package/dist/internal.d.ts +2021 -7090
- package/dist/internal.js +171 -1163
- package/dist/server/edge.d.ts +9 -359
- package/dist/server.d.ts +9 -359
- package/dist/types.d.ts +550 -4692
- package/package.json +2 -3
package/README.md
CHANGED
|
@@ -1,708 +1,215 @@
|
|
|
1
1
|
# @playcademy/sdk
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
The Playcademy SDK provides a comprehensive, type-safe interface for building games on the Playcademy platform. It handles authentication, game sessions, user data, inventory management, and all platform APIs through a unified client interface.
|
|
3
|
+
TypeScript SDK for building games on the Playcademy platform.
|
|
6
4
|
|
|
7
5
|
## Overview
|
|
8
6
|
|
|
9
|
-
The SDK
|
|
10
|
-
|
|
11
|
-
- **Automatic Environment Detection**: Seamlessly works in development and production
|
|
12
|
-
- **Type-Safe API Access**: Full TypeScript support with comprehensive type definitions
|
|
13
|
-
- **Session Management**: Automatic game session handling and state persistence
|
|
14
|
-
- **Event System**: Real-time notifications for inventory changes, level ups, and more
|
|
15
|
-
- **Developer Tools**: Built-in support for game development and testing workflows
|
|
16
|
-
|
|
17
|
-
### Public vs Internal SDK
|
|
7
|
+
The SDK is the interface between your game and the Playcademy platform. It provides:
|
|
18
8
|
|
|
19
|
-
|
|
9
|
+
- **Automatic Environment Detection** — works in development (sandbox) and production (platform loader)
|
|
10
|
+
- **Type-Safe API Access** — full TypeScript support
|
|
11
|
+
- **Activity Tracking** — TimeBack integration with automatic heartbeats and idle detection
|
|
12
|
+
- **Developer Tools** — game deployment, secrets, database, and asset management
|
|
20
13
|
|
|
21
|
-
|
|
14
|
+
### Entry Points
|
|
22
15
|
|
|
23
|
-
|
|
16
|
+
| Export | Use |
|
|
17
|
+
| -------------------------- | ---------------------------------------------------------------------------------------- |
|
|
18
|
+
| `@playcademy/sdk` | Game SDK — used by games running on the platform |
|
|
19
|
+
| `@playcademy/sdk/internal` | Platform SDK — used by CLI, admin tools, and platform internals |
|
|
20
|
+
| `@playcademy/sdk/server` | Server SDK — used by game backends (token verification, server-side activity submission) |
|
|
21
|
+
| `@playcademy/sdk/types` | Type-only imports |
|
|
22
|
+
| `@playcademy/sdk/test` | Test utilities |
|
|
24
23
|
|
|
25
|
-
|
|
26
|
-
- **Economy**: `credits`
|
|
27
|
-
- **Gameplay**: `scores`
|
|
28
|
-
- **Multiplayer**: `realtime`
|
|
29
|
-
- **Integrations**: `timeback`
|
|
24
|
+
## Quick Start
|
|
30
25
|
|
|
31
26
|
```typescript
|
|
32
27
|
import { PlaycademyClient } from '@playcademy/sdk'
|
|
33
28
|
|
|
34
29
|
const client = await PlaycademyClient.init()
|
|
35
|
-
await client.timeback.endActivity({
|
|
36
|
-
/* ... */
|
|
37
|
-
})
|
|
38
|
-
await client.users.inventory.add('gold-coin', 100)
|
|
39
|
-
```
|
|
40
|
-
|
|
41
|
-
#### Internal SDK (`@playcademy/sdk/internal`)
|
|
42
|
-
|
|
43
|
-
For CLI, platform, and admin tools. Includes all 21 namespaces (8 public + 13 internal):
|
|
44
|
-
|
|
45
|
-
**Additional internal namespaces:**
|
|
46
|
-
|
|
47
|
-
- **Platform Auth**: `auth` (email/password login, API keys)
|
|
48
|
-
- **Administration**: `admin` (game management, items, currencies)
|
|
49
|
-
- **Developer Tools**: `dev` (publish games, uploads)
|
|
50
|
-
- **Game Directory**: `games` (fetch, list, sessions)
|
|
51
|
-
- **Overworld Features**: `character`, `achievements`, `leaderboard`, `levels`, `shop`, `maps`, `sprites`, `notifications`
|
|
52
|
-
- **Analytics**: `telemetry`
|
|
53
|
-
|
|
54
|
-
```typescript
|
|
55
|
-
import { PlaycademyClient } from '@playcademy/sdk/internal'
|
|
56
30
|
|
|
57
|
-
const
|
|
58
|
-
|
|
59
|
-
const games = await client.games.list()
|
|
60
|
-
await client.admin.games.pauseGame('game-123')
|
|
31
|
+
const user = await client.users.me()
|
|
32
|
+
console.log('Welcome,', user.displayName)
|
|
61
33
|
```
|
|
62
34
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
### Key Benefits
|
|
66
|
-
|
|
67
|
-
- **Zero Configuration**: Automatic initialization with environment detection
|
|
68
|
-
- **Production Ready**: Battle-tested API patterns with robust error handling
|
|
69
|
-
- **Real-Time Communication**: Open game-scoped WebSocket channels for multiplayer features.
|
|
70
|
-
- **Event System**: Subscribe to platform events like inventory changes and level ups
|
|
71
|
-
- **Comprehensive Coverage**: Access to all Playcademy platform features
|
|
72
|
-
- **Development Experience**: Integrated with sandbox environment for local development
|
|
73
|
-
|
|
74
|
-
### Use Cases
|
|
75
|
-
|
|
76
|
-
- **Game Development**: Primary SDK for building games on Playcademy
|
|
77
|
-
- **Web Applications**: Frontend applications interacting with the platform
|
|
78
|
-
- **Developer Tools**: Scripts and utilities for game management
|
|
79
|
-
- **Server Integration**: Backend services integrating with Playcademy APIs
|
|
80
|
-
- **Testing & Automation**: Automated testing of platform integrations
|
|
81
|
-
|
|
82
|
-
### Connection Monitoring
|
|
83
|
-
|
|
84
|
-
The SDK automatically monitors network connectivity and provides hooks for games to handle disconnects gracefully:
|
|
35
|
+
In development, the Vite plugin starts a local sandbox automatically. In production, the platform loader provides configuration via `postMessage`.
|
|
85
36
|
|
|
86
|
-
|
|
87
|
-
- **Game Handlers**: Implement custom disconnect behavior (e.g., return to lobby, pause game)
|
|
88
|
-
- **Platform Integration**: Built-in helpers for displaying connection alerts
|
|
89
|
-
- **Zero Config**: Works out of the box, customizable when needed
|
|
37
|
+
## Game SDK Namespaces
|
|
90
38
|
|
|
91
|
-
|
|
39
|
+
### `client.users`
|
|
92
40
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
Install the SDK using your preferred package manager:
|
|
96
|
-
|
|
97
|
-
```bash
|
|
98
|
-
# Using Bun (recommended)
|
|
99
|
-
bun add @playcademy/sdk
|
|
100
|
-
|
|
101
|
-
# Using npm
|
|
102
|
-
npm install @playcademy/sdk
|
|
103
|
-
|
|
104
|
-
# Using yarn
|
|
105
|
-
yarn add @playcademy/sdk
|
|
106
|
-
|
|
107
|
-
# Using pnpm
|
|
108
|
-
pnpm add @playcademy/sdk
|
|
109
|
-
```
|
|
110
|
-
|
|
111
|
-
## Quick Start
|
|
112
|
-
|
|
113
|
-
### Automatic Initialization (Recommended)
|
|
114
|
-
|
|
115
|
-
For most game development scenarios, use automatic initialization:
|
|
41
|
+
User profile access.
|
|
116
42
|
|
|
117
43
|
```typescript
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
async function initializeGame() {
|
|
121
|
-
try {
|
|
122
|
-
// Automatic initialization - detects environment and configures appropriately
|
|
123
|
-
const client = await PlaycademyClient.init({
|
|
124
|
-
// Optional: Handle connection issues gracefully
|
|
125
|
-
onDisconnect: ({ state, displayAlert }) => {
|
|
126
|
-
if (state === 'offline') {
|
|
127
|
-
displayAlert?.('Connection lost. Reconnecting...', { type: 'warning' })
|
|
128
|
-
// Return to safe state (e.g., lobby)
|
|
129
|
-
}
|
|
130
|
-
},
|
|
131
|
-
})
|
|
132
|
-
|
|
133
|
-
// Get current user
|
|
134
|
-
const user = await client.users.me()
|
|
135
|
-
console.log('Welcome,', user.name)
|
|
136
|
-
|
|
137
|
-
// The client is ready for all platform operations
|
|
138
|
-
return client
|
|
139
|
-
} catch (error) {
|
|
140
|
-
console.error('Failed to initialize Playcademy SDK:', error)
|
|
141
|
-
throw error
|
|
142
|
-
}
|
|
143
|
-
}
|
|
44
|
+
const user = await client.users.me()
|
|
144
45
|
```
|
|
145
46
|
|
|
146
|
-
###
|
|
147
|
-
|
|
148
|
-
The SDK automatically detects and configures for different environments:
|
|
149
|
-
|
|
150
|
-
- **Development**: Connects to local sandbox (started by `@playcademy/vite-plugin`)
|
|
151
|
-
- **Production**: Receives configuration from Playcademy platform loader
|
|
152
|
-
- **Testing**: Falls back to mock configuration for automated testing
|
|
153
|
-
|
|
154
|
-
## Core Features
|
|
47
|
+
### `client.runtime`
|
|
155
48
|
|
|
156
|
-
|
|
49
|
+
Game lifecycle and platform communication.
|
|
157
50
|
|
|
158
51
|
```typescript
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
// Save transient game state (position, health, temporary data)
|
|
163
|
-
await client.games.saveState({
|
|
164
|
-
currentLevel: 'forest_glade',
|
|
165
|
-
playerPosition: { x: 100, y: 200 },
|
|
166
|
-
health: 85,
|
|
167
|
-
activePowerUps: ['speed_boost'],
|
|
168
|
-
})
|
|
52
|
+
client.runtime.ready()
|
|
53
|
+
client.runtime.exit()
|
|
54
|
+
client.runtime.sendTelemetry({ fps: 60, mem: 128 })
|
|
169
55
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
console.log('Loaded state:', gameState)
|
|
173
|
-
|
|
174
|
-
// Exit game (automatically ends session if managed)
|
|
175
|
-
await client.runtime.exit()
|
|
56
|
+
client.runtime.onPause(() => pauseGame())
|
|
57
|
+
client.runtime.onResume(() => resumeGame())
|
|
176
58
|
```
|
|
177
59
|
|
|
178
|
-
|
|
60
|
+
**Assets** — build CDN URLs for game assets uploaded via `playcademy deploy`:
|
|
179
61
|
|
|
180
62
|
```typescript
|
|
181
|
-
|
|
182
|
-
const
|
|
183
|
-
|
|
184
|
-
// Inventory operations (accepts UUIDs or slugs)
|
|
185
|
-
const inventory = await client.users.inventory.get()
|
|
186
|
-
await client.users.inventory.add('magic-sword', 1)
|
|
187
|
-
await client.users.inventory.remove('health-potion', 1)
|
|
188
|
-
|
|
189
|
-
// Check item quantities and ownership
|
|
190
|
-
const goldCount = await client.users.inventory.quantity('gold-coin')
|
|
191
|
-
const hasKey = await client.users.inventory.has('dungeon-key')
|
|
192
|
-
const hasEnoughGold = await client.users.inventory.has('gold-coin', 100)
|
|
63
|
+
const url = client.runtime.assets.url`sprites/player.png`
|
|
64
|
+
const data = await client.runtime.assets.json('config/levels.json')
|
|
193
65
|
```
|
|
194
66
|
|
|
195
|
-
###
|
|
67
|
+
### `client.scores`
|
|
68
|
+
|
|
69
|
+
Score submission for the current game.
|
|
196
70
|
|
|
197
71
|
```typescript
|
|
198
|
-
|
|
199
|
-
const balance = await client.credits.balance()
|
|
200
|
-
await client.credits.add(100)
|
|
201
|
-
await client.credits.spend(50)
|
|
202
|
-
|
|
203
|
-
// Check affordability
|
|
204
|
-
if ((await client.credits.balance()) >= 100) {
|
|
205
|
-
await client.credits.spend(100)
|
|
206
|
-
console.log('Purchase successful!')
|
|
207
|
-
}
|
|
72
|
+
await client.scores.submit(1250, { level: 'forest', time: 42 })
|
|
208
73
|
```
|
|
209
74
|
|
|
210
|
-
###
|
|
75
|
+
### `client.leaderboard`
|
|
211
76
|
|
|
212
|
-
|
|
213
|
-
// Level management
|
|
214
|
-
const userLevel = await client.levels.get()
|
|
215
|
-
const progress = await client.levels.progress()
|
|
216
|
-
console.log(`Level ${userLevel.currentLevel}, ${progress.xpToNextLevel} XP to next level`)
|
|
77
|
+
Leaderboard queries for the current game.
|
|
217
78
|
|
|
218
|
-
|
|
219
|
-
|
|
79
|
+
```typescript
|
|
80
|
+
const entries = await client.leaderboard.fetch({
|
|
81
|
+
timeframe: 'weekly',
|
|
82
|
+
limit: 10,
|
|
83
|
+
})
|
|
220
84
|
```
|
|
221
85
|
|
|
222
|
-
###
|
|
86
|
+
### `client.timeback`
|
|
223
87
|
|
|
224
|
-
|
|
88
|
+
TimeBack LMS integration — activity tracking with automatic heartbeats, idle detection, and pause/resume tied to tab visibility.
|
|
225
89
|
|
|
226
90
|
```typescript
|
|
227
|
-
|
|
228
|
-
const
|
|
229
|
-
const enrollments = client.timeback.enrollments // [{ subject, grade, courseId }]
|
|
230
|
-
|
|
231
|
-
// Check if user is enrolled in a specific grade
|
|
232
|
-
const grade3Enrollment = enrollments.find(e => e.grade === 3)
|
|
233
|
-
if (grade3Enrollment) {
|
|
234
|
-
// Show grade 3 content
|
|
235
|
-
}
|
|
91
|
+
const role = client.timeback.user.role
|
|
92
|
+
const enrollments = client.timeback.user.enrollments
|
|
236
93
|
|
|
237
|
-
|
|
238
|
-
const { runId } = client.timeback.startActivity({
|
|
94
|
+
const { runId } = await client.timeback.startActivity({
|
|
239
95
|
activityId: 'math-quiz-level-1',
|
|
240
96
|
})
|
|
241
|
-
// Auto-derived: activityName "Math Quiz Level 1"
|
|
242
|
-
// Auto-filled by backend: appName, subject, sensorUrl
|
|
243
|
-
// Returns the runId used by heartbeat and endActivity events
|
|
244
|
-
// Automatically pauses active time when the tab is hidden, the shell pauses,
|
|
245
|
-
// or the player has no visible keyboard/mouse activity for 10 minutes
|
|
246
|
-
|
|
247
|
-
// Customize inactivity handling
|
|
248
|
-
client.timeback.startActivity(
|
|
249
|
-
{ activityId: 'math-quiz-level-1' },
|
|
250
|
-
{
|
|
251
|
-
inactivityTimeoutMs: 5 * 60 * 1000, // 5 minutes
|
|
252
|
-
heartbeatIntervalMs: 15_000,
|
|
253
|
-
},
|
|
254
|
-
)
|
|
255
|
-
|
|
256
|
-
// Disable keyboard/mouse inactivity tracking
|
|
257
|
-
client.timeback.startActivity(
|
|
258
|
-
{ activityId: 'math-quiz-level-1' },
|
|
259
|
-
{ inactivityTimeoutMs: Infinity },
|
|
260
|
-
)
|
|
261
97
|
|
|
262
|
-
//
|
|
98
|
+
// Activity auto-pauses on tab hide, shell pause, or 10min keyboard/mouse idle.
|
|
99
|
+
// Heartbeats are sent automatically.
|
|
263
100
|
|
|
264
|
-
// End activity and submit results (XP calculated automatically)
|
|
265
101
|
await client.timeback.endActivity({
|
|
266
102
|
correctQuestions: 8,
|
|
267
103
|
totalQuestions: 10,
|
|
268
104
|
})
|
|
269
|
-
// XP calculation: base (1 min = 1 XP) × accuracy multiplier
|
|
270
|
-
// 100%: 1.25x | 80-99%: 1.0x | 65-79%: 0.5x | <65%: 0x
|
|
271
105
|
```
|
|
272
106
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
Get authentication tokens for WebSocket connections used by the platform's multiplayer system.
|
|
107
|
+
Options for `startActivity`:
|
|
276
108
|
|
|
277
109
|
```typescript
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
110
|
+
await client.timeback.startActivity(
|
|
111
|
+
{ activityId: 'math-quiz-level-1' },
|
|
112
|
+
{
|
|
113
|
+
inactivityTimeoutMs: 5 * 60 * 1000, // custom idle timeout (default: 10min)
|
|
114
|
+
heartbeatIntervalMs: 15_000, // heartbeat frequency
|
|
115
|
+
},
|
|
116
|
+
)
|
|
283
117
|
```
|
|
284
118
|
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
### Core Modules
|
|
288
|
-
|
|
289
|
-
#### **Authentication** (`client.auth`)
|
|
290
|
-
|
|
291
|
-
- `logout()`: Logs out user and clears authentication token
|
|
292
|
-
|
|
293
|
-
#### **Users** (`client.users`)
|
|
294
|
-
|
|
295
|
-
- `me()`: Get current user information
|
|
296
|
-
- **Inventory** (`client.users.inventory`):
|
|
297
|
-
- `get()`: Get user's inventory
|
|
298
|
-
- `add(identifier, quantity)`: Add items to inventory
|
|
299
|
-
- `remove(identifier, quantity)`: Remove items from inventory
|
|
300
|
-
- `quantity(identifier)`: Get item quantity
|
|
301
|
-
- `has(identifier, minQuantity?)`: Check item ownership
|
|
302
|
-
|
|
303
|
-
#### **Games** (`client.games`)
|
|
304
|
-
|
|
305
|
-
- `list()`: Get all available games
|
|
306
|
-
- `fetch(gameIdOrSlug)`: Get specific game details
|
|
307
|
-
- `saveState(state)`: Save transient game state
|
|
308
|
-
- `loadState()`: Load saved game state
|
|
309
|
-
- `startSession(gameId?)`: Start game session
|
|
310
|
-
- `endSession(sessionId, gameId?)`: End game session
|
|
311
|
-
|
|
312
|
-
#### **Credits** (`client.credits`)
|
|
313
|
-
|
|
314
|
-
- `balance()`: Get current credits balance
|
|
315
|
-
- `add(amount)`: Add credits to user
|
|
316
|
-
- `spend(amount)`: Spend user credits
|
|
317
|
-
|
|
318
|
-
#### **Levels** (`client.levels`)
|
|
319
|
-
|
|
320
|
-
- `get()`: Get current user level information
|
|
321
|
-
- `progress()`: Get level progress and XP to next level
|
|
322
|
-
- `addXP(amount)`: Add experience points
|
|
323
|
-
- **Config** (`client.levels.config`):
|
|
324
|
-
- `list()`: Get all level configurations
|
|
325
|
-
- `get(level)`: Get specific level configuration
|
|
326
|
-
|
|
327
|
-
#### **Maps** (`client.maps`)
|
|
328
|
-
|
|
329
|
-
- `elements(mapId)`: Get map elements and points of interest
|
|
330
|
-
|
|
331
|
-
#### **Runtime** (`client.runtime`)
|
|
332
|
-
|
|
333
|
-
- `getGameToken(gameId, options?)`: Get game-specific authentication token
|
|
334
|
-
- `exit()`: Signal platform to exit game view
|
|
335
|
-
|
|
336
|
-
#### **Real-time** (`client.realtime`)
|
|
337
|
-
|
|
338
|
-
- `token.get()`: Retrieves a JWT for WebSocket authentication (used by the platform's multiplayer system).
|
|
339
|
-
|
|
340
|
-
#### **TimeBack** (`client.timeback`)
|
|
341
|
-
|
|
342
|
-
- `role`: The user's TimeBack role (`'student'`, `'parent'`, `'teacher'`, or `'administrator'`)
|
|
343
|
-
- `enrollments`: Array of course enrollments with `subject`, `grade`, and `courseId`
|
|
344
|
-
- `currentRunId`: Active activity run ID, or `undefined` when no activity is active
|
|
345
|
-
- `startActivity(metadata, options?)`: Start tracking an activity and return `{ runId }`
|
|
346
|
-
- `metadata.activityId`: Unique activity identifier (required)
|
|
347
|
-
- `metadata.activityName`: Human-readable activity name
|
|
348
|
-
- `metadata.subject`: Subject area (Math, Reading, Science, etc.)
|
|
349
|
-
- `metadata.appName`: Application name
|
|
350
|
-
- `metadata.sensorUrl`: Sensor URL for tracking
|
|
351
|
-
- Automatically pauses active time when the tab is hidden, the shell pauses the game, or the player is idle while visible
|
|
352
|
-
- `options.pausedHeartbeatTimeoutMs`: Stop periodic heartbeats after a long automatic pause (`hidden` or `inactivity`); `Infinity` keeps them running
|
|
353
|
-
- `options.heartbeatIntervalMs`: Flush incremental heartbeats (`Infinity` disables periodic heartbeats)
|
|
354
|
-
- `options.inactivityTimeoutMs`: Keyboard/mouse idle timeout while visible (defaults to 10 minutes; `Infinity` disables)
|
|
355
|
-
- `options.runId`: Stable UUID to reuse for resumable activities
|
|
356
|
-
- `endActivity(scoreData)`: End activity and submit results
|
|
357
|
-
- `scoreData.correctQuestions`: Number of correct answers
|
|
358
|
-
- `scoreData.totalQuestions`: Total number of questions
|
|
359
|
-
- **XP Query** (`client.timeback.xp`):
|
|
360
|
-
- `today(options?)`: Get today's XP (supports timezone parameter)
|
|
361
|
-
- `total()`: Get total accumulated XP
|
|
362
|
-
- `history(options?)`: Get XP history with optional date filtering
|
|
363
|
-
- `summary(options?)`: Get both today's and total XP in one call
|
|
364
|
-
|
|
365
|
-
#### **Leaderboard** (`client.leaderboard`) - Game-specific
|
|
366
|
-
|
|
367
|
-
- `fetch(options?)`: Get leaderboard for a specific game
|
|
368
|
-
- `options.timeframe`: Filter by time period (`'all_time'`, `'monthly'`, `'weekly'`, `'daily'`)
|
|
369
|
-
- `options.gameId`: Game ID to fetch leaderboard for (required)
|
|
370
|
-
- `options.limit`: Number of entries to return (default: 10)
|
|
371
|
-
- `options.offset`: Pagination offset (default: 0)
|
|
372
|
-
|
|
373
|
-
#### **Scores** (`client.scores`) - Platform-wide
|
|
374
|
-
|
|
375
|
-
- `submit(gameId, score, metadata?)`: Submit a score for any game
|
|
376
|
-
- `getUserScores(userId, options?)`: Get all scores for a user
|
|
377
|
-
- `options.gameId`: Filter by specific game (optional)
|
|
378
|
-
- `options.limit`: Number of scores to return (default: 50)
|
|
379
|
-
|
|
380
|
-
### Developer Tools
|
|
381
|
-
|
|
382
|
-
#### **Developer Authentication** (`client.dev.auth`)
|
|
383
|
-
|
|
384
|
-
- `applyForDeveloper()`: Apply for developer status
|
|
385
|
-
- `getDeveloperStatus()`: Check current developer status
|
|
386
|
-
|
|
387
|
-
#### **Game Management** (`client.dev.games`)
|
|
388
|
-
|
|
389
|
-
- `upsert(slug, metadata, gameFile)`: Create or update game
|
|
390
|
-
- `update(gameId, updates)`: Update game properties
|
|
391
|
-
- `delete(gameId)`: Delete game
|
|
119
|
+
### `client.backend`
|
|
392
120
|
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
- `createKey(gameId, name)`: Create API key for server authentication
|
|
396
|
-
- `listKeys()`: List all API keys
|
|
397
|
-
- `revokeKey(keyId)`: Revoke API key
|
|
398
|
-
|
|
399
|
-
#### **Item Management** (`client.dev.items`)
|
|
400
|
-
|
|
401
|
-
- `list(gameId)`: List all items for a game
|
|
402
|
-
- `get(gameId, slug)`: Get specific item
|
|
403
|
-
- `create(gameId, slug, data)`: Create new game item
|
|
404
|
-
- `update(gameId, itemId, updates)`: Update existing item
|
|
405
|
-
- `delete(gameId, itemId)`: Delete item
|
|
406
|
-
|
|
407
|
-
## Event System
|
|
408
|
-
|
|
409
|
-
The SDK provides real-time event notifications for important platform changes:
|
|
410
|
-
|
|
411
|
-
### Available Events
|
|
121
|
+
HTTP client for game-specific backend functions deployed via `playcademy deploy`.
|
|
412
122
|
|
|
413
123
|
```typescript
|
|
414
|
-
|
|
415
|
-
client.
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
// Connection state changes
|
|
420
|
-
client.on('connectionChange', ({ state, reason }) => {
|
|
421
|
-
console.log(`Connection: ${state} - ${reason}`)
|
|
422
|
-
})
|
|
124
|
+
const result = await client.backend.post('/generate-puzzle', { difficulty: 3 })
|
|
125
|
+
const config = await client.backend.get('/config')
|
|
126
|
+
const url = client.backend.url`/assets/map.json`
|
|
127
|
+
```
|
|
423
128
|
|
|
424
|
-
|
|
425
|
-
client.on('inventoryChange', payload => {
|
|
426
|
-
console.log(`Item ${payload.itemId}: ${payload.delta} (total: ${payload.newTotal})`)
|
|
427
|
-
})
|
|
129
|
+
### `client.identity`
|
|
428
130
|
|
|
429
|
-
|
|
430
|
-
client.on('xpGained', payload => {
|
|
431
|
-
console.log(`Gained ${payload.amount} XP (total: ${payload.totalXP})`)
|
|
432
|
-
})
|
|
131
|
+
OAuth provider connections (Discord, Google, etc.).
|
|
433
132
|
|
|
434
|
-
|
|
435
|
-
client.
|
|
436
|
-
console.log(`Level up! ${payload.oldLevel} → ${payload.newLevel}`)
|
|
437
|
-
console.log('Credits awarded:', payload.creditsAwarded)
|
|
438
|
-
})
|
|
133
|
+
```typescript
|
|
134
|
+
await client.identity.connect({ provider: 'discord' })
|
|
439
135
|
```
|
|
440
136
|
|
|
441
|
-
###
|
|
137
|
+
### `client.demo`
|
|
442
138
|
|
|
443
|
-
|
|
139
|
+
Demo mode for anonymous players on the landing page.
|
|
444
140
|
|
|
445
141
|
```typescript
|
|
446
|
-
const
|
|
447
|
-
|
|
448
|
-
displayAlert?.(`Connection ${state}: ${reason}`, { type: 'warning' })
|
|
449
|
-
})
|
|
450
|
-
|
|
451
|
-
// Later: cleanup() to unregister
|
|
142
|
+
const profile = await client.demo.profile.get()
|
|
143
|
+
client.demo.end(score, { displayName: 'Player 1' })
|
|
452
144
|
```
|
|
453
145
|
|
|
454
|
-
|
|
146
|
+
## Internal SDK
|
|
147
|
+
|
|
148
|
+
For CLI, admin dashboard, and platform tools. Includes all game namespaces plus:
|
|
149
|
+
|
|
150
|
+
| Namespace | Purpose |
|
|
151
|
+
| ----------------- | ------------------------------------------------------------------------- |
|
|
152
|
+
| `client.auth` | Email/password login, API key management |
|
|
153
|
+
| `client.admin` | Game pause/resume |
|
|
154
|
+
| `client.dev` | Game deployment, secrets, database, KV, bucket management |
|
|
155
|
+
| `client.games` | Game listing, fetching, state, sessions |
|
|
156
|
+
| `client.timeback` | Platform-tier TimeBack operations (enrollment, XP grants, reconciliation) |
|
|
455
157
|
|
|
456
158
|
```typescript
|
|
457
|
-
|
|
458
|
-
client.on('inventoryChange', payload => {
|
|
459
|
-
updateInventoryDisplay(payload.itemId, payload.newTotal)
|
|
460
|
-
})
|
|
159
|
+
import { PlaycademyClient } from '@playcademy/sdk/internal'
|
|
461
160
|
|
|
462
|
-
client
|
|
463
|
-
|
|
464
|
-
showCreditsAwarded(payload.creditsAwarded)
|
|
465
|
-
})
|
|
161
|
+
const client = new PlaycademyClient({ baseUrl: 'https://hub.playcademy.net' })
|
|
162
|
+
await client.auth.login({ email: 'dev@example.com', password: '***' })
|
|
466
163
|
|
|
467
|
-
client.
|
|
468
|
-
|
|
469
|
-
})
|
|
164
|
+
const games = await client.games.list()
|
|
165
|
+
await client.dev.games.deploy('my-game', { metadata, file })
|
|
470
166
|
```
|
|
471
167
|
|
|
472
|
-
##
|
|
473
|
-
|
|
474
|
-
### Manual Initialization
|
|
168
|
+
## Server SDK
|
|
475
169
|
|
|
476
|
-
For
|
|
170
|
+
For game backends that need to verify player tokens or submit activity results server-side.
|
|
477
171
|
|
|
478
172
|
```typescript
|
|
479
|
-
import { PlaycademyClient } from '@playcademy/sdk'
|
|
173
|
+
import { PlaycademyClient, verifyGameToken } from '@playcademy/sdk/server'
|
|
480
174
|
|
|
481
|
-
|
|
175
|
+
const { user } = await verifyGameToken(token)
|
|
482
176
|
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
'https://api.playcademy.com',
|
|
486
|
-
'user@example.com',
|
|
487
|
-
'password',
|
|
488
|
-
)
|
|
489
|
-
|
|
490
|
-
// Step 2: Initialize client
|
|
491
|
-
const client = new PlaycademyClient({
|
|
492
|
-
baseUrl: 'https://api.playcademy.com',
|
|
493
|
-
token: loginData.token,
|
|
494
|
-
gameId: 'your-game-id', // Optional: enables automatic session management
|
|
495
|
-
})
|
|
177
|
+
const client = new PlaycademyClient({ apiToken, gameId, env })
|
|
178
|
+
await client.timeback.endActivity(userId, payload)
|
|
496
179
|
```
|
|
497
180
|
|
|
498
|
-
|
|
181
|
+
## Base Client API
|
|
182
|
+
|
|
183
|
+
All client variants share:
|
|
499
184
|
|
|
500
185
|
```typescript
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
retries: 3, // Number of retry attempts
|
|
508
|
-
})
|
|
186
|
+
client.getToken()
|
|
187
|
+
client.setToken(token, tokenType?)
|
|
188
|
+
client.isAuthenticated()
|
|
189
|
+
client.on(event, callback) // returns cleanup function
|
|
190
|
+
client.off(event, callback)
|
|
191
|
+
client.onAuthChange(callback)
|
|
509
192
|
```
|
|
510
193
|
|
|
511
|
-
|
|
194
|
+
## Error Handling
|
|
512
195
|
|
|
513
196
|
```typescript
|
|
514
197
|
import { PlaycademyError } from '@playcademy/sdk'
|
|
515
198
|
|
|
516
199
|
try {
|
|
517
|
-
|
|
518
|
-
// Handle success
|
|
200
|
+
await client.scores.submit(score)
|
|
519
201
|
} catch (error) {
|
|
520
202
|
if (error instanceof PlaycademyError) {
|
|
521
|
-
console.error(
|
|
522
|
-
console.error('Status Code:', error.statusCode)
|
|
523
|
-
console.error('Error Code:', error.code)
|
|
524
|
-
} else {
|
|
525
|
-
console.error('Unexpected error:', error)
|
|
203
|
+
console.error(error.message, error.statusCode, error.code)
|
|
526
204
|
}
|
|
527
205
|
}
|
|
528
206
|
```
|
|
529
207
|
|
|
530
|
-
## Development
|
|
531
|
-
|
|
532
|
-
### Integration with Playcademy Vite Plugin
|
|
208
|
+
## Development
|
|
533
209
|
|
|
534
|
-
|
|
210
|
+
In development, the SDK connects to a local sandbox started by `@playcademy/vite-plugin`. The sandbox provides mock auth, a local API, and game asset serving — no platform account needed.
|
|
535
211
|
|
|
536
212
|
```typescript
|
|
537
|
-
//
|
|
538
|
-
import { PlaycademyClient } from '@playcademy/sdk'
|
|
539
|
-
|
|
540
|
-
// The vite plugin automatically starts the sandbox
|
|
213
|
+
// Automatic — vite plugin handles everything
|
|
541
214
|
const client = await PlaycademyClient.init()
|
|
542
|
-
// SDK automatically connects to local sandbox at http://localhost:4321
|
|
543
|
-
```
|
|
544
|
-
|
|
545
|
-
### Manual Sandbox Setup
|
|
546
|
-
|
|
547
|
-
If not using the Vite plugin, start the sandbox manually:
|
|
548
|
-
|
|
549
|
-
```bash
|
|
550
|
-
# Start sandbox server (will also start realtime server on port 4322)
|
|
551
|
-
bunx @playcademy/sandbox --port 4321 --verbose
|
|
552
|
-
|
|
553
|
-
# In your application
|
|
554
|
-
const client = new PlaycademyClient({
|
|
555
|
-
baseUrl: 'http://localhost:4321/api',
|
|
556
|
-
realtimeUrl: 'ws://localhost:4322',
|
|
557
|
-
token: 'dev-token' // Sandbox provides mock authentication
|
|
558
|
-
})
|
|
559
215
|
```
|
|
560
|
-
|
|
561
|
-
## Best Practices
|
|
562
|
-
|
|
563
|
-
### Initialization & Setup
|
|
564
|
-
|
|
565
|
-
- **Always use automatic initialization** for game development with `PlaycademyClient.init()`
|
|
566
|
-
- **Handle initialization errors gracefully** with proper try-catch blocks
|
|
567
|
-
- **Store the client instance** for reuse throughout your application lifecycle
|
|
568
|
-
|
|
569
|
-
### State Management
|
|
570
|
-
|
|
571
|
-
- **Use `games.saveState()`** for transient data (current level, position, temporary status)
|
|
572
|
-
- **Use `users.inventory`** for persistent items and resources that carry between sessions
|
|
573
|
-
- **Save state periodically**, not on every frame or minor change
|
|
574
|
-
- **Load state once** at game start, then manage locally
|
|
575
|
-
|
|
576
|
-
### Performance Optimization
|
|
577
|
-
|
|
578
|
-
- **Cache frequently accessed data** like user information and inventory
|
|
579
|
-
- **Batch inventory operations** when possible instead of individual API calls
|
|
580
|
-
- **Use event listeners** to update UI reactively rather than polling
|
|
581
|
-
- **Implement proper loading states** for better user experience
|
|
582
|
-
|
|
583
|
-
### Error Handling
|
|
584
|
-
|
|
585
|
-
- **Wrap all SDK calls** in appropriate try-catch blocks
|
|
586
|
-
- **Provide fallback behavior** for network errors and API failures
|
|
587
|
-
- **Show meaningful error messages** to users when operations fail
|
|
588
|
-
- **Implement retry logic** for non-critical operations
|
|
589
|
-
|
|
590
|
-
### Development Workflow
|
|
591
|
-
|
|
592
|
-
- **Use the sandbox environment** for all local development
|
|
593
|
-
- **Test both online and offline scenarios** to ensure robust error handling
|
|
594
|
-
- **Enable verbose logging** during development for debugging
|
|
595
|
-
- **Validate API responses** and handle edge cases appropriately
|
|
596
|
-
|
|
597
|
-
## Testing
|
|
598
|
-
|
|
599
|
-
### Unit Testing
|
|
600
|
-
|
|
601
|
-
```typescript
|
|
602
|
-
// Mock the SDK for unit tests
|
|
603
|
-
import { jest } from '@jest/globals'
|
|
604
|
-
|
|
605
|
-
// Mock the entire SDK module
|
|
606
|
-
jest.mock('@playcademy/sdk', () => ({
|
|
607
|
-
PlaycademyClient: {
|
|
608
|
-
init: jest.fn().mockResolvedValue({
|
|
609
|
-
users: {
|
|
610
|
-
me: jest.fn().mockResolvedValue({ id: 'test-user', name: 'Test User' }),
|
|
611
|
-
inventory: {
|
|
612
|
-
get: jest.fn().mockResolvedValue([]),
|
|
613
|
-
add: jest.fn().mockResolvedValue(undefined),
|
|
614
|
-
},
|
|
615
|
-
},
|
|
616
|
-
}),
|
|
617
|
-
},
|
|
618
|
-
}))
|
|
619
|
-
```
|
|
620
|
-
|
|
621
|
-
### Integration Testing
|
|
622
|
-
|
|
623
|
-
```typescript
|
|
624
|
-
// Test with real sandbox
|
|
625
|
-
import { PlaycademyClient } from '@playcademy/sdk'
|
|
626
|
-
|
|
627
|
-
describe('Playcademy Integration', () => {
|
|
628
|
-
let client: PlaycademyClient
|
|
629
|
-
|
|
630
|
-
beforeAll(async () => {
|
|
631
|
-
// Initialize with sandbox
|
|
632
|
-
client = new PlaycademyClient({
|
|
633
|
-
baseUrl: 'http://localhost:4321/api',
|
|
634
|
-
token: 'test-token',
|
|
635
|
-
})
|
|
636
|
-
})
|
|
637
|
-
|
|
638
|
-
test('should fetch user data', async () => {
|
|
639
|
-
const user = await client.users.me()
|
|
640
|
-
expect(user).toBeDefined()
|
|
641
|
-
expect(user.name).toEqual(expect.any(String))
|
|
642
|
-
})
|
|
643
|
-
})
|
|
644
|
-
```
|
|
645
|
-
|
|
646
|
-
## Troubleshooting
|
|
647
|
-
|
|
648
|
-
### Common Issues
|
|
649
|
-
|
|
650
|
-
**SDK Initialization Timeout**
|
|
651
|
-
|
|
652
|
-
```
|
|
653
|
-
Error: PLAYCADEMY_INIT not received within 5000ms
|
|
654
|
-
```
|
|
655
|
-
|
|
656
|
-
- Ensure you're running in the correct environment (development with sandbox, or production with platform)
|
|
657
|
-
- Check that the Vite plugin is properly configured
|
|
658
|
-
- Verify the sandbox is running on the expected port
|
|
659
|
-
|
|
660
|
-
**Authentication Errors**
|
|
661
|
-
|
|
662
|
-
```
|
|
663
|
-
Error: Unauthorized (401)
|
|
664
|
-
```
|
|
665
|
-
|
|
666
|
-
- Check that your authentication token is valid
|
|
667
|
-
- Ensure you have the necessary permissions for the operation
|
|
668
|
-
- Try re-authenticating with `PlaycademyClient.login()`
|
|
669
|
-
|
|
670
|
-
**Network Connection Issues**
|
|
671
|
-
|
|
672
|
-
```
|
|
673
|
-
Error: Failed to fetch
|
|
674
|
-
```
|
|
675
|
-
|
|
676
|
-
- Verify the API endpoint is accessible
|
|
677
|
-
- Check network connectivity
|
|
678
|
-
- Ensure CORS is properly configured for cross-origin requests
|
|
679
|
-
|
|
680
|
-
### Debugging
|
|
681
|
-
|
|
682
|
-
Use these debugging techniques for troubleshooting SDK issues:
|
|
683
|
-
|
|
684
|
-
```typescript
|
|
685
|
-
// Check initialization process
|
|
686
|
-
try {
|
|
687
|
-
const client = await PlaycademyClient.init()
|
|
688
|
-
console.log('SDK initialized successfully')
|
|
689
|
-
} catch (error) {
|
|
690
|
-
console.error('SDK initialization failed:', error)
|
|
691
|
-
}
|
|
692
|
-
|
|
693
|
-
// Monitor network requests in browser dev tools (Network tab)
|
|
694
|
-
// Check console for SDK error messages
|
|
695
|
-
// Verify API responses and error details
|
|
696
|
-
```
|
|
697
|
-
|
|
698
|
-
## Contributing
|
|
699
|
-
|
|
700
|
-
The SDK is a critical component of the Playcademy platform ecosystem. When contributing:
|
|
701
|
-
|
|
702
|
-
1. **Maintain Type Safety**: Ensure all new APIs are fully typed
|
|
703
|
-
2. **Update Documentation**: Keep this README and JSDoc comments current
|
|
704
|
-
3. **Add Tests**: Include both unit and integration tests for new features
|
|
705
|
-
4. **Follow Patterns**: Use consistent patterns with existing SDK methods
|
|
706
|
-
5. **Handle Errors**: Implement proper error handling and user feedback
|
|
707
|
-
|
|
708
|
-
For general contribution guidelines, see the [monorepo CONTRIBUTING.md](../../CONTRIBUTING.md).
|