@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 CHANGED
@@ -1,708 +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
- 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
- // ... player completes activity ...
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
- ### Real-time Authentication
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
- // Get a realtime token for WebSocket authentication
279
- const { token } = await client.realtime.token.get()
280
-
281
- // Token is used internally by the platform's multiplayer WebSocket client
282
- // 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
+ )
283
117
  ```
284
118
 
285
- ## API Reference
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
- #### **API Keys** (`client.dev.keys`)
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
- // Authentication changes
415
- client.on('authChange', payload => {
416
- console.log('Authentication changed:', payload.token)
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
- // Inventory changes
425
- client.on('inventoryChange', payload => {
426
- console.log(`Item ${payload.itemId}: ${payload.delta} (total: ${payload.newTotal})`)
427
- })
129
+ ### `client.identity`
428
130
 
429
- // Experience gained
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
- // Level up notifications
435
- client.on('levelUp', payload => {
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
- ### Disconnect Handling
137
+ ### `client.demo`
442
138
 
443
- For convenience, use the `onDisconnect` method to handle only offline/degraded states:
139
+ Demo mode for anonymous players on the landing page.
444
140
 
445
141
  ```typescript
446
- const cleanup = client.onDisconnect(({ state, reason, displayAlert }) => {
447
- console.log(`Disconnect detected: ${state}`)
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
- ### 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) |
455
157
 
456
158
  ```typescript
457
- // Update UI in response to platform events
458
- client.on('inventoryChange', payload => {
459
- updateInventoryDisplay(payload.itemId, payload.newTotal)
460
- })
159
+ import { PlaycademyClient } from '@playcademy/sdk/internal'
461
160
 
462
- client.on('levelUp', payload => {
463
- showLevelUpAnimation(payload.newLevel)
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.on('xpGained', payload => {
468
- updateXPBar(payload.totalXP, payload.leveledUp)
469
- })
164
+ const games = await client.games.list()
165
+ await client.dev.games.deploy('my-game', { metadata, file })
470
166
  ```
471
167
 
472
- ## Advanced Usage
473
-
474
- ### Manual Initialization
168
+ ## Server SDK
475
169
 
476
- For server-side applications or custom environments:
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
- import type { LoginResponse } from '@playcademy/sdk'
175
+ const { user } = await verifyGameToken(token)
482
176
 
483
- // Step 1: Authenticate
484
- const loginData: LoginResponse = await PlaycademyClient.login(
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
- ### Custom Configuration
181
+ ## Base Client API
182
+
183
+ All client variants share:
499
184
 
500
185
  ```typescript
501
- const client = new PlaycademyClient({
502
- baseUrl: 'https://api.playcademy.com',
503
- token: 'your-auth-token',
504
- gameId: 'your-game-id',
505
- // Additional options
506
- timeout: 10000, // Request timeout in milliseconds
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
- ### Error Handling
194
+ ## Error Handling
512
195
 
513
196
  ```typescript
514
197
  import { PlaycademyError } from '@playcademy/sdk'
515
198
 
516
199
  try {
517
- const user = await client.users.me()
518
- // Handle success
200
+ await client.scores.submit(score)
519
201
  } catch (error) {
520
202
  if (error instanceof PlaycademyError) {
521
- console.error('Playcademy API Error:', error.message)
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 Environment
531
-
532
- ### Integration with Playcademy Vite Plugin
208
+ ## Development
533
209
 
534
- 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.
535
211
 
536
212
  ```typescript
537
- // In your game's main file
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).