@playcademy/sdk 0.9.1-beta.1 → 0.9.1-beta.3

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 CHANGED
@@ -1,705 +1,215 @@
1
1
  # @playcademy/sdk
2
2
 
3
- **Official TypeScript SDK for the Playcademy platform**
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 serves as the primary interface between your game and the Playcademy platform, providing:
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
- The Playcademy SDK is split into two entry points:
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
- #### Public SDK (`@playcademy/sdk`)
14
+ ### Entry Points
22
15
 
23
- For game developers building games on the Playcademy platform. Includes 8 essential namespaces:
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
- - **Core**: `identity`, `runtime`, `backend`, `users`
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 client = new PlaycademyClient({ baseUrl: 'https://hub.playcademy.net' })
58
- await client.auth.login({ email: 'dev@example.com', password: '***' })
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
- **Note**: The internal SDK is for trusted code only (CLI, platform internals). Game developers should use the public SDK.
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
- - **Automatic Detection**: Multi-signal approach detects offline, slow, and degraded connections
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
- See the SDK Browser documentation for detailed connection monitoring documentation and examples.
39
+ ### `client.users`
92
40
 
93
- ## Installation
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
- import { PlaycademyClient } from '@playcademy/sdk'
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
- ### Environment Detection
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
- ### Game Session Management
49
+ Game lifecycle and platform communication.
157
50
 
158
51
  ```typescript
159
- // Automatic session management when gameId is available
160
- const client = await PlaycademyClient.init()
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
- // Load previously saved state
171
- const gameState = await client.games.loadState()
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
- ### User & Inventory Management
60
+ **Assets** build CDN URLs for game assets uploaded via `playcademy deploy`:
179
61
 
180
62
  ```typescript
181
- // Get user information
182
- const user = await client.users.me()
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
- ### Credits & Currency
67
+ ### `client.scores`
68
+
69
+ Score submission for the current game.
196
70
 
197
71
  ```typescript
198
- // Platform currency management
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
- ### Experience & Levels
75
+ ### `client.leaderboard`
211
76
 
212
- ```typescript
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
- // Note: XP is now managed entirely through TimeBack integration.
219
- // XP updates come from TimeBack webhooks only.
79
+ ```typescript
80
+ const entries = await client.leaderboard.fetch({
81
+ timeframe: 'weekly',
82
+ limit: 10,
83
+ })
220
84
  ```
221
85
 
222
- ### TimeBack Integration
86
+ ### `client.timeback`
223
87
 
224
- Access user role and enrollments, and track learning activities:
88
+ TimeBack LMS integration activity tracking with automatic heartbeats, idle detection, and pause/resume tied to tab visibility.
225
89
 
226
90
  ```typescript
227
- // Access TimeBack role and enrollments
228
- const role = client.timeback.role // 'student' | 'parent' | 'teacher' | 'administrator'
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
- // Start tracking an activity (only activityId required!)
238
- 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
- // Automatically pauses active time when the tab is hidden, the shell pauses,
244
- // or the player has no visible keyboard/mouse activity for 10 minutes
245
-
246
- // Customize inactivity handling
247
- client.timeback.startActivity(
248
- { activityId: 'math-quiz-level-1' },
249
- {
250
- inactivityTimeoutMs: 5 * 60 * 1000, // 5 minutes
251
- heartbeatIntervalMs: 15_000,
252
- },
253
- )
254
97
 
255
- // Disable keyboard/mouse inactivity tracking
256
- client.timeback.startActivity(
257
- { activityId: 'math-quiz-level-1' },
258
- { inactivityTimeoutMs: Infinity },
259
- )
260
-
261
- // ... player completes activity ...
98
+ // Activity auto-pauses on tab hide, shell pause, or 10min keyboard/mouse idle.
99
+ // Heartbeats are sent automatically.
262
100
 
263
- // End activity and submit results (XP calculated automatically)
264
101
  await client.timeback.endActivity({
265
102
  correctQuestions: 8,
266
103
  totalQuestions: 10,
267
104
  })
268
- // XP calculation: base (1 min = 1 XP) × accuracy multiplier
269
- // 100%: 1.25x | 80-99%: 1.0x | 65-79%: 0.5x | <65%: 0x
270
105
  ```
271
106
 
272
- ### Real-time Authentication
273
-
274
- Get authentication tokens for WebSocket connections used by the platform's multiplayer system.
107
+ Options for `startActivity`:
275
108
 
276
109
  ```typescript
277
- // Get a realtime token for WebSocket authentication
278
- const { token } = await client.realtime.token.get()
279
-
280
- // Token is used internally by the platform's multiplayer WebSocket client
281
- // for presence tracking, player positions, and real-time game features
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
+ )
282
117
  ```
283
118
 
284
- ## API Reference
285
-
286
- ### Core Modules
287
-
288
- #### **Authentication** (`client.auth`)
289
-
290
- - `logout()`: Logs out user and clears authentication token
291
-
292
- #### **Users** (`client.users`)
293
-
294
- - `me()`: Get current user information
295
- - **Inventory** (`client.users.inventory`):
296
- - `get()`: Get user's inventory
297
- - `add(identifier, quantity)`: Add items to inventory
298
- - `remove(identifier, quantity)`: Remove items from inventory
299
- - `quantity(identifier)`: Get item quantity
300
- - `has(identifier, minQuantity?)`: Check item ownership
301
-
302
- #### **Games** (`client.games`)
303
-
304
- - `list()`: Get all available games
305
- - `fetch(gameIdOrSlug)`: Get specific game details
306
- - `saveState(state)`: Save transient game state
307
- - `loadState()`: Load saved game state
308
- - `startSession(gameId?)`: Start game session
309
- - `endSession(sessionId, gameId?)`: End game session
310
-
311
- #### **Credits** (`client.credits`)
312
-
313
- - `balance()`: Get current credits balance
314
- - `add(amount)`: Add credits to user
315
- - `spend(amount)`: Spend user credits
316
-
317
- #### **Levels** (`client.levels`)
318
-
319
- - `get()`: Get current user level information
320
- - `progress()`: Get level progress and XP to next level
321
- - `addXP(amount)`: Add experience points
322
- - **Config** (`client.levels.config`):
323
- - `list()`: Get all level configurations
324
- - `get(level)`: Get specific level configuration
325
-
326
- #### **Maps** (`client.maps`)
327
-
328
- - `elements(mapId)`: Get map elements and points of interest
329
-
330
- #### **Runtime** (`client.runtime`)
331
-
332
- - `getGameToken(gameId, options?)`: Get game-specific authentication token
333
- - `exit()`: Signal platform to exit game view
334
-
335
- #### **Real-time** (`client.realtime`)
336
-
337
- - `token.get()`: Retrieves a JWT for WebSocket authentication (used by the platform's multiplayer system).
338
-
339
- #### **TimeBack** (`client.timeback`)
340
-
341
- - `role`: The user's TimeBack role (`'student'`, `'parent'`, `'teacher'`, or `'administrator'`)
342
- - `enrollments`: Array of course enrollments with `subject`, `grade`, and `courseId`
343
- - `startActivity(metadata, options?)`: Start tracking an activity (stores start time and metadata)
344
- - `metadata.activityId`: Unique activity identifier (required)
345
- - `metadata.activityName`: Human-readable activity name
346
- - `metadata.subject`: Subject area (Math, Reading, Science, etc.)
347
- - `metadata.appName`: Application name
348
- - `metadata.sensorUrl`: Sensor URL for tracking
349
- - Automatically pauses active time when the tab is hidden, the shell pauses the game, or the player is idle while visible
350
- - `options.pausedHeartbeatTimeoutMs`: Stop periodic heartbeats after a long automatic pause (`hidden` or `inactivity`); `Infinity` keeps them running
351
- - `options.heartbeatIntervalMs`: Flush incremental heartbeats (`Infinity` disables periodic heartbeats)
352
- - `options.inactivityTimeoutMs`: Keyboard/mouse idle timeout while visible (defaults to 10 minutes; `Infinity` disables)
353
- - `endActivity(scoreData)`: End activity and submit results
354
- - `scoreData.correctQuestions`: Number of correct answers
355
- - `scoreData.totalQuestions`: Total number of questions
356
- - **XP Query** (`client.timeback.xp`):
357
- - `today(options?)`: Get today's XP (supports timezone parameter)
358
- - `total()`: Get total accumulated XP
359
- - `history(options?)`: Get XP history with optional date filtering
360
- - `summary(options?)`: Get both today's and total XP in one call
361
-
362
- #### **Leaderboard** (`client.leaderboard`) - Game-specific
363
-
364
- - `fetch(options?)`: Get leaderboard for a specific game
365
- - `options.timeframe`: Filter by time period (`'all_time'`, `'monthly'`, `'weekly'`, `'daily'`)
366
- - `options.gameId`: Game ID to fetch leaderboard for (required)
367
- - `options.limit`: Number of entries to return (default: 10)
368
- - `options.offset`: Pagination offset (default: 0)
369
-
370
- #### **Scores** (`client.scores`) - Platform-wide
371
-
372
- - `submit(gameId, score, metadata?)`: Submit a score for any game
373
- - `getUserScores(userId, options?)`: Get all scores for a user
374
- - `options.gameId`: Filter by specific game (optional)
375
- - `options.limit`: Number of scores to return (default: 50)
376
-
377
- ### Developer Tools
378
-
379
- #### **Developer Authentication** (`client.dev.auth`)
380
-
381
- - `applyForDeveloper()`: Apply for developer status
382
- - `getDeveloperStatus()`: Check current developer status
383
-
384
- #### **Game Management** (`client.dev.games`)
119
+ ### `client.backend`
385
120
 
386
- - `upsert(slug, metadata, gameFile)`: Create or update game
387
- - `update(gameId, updates)`: Update game properties
388
- - `delete(gameId)`: Delete game
389
-
390
- #### **API Keys** (`client.dev.keys`)
391
-
392
- - `createKey(gameId, name)`: Create API key for server authentication
393
- - `listKeys()`: List all API keys
394
- - `revokeKey(keyId)`: Revoke API key
395
-
396
- #### **Item Management** (`client.dev.items`)
397
-
398
- - `list(gameId)`: List all items for a game
399
- - `get(gameId, slug)`: Get specific item
400
- - `create(gameId, slug, data)`: Create new game item
401
- - `update(gameId, itemId, updates)`: Update existing item
402
- - `delete(gameId, itemId)`: Delete item
403
-
404
- ## Event System
405
-
406
- The SDK provides real-time event notifications for important platform changes:
407
-
408
- ### Available Events
121
+ HTTP client for game-specific backend functions deployed via `playcademy deploy`.
409
122
 
410
123
  ```typescript
411
- // Authentication changes
412
- client.on('authChange', payload => {
413
- console.log('Authentication changed:', payload.token)
414
- })
415
-
416
- // Connection state changes
417
- client.on('connectionChange', ({ state, reason }) => {
418
- console.log(`Connection: ${state} - ${reason}`)
419
- })
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
+ ```
420
128
 
421
- // Inventory changes
422
- client.on('inventoryChange', payload => {
423
- console.log(`Item ${payload.itemId}: ${payload.delta} (total: ${payload.newTotal})`)
424
- })
129
+ ### `client.identity`
425
130
 
426
- // Experience gained
427
- client.on('xpGained', payload => {
428
- console.log(`Gained ${payload.amount} XP (total: ${payload.totalXP})`)
429
- })
131
+ OAuth provider connections (Discord, Google, etc.).
430
132
 
431
- // Level up notifications
432
- client.on('levelUp', payload => {
433
- console.log(`Level up! ${payload.oldLevel} → ${payload.newLevel}`)
434
- console.log('Credits awarded:', payload.creditsAwarded)
435
- })
133
+ ```typescript
134
+ await client.identity.connect({ provider: 'discord' })
436
135
  ```
437
136
 
438
- ### Disconnect Handling
137
+ ### `client.demo`
439
138
 
440
- For convenience, use the `onDisconnect` method to handle only offline/degraded states:
139
+ Demo mode for anonymous players on the landing page.
441
140
 
442
141
  ```typescript
443
- const cleanup = client.onDisconnect(({ state, reason, displayAlert }) => {
444
- console.log(`Disconnect detected: ${state}`)
445
- displayAlert?.(`Connection ${state}: ${reason}`, { type: 'warning' })
446
- })
447
-
448
- // Later: cleanup() to unregister
142
+ const profile = await client.demo.profile.get()
143
+ client.demo.end(score, { displayName: 'Player 1' })
449
144
  ```
450
145
 
451
- ### Event-Driven UI Updates
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) |
452
157
 
453
158
  ```typescript
454
- // Update UI in response to platform events
455
- client.on('inventoryChange', payload => {
456
- updateInventoryDisplay(payload.itemId, payload.newTotal)
457
- })
159
+ import { PlaycademyClient } from '@playcademy/sdk/internal'
458
160
 
459
- client.on('levelUp', payload => {
460
- showLevelUpAnimation(payload.newLevel)
461
- showCreditsAwarded(payload.creditsAwarded)
462
- })
161
+ const client = new PlaycademyClient({ baseUrl: 'https://hub.playcademy.net' })
162
+ await client.auth.login({ email: 'dev@example.com', password: '***' })
463
163
 
464
- client.on('xpGained', payload => {
465
- updateXPBar(payload.totalXP, payload.leveledUp)
466
- })
164
+ const games = await client.games.list()
165
+ await client.dev.games.deploy('my-game', { metadata, file })
467
166
  ```
468
167
 
469
- ## Advanced Usage
470
-
471
- ### Manual Initialization
168
+ ## Server SDK
472
169
 
473
- For server-side applications or custom environments:
170
+ For game backends that need to verify player tokens or submit activity results server-side.
474
171
 
475
172
  ```typescript
476
- import { PlaycademyClient } from '@playcademy/sdk'
477
-
478
- import type { LoginResponse } from '@playcademy/sdk'
173
+ import { PlaycademyClient, verifyGameToken } from '@playcademy/sdk/server'
479
174
 
480
- // Step 1: Authenticate
481
- const loginData: LoginResponse = await PlaycademyClient.login(
482
- 'https://api.playcademy.com',
483
- 'user@example.com',
484
- 'password',
485
- )
175
+ const { user } = await verifyGameToken(token)
486
176
 
487
- // Step 2: Initialize client
488
- const client = new PlaycademyClient({
489
- baseUrl: 'https://api.playcademy.com',
490
- token: loginData.token,
491
- gameId: 'your-game-id', // Optional: enables automatic session management
492
- })
177
+ const client = new PlaycademyClient({ apiToken, gameId, env })
178
+ await client.timeback.endActivity(userId, payload)
493
179
  ```
494
180
 
495
- ### Custom Configuration
181
+ ## Base Client API
182
+
183
+ All client variants share:
496
184
 
497
185
  ```typescript
498
- const client = new PlaycademyClient({
499
- baseUrl: 'https://api.playcademy.com',
500
- token: 'your-auth-token',
501
- gameId: 'your-game-id',
502
- // Additional options
503
- timeout: 10000, // Request timeout in milliseconds
504
- retries: 3, // Number of retry attempts
505
- })
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)
506
192
  ```
507
193
 
508
- ### Error Handling
194
+ ## Error Handling
509
195
 
510
196
  ```typescript
511
197
  import { PlaycademyError } from '@playcademy/sdk'
512
198
 
513
199
  try {
514
- const user = await client.users.me()
515
- // Handle success
200
+ await client.scores.submit(score)
516
201
  } catch (error) {
517
202
  if (error instanceof PlaycademyError) {
518
- console.error('Playcademy API Error:', error.message)
519
- console.error('Status Code:', error.statusCode)
520
- console.error('Error Code:', error.code)
521
- } else {
522
- console.error('Unexpected error:', error)
203
+ console.error(error.message, error.statusCode, error.code)
523
204
  }
524
205
  }
525
206
  ```
526
207
 
527
- ## Development Environment
208
+ ## Development
528
209
 
529
- ### Integration with Playcademy Vite Plugin
530
-
531
- When using the official Playcademy Vite templates, the development environment is automatically configured:
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.
532
211
 
533
212
  ```typescript
534
- // In your game's main file
535
- import { PlaycademyClient } from '@playcademy/sdk'
536
-
537
- // The vite plugin automatically starts the sandbox
213
+ // Automatic vite plugin handles everything
538
214
  const client = await PlaycademyClient.init()
539
- // SDK automatically connects to local sandbox at http://localhost:4321
540
- ```
541
-
542
- ### Manual Sandbox Setup
543
-
544
- If not using the Vite plugin, start the sandbox manually:
545
-
546
- ```bash
547
- # Start sandbox server (will also start realtime server on port 4322)
548
- bunx @playcademy/sandbox --port 4321 --verbose
549
-
550
- # In your application
551
- const client = new PlaycademyClient({
552
- baseUrl: 'http://localhost:4321/api',
553
- realtimeUrl: 'ws://localhost:4322',
554
- token: 'dev-token' // Sandbox provides mock authentication
555
- })
556
- ```
557
-
558
- ## Best Practices
559
-
560
- ### Initialization & Setup
561
-
562
- - **Always use automatic initialization** for game development with `PlaycademyClient.init()`
563
- - **Handle initialization errors gracefully** with proper try-catch blocks
564
- - **Store the client instance** for reuse throughout your application lifecycle
565
-
566
- ### State Management
567
-
568
- - **Use `games.saveState()`** for transient data (current level, position, temporary status)
569
- - **Use `users.inventory`** for persistent items and resources that carry between sessions
570
- - **Save state periodically**, not on every frame or minor change
571
- - **Load state once** at game start, then manage locally
572
-
573
- ### Performance Optimization
574
-
575
- - **Cache frequently accessed data** like user information and inventory
576
- - **Batch inventory operations** when possible instead of individual API calls
577
- - **Use event listeners** to update UI reactively rather than polling
578
- - **Implement proper loading states** for better user experience
579
-
580
- ### Error Handling
581
-
582
- - **Wrap all SDK calls** in appropriate try-catch blocks
583
- - **Provide fallback behavior** for network errors and API failures
584
- - **Show meaningful error messages** to users when operations fail
585
- - **Implement retry logic** for non-critical operations
586
-
587
- ### Development Workflow
588
-
589
- - **Use the sandbox environment** for all local development
590
- - **Test both online and offline scenarios** to ensure robust error handling
591
- - **Enable verbose logging** during development for debugging
592
- - **Validate API responses** and handle edge cases appropriately
593
-
594
- ## Testing
595
-
596
- ### Unit Testing
597
-
598
- ```typescript
599
- // Mock the SDK for unit tests
600
- import { jest } from '@jest/globals'
601
-
602
- // Mock the entire SDK module
603
- jest.mock('@playcademy/sdk', () => ({
604
- PlaycademyClient: {
605
- init: jest.fn().mockResolvedValue({
606
- users: {
607
- me: jest.fn().mockResolvedValue({ id: 'test-user', name: 'Test User' }),
608
- inventory: {
609
- get: jest.fn().mockResolvedValue([]),
610
- add: jest.fn().mockResolvedValue(undefined),
611
- },
612
- },
613
- }),
614
- },
615
- }))
616
- ```
617
-
618
- ### Integration Testing
619
-
620
- ```typescript
621
- // Test with real sandbox
622
- import { PlaycademyClient } from '@playcademy/sdk'
623
-
624
- describe('Playcademy Integration', () => {
625
- let client: PlaycademyClient
626
-
627
- beforeAll(async () => {
628
- // Initialize with sandbox
629
- client = new PlaycademyClient({
630
- baseUrl: 'http://localhost:4321/api',
631
- token: 'test-token',
632
- })
633
- })
634
-
635
- test('should fetch user data', async () => {
636
- const user = await client.users.me()
637
- expect(user).toBeDefined()
638
- expect(user.name).toEqual(expect.any(String))
639
- })
640
- })
641
- ```
642
-
643
- ## Troubleshooting
644
-
645
- ### Common Issues
646
-
647
- **SDK Initialization Timeout**
648
-
649
- ```
650
- Error: PLAYCADEMY_INIT not received within 5000ms
651
215
  ```
652
-
653
- - Ensure you're running in the correct environment (development with sandbox, or production with platform)
654
- - Check that the Vite plugin is properly configured
655
- - Verify the sandbox is running on the expected port
656
-
657
- **Authentication Errors**
658
-
659
- ```
660
- Error: Unauthorized (401)
661
- ```
662
-
663
- - Check that your authentication token is valid
664
- - Ensure you have the necessary permissions for the operation
665
- - Try re-authenticating with `PlaycademyClient.login()`
666
-
667
- **Network Connection Issues**
668
-
669
- ```
670
- Error: Failed to fetch
671
- ```
672
-
673
- - Verify the API endpoint is accessible
674
- - Check network connectivity
675
- - Ensure CORS is properly configured for cross-origin requests
676
-
677
- ### Debugging
678
-
679
- Use these debugging techniques for troubleshooting SDK issues:
680
-
681
- ```typescript
682
- // Check initialization process
683
- try {
684
- const client = await PlaycademyClient.init()
685
- console.log('SDK initialized successfully')
686
- } catch (error) {
687
- console.error('SDK initialization failed:', error)
688
- }
689
-
690
- // Monitor network requests in browser dev tools (Network tab)
691
- // Check console for SDK error messages
692
- // Verify API responses and error details
693
- ```
694
-
695
- ## Contributing
696
-
697
- The SDK is a critical component of the Playcademy platform ecosystem. When contributing:
698
-
699
- 1. **Maintain Type Safety**: Ensure all new APIs are fully typed
700
- 2. **Update Documentation**: Keep this README and JSDoc comments current
701
- 3. **Add Tests**: Include both unit and integration tests for new features
702
- 4. **Follow Patterns**: Use consistent patterns with existing SDK methods
703
- 5. **Handle Errors**: Implement proper error handling and user feedback
704
-
705
- For general contribution guidelines, see the [monorepo CONTRIBUTING.md](../../CONTRIBUTING.md).