keeperboard 1.0.2 → 1.0.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 +200 -115
- package/dist/index.d.mts +88 -59
- package/dist/index.d.ts +88 -59
- package/dist/index.js +35 -78
- package/dist/index.mjs +35 -78
- package/package.json +7 -2
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# KeeperBoard SDK
|
|
2
2
|
|
|
3
|
-
TypeScript client SDK for [KeeperBoard](https://github.com/
|
|
3
|
+
TypeScript client SDK for [KeeperBoard](https://github.com/clauderoy790/keeperboard) — a free, open-source leaderboard-as-a-service for indie game developers.
|
|
4
4
|
|
|
5
5
|
Works with Phaser.js, vanilla JavaScript, and any TypeScript/JavaScript game running in the browser.
|
|
6
6
|
|
|
@@ -24,7 +24,8 @@ import { KeeperBoardClient, PlayerIdentity } from './keeperboard/index';
|
|
|
24
24
|
|
|
25
25
|
1. Sign up at your KeeperBoard dashboard
|
|
26
26
|
2. Create a game
|
|
27
|
-
3.
|
|
27
|
+
3. Create an environment (dev/prod)
|
|
28
|
+
4. Generate an API key for that environment
|
|
28
29
|
|
|
29
30
|
### 2. Initialize the client
|
|
30
31
|
|
|
@@ -32,23 +33,22 @@ import { KeeperBoardClient, PlayerIdentity } from './keeperboard/index';
|
|
|
32
33
|
import { KeeperBoardClient, PlayerIdentity } from 'keeperboard-sdk';
|
|
33
34
|
|
|
34
35
|
// Create the API client
|
|
35
|
-
const
|
|
36
|
+
const client = new KeeperBoardClient({
|
|
36
37
|
apiUrl: 'https://keeperboard.vercel.app',
|
|
37
|
-
apiKey: '
|
|
38
|
+
apiKey: 'kb_prod_your_api_key_here',
|
|
38
39
|
});
|
|
39
40
|
|
|
40
41
|
// Helper for persistent player identity
|
|
41
|
-
const
|
|
42
|
+
const identity = new PlayerIdentity();
|
|
42
43
|
```
|
|
43
44
|
|
|
44
45
|
### 3. Submit a score
|
|
45
46
|
|
|
46
47
|
```typescript
|
|
47
|
-
|
|
48
|
-
const playerGuid = playerIdentity.getOrCreatePlayerGuid();
|
|
48
|
+
const playerGuid = identity.getOrCreatePlayerGuid();
|
|
49
49
|
|
|
50
|
-
// Submit
|
|
51
|
-
const result = await
|
|
50
|
+
// Submit to default leaderboard
|
|
51
|
+
const result = await client.submitScore(playerGuid, 'PlayerName', 1500);
|
|
52
52
|
|
|
53
53
|
console.log(`Rank: #${result.rank}`);
|
|
54
54
|
console.log(`New high score: ${result.is_new_high_score}`);
|
|
@@ -57,13 +57,24 @@ console.log(`New high score: ${result.is_new_high_score}`);
|
|
|
57
57
|
### 4. Display the leaderboard
|
|
58
58
|
|
|
59
59
|
```typescript
|
|
60
|
-
|
|
60
|
+
// Get top 10
|
|
61
|
+
const leaderboard = await client.getLeaderboard();
|
|
61
62
|
|
|
62
63
|
leaderboard.entries.forEach((entry) => {
|
|
63
64
|
console.log(`#${entry.rank} ${entry.player_name}: ${entry.score}`);
|
|
64
65
|
});
|
|
65
66
|
```
|
|
66
67
|
|
|
68
|
+
### 5. Show player's rank (even if not in top 10)
|
|
69
|
+
|
|
70
|
+
```typescript
|
|
71
|
+
const player = await client.getPlayerRank(playerGuid);
|
|
72
|
+
|
|
73
|
+
if (player && player.rank > 10) {
|
|
74
|
+
console.log(`You are ranked #${player.rank} with ${player.score} points`);
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
67
78
|
## API Reference
|
|
68
79
|
|
|
69
80
|
### KeeperBoardClient
|
|
@@ -72,97 +83,159 @@ leaderboard.entries.forEach((entry) => {
|
|
|
72
83
|
|
|
73
84
|
```typescript
|
|
74
85
|
const client = new KeeperBoardClient({
|
|
75
|
-
apiUrl: string,
|
|
76
|
-
apiKey: string,
|
|
86
|
+
apiUrl: string, // Your KeeperBoard API URL
|
|
87
|
+
apiKey: string, // API key from dashboard
|
|
77
88
|
});
|
|
78
89
|
```
|
|
79
90
|
|
|
80
|
-
|
|
91
|
+
> **Note:** The API key determines which game and environment you're accessing. You don't need to pass environment or game IDs — they're implicit in the key.
|
|
92
|
+
|
|
93
|
+
---
|
|
81
94
|
|
|
82
|
-
|
|
95
|
+
### Score Submission
|
|
83
96
|
|
|
84
|
-
|
|
97
|
+
#### `submitScore(playerGuid, playerName, score)`
|
|
98
|
+
|
|
99
|
+
Submit a score to the default leaderboard. Only updates if higher than existing score.
|
|
85
100
|
|
|
86
101
|
```typescript
|
|
87
|
-
const result = await client.submitScore(
|
|
88
|
-
|
|
102
|
+
const result = await client.submitScore('player-uuid', 'PlayerName', 2500);
|
|
103
|
+
// Returns: { id, player_guid, player_name, score, rank, is_new_high_score }
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
#### `submitScore(playerGuid, playerName, score, leaderboard)`
|
|
107
|
+
|
|
108
|
+
Submit to a specific leaderboard by name.
|
|
109
|
+
|
|
110
|
+
```typescript
|
|
111
|
+
await client.submitScore('player-uuid', 'PlayerName', 2500, 'Weekly Best');
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
#### `submitScore(playerGuid, playerName, score, leaderboard, metadata)`
|
|
115
|
+
|
|
116
|
+
Submit with optional metadata.
|
|
117
|
+
|
|
118
|
+
```typescript
|
|
119
|
+
await client.submitScore(
|
|
120
|
+
'player-uuid',
|
|
89
121
|
'PlayerName',
|
|
90
122
|
2500,
|
|
91
|
-
|
|
92
|
-
|
|
123
|
+
'Weekly Best',
|
|
124
|
+
{ level: 10, character: 'warrior' }
|
|
93
125
|
);
|
|
126
|
+
```
|
|
94
127
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
128
|
+
---
|
|
129
|
+
|
|
130
|
+
### Leaderboard
|
|
131
|
+
|
|
132
|
+
#### `getLeaderboard()`
|
|
133
|
+
|
|
134
|
+
Get the default leaderboard (top 10 entries).
|
|
135
|
+
|
|
136
|
+
```typescript
|
|
137
|
+
const lb = await client.getLeaderboard();
|
|
138
|
+
// Returns: { entries, total_count, reset_schedule }
|
|
104
139
|
```
|
|
105
140
|
|
|
106
|
-
|
|
141
|
+
#### `getLeaderboard(name)`
|
|
107
142
|
|
|
108
|
-
Get leaderboard
|
|
143
|
+
Get a specific leaderboard by name.
|
|
109
144
|
|
|
110
145
|
```typescript
|
|
111
|
-
const
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
146
|
+
const lb = await client.getLeaderboard('Weekly Best');
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
#### `getLeaderboard(name, limit)`
|
|
150
|
+
|
|
151
|
+
Get with a custom limit (max 100).
|
|
152
|
+
|
|
153
|
+
```typescript
|
|
154
|
+
const lb = await client.getLeaderboard('Weekly Best', 50);
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
#### `getLeaderboard(name, limit, offset)`
|
|
158
|
+
|
|
159
|
+
Get with pagination.
|
|
160
|
+
|
|
161
|
+
```typescript
|
|
162
|
+
// Page 2 (entries 11-20)
|
|
163
|
+
const lb = await client.getLeaderboard('Weekly Best', 10, 10);
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
---
|
|
167
|
+
|
|
168
|
+
### Leaderboard Versions (Time-Based)
|
|
116
169
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
170
|
+
For leaderboards with reset schedules (daily/weekly/monthly), you can query historical versions.
|
|
171
|
+
|
|
172
|
+
#### `getLeaderboardVersion(name, version)`
|
|
173
|
+
|
|
174
|
+
Get a specific version of a time-based leaderboard.
|
|
175
|
+
|
|
176
|
+
```typescript
|
|
177
|
+
// Get last week's scores (version 3)
|
|
178
|
+
const lastWeek = await client.getLeaderboardVersion('Weekly Best', 3);
|
|
122
179
|
```
|
|
123
180
|
|
|
124
|
-
|
|
181
|
+
#### `getLeaderboardVersion(name, version, limit, offset)`
|
|
125
182
|
|
|
126
|
-
Get
|
|
183
|
+
Get historical version with pagination.
|
|
127
184
|
|
|
128
185
|
```typescript
|
|
129
|
-
const
|
|
186
|
+
const lb = await client.getLeaderboardVersion('Weekly Best', 3, 25, 0);
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
---
|
|
190
|
+
|
|
191
|
+
### Player
|
|
192
|
+
|
|
193
|
+
#### `getPlayerRank(playerGuid)`
|
|
194
|
+
|
|
195
|
+
Get a player's rank and score. Returns `null` if player has no score.
|
|
196
|
+
|
|
197
|
+
```typescript
|
|
198
|
+
const player = await client.getPlayerRank('player-uuid');
|
|
130
199
|
|
|
131
200
|
if (player) {
|
|
132
|
-
console.log(`
|
|
201
|
+
console.log(`Rank: #${player.rank}, Score: ${player.score}`);
|
|
133
202
|
}
|
|
134
203
|
```
|
|
135
204
|
|
|
136
|
-
|
|
205
|
+
#### `getPlayerRank(playerGuid, leaderboard)`
|
|
206
|
+
|
|
207
|
+
Get player's rank on a specific leaderboard.
|
|
208
|
+
|
|
209
|
+
```typescript
|
|
210
|
+
const player = await client.getPlayerRank('player-uuid', 'Weekly Best');
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
#### `updatePlayerName(playerGuid, newName)`
|
|
137
214
|
|
|
138
215
|
Update a player's display name.
|
|
139
216
|
|
|
140
217
|
```typescript
|
|
141
|
-
|
|
142
|
-
'player-uuid-123',
|
|
143
|
-
'NewPlayerName',
|
|
144
|
-
);
|
|
218
|
+
await client.updatePlayerName('player-uuid', 'NewPlayerName');
|
|
145
219
|
```
|
|
146
220
|
|
|
147
|
-
|
|
221
|
+
---
|
|
222
|
+
|
|
223
|
+
### Claim (for migrated scores)
|
|
224
|
+
|
|
225
|
+
#### `claimScore(playerGuid, playerName)`
|
|
148
226
|
|
|
149
227
|
Claim a migrated score by matching player name. Used when scores were imported without player GUIDs.
|
|
150
228
|
|
|
151
229
|
```typescript
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
'new-player-guid',
|
|
155
|
-
'ImportedPlayerName',
|
|
156
|
-
);
|
|
157
|
-
console.log(`Claimed score: ${result.score}`);
|
|
158
|
-
} catch (error) {
|
|
159
|
-
if (error.code === 'NOT_FOUND') {
|
|
160
|
-
console.log('No unclaimed score found');
|
|
161
|
-
}
|
|
162
|
-
}
|
|
230
|
+
const result = await client.claimScore('new-player-guid', 'ImportedPlayerName');
|
|
231
|
+
console.log(`Claimed score: ${result.score}, Rank: #${result.rank}`);
|
|
163
232
|
```
|
|
164
233
|
|
|
165
|
-
|
|
234
|
+
---
|
|
235
|
+
|
|
236
|
+
### Health Check
|
|
237
|
+
|
|
238
|
+
#### `healthCheck()`
|
|
166
239
|
|
|
167
240
|
Check if the API is healthy. Does not require an API key.
|
|
168
241
|
|
|
@@ -171,6 +244,8 @@ const health = await client.healthCheck();
|
|
|
171
244
|
console.log(`API Version: ${health.version}`);
|
|
172
245
|
```
|
|
173
246
|
|
|
247
|
+
---
|
|
248
|
+
|
|
174
249
|
### PlayerIdentity
|
|
175
250
|
|
|
176
251
|
Helper for managing persistent player identity in localStorage.
|
|
@@ -196,6 +271,8 @@ if (identity.hasIdentity()) {
|
|
|
196
271
|
identity.clear();
|
|
197
272
|
```
|
|
198
273
|
|
|
274
|
+
---
|
|
275
|
+
|
|
199
276
|
### Error Handling
|
|
200
277
|
|
|
201
278
|
All methods throw `KeeperBoardError` on failure:
|
|
@@ -219,25 +296,41 @@ try {
|
|
|
219
296
|
case 'INVALID_REQUEST':
|
|
220
297
|
// Check request parameters
|
|
221
298
|
break;
|
|
222
|
-
case 'ALREADY_CLAIMED':
|
|
223
|
-
// Player already has a score
|
|
224
|
-
break;
|
|
225
299
|
}
|
|
226
300
|
}
|
|
227
301
|
}
|
|
228
302
|
```
|
|
229
303
|
|
|
304
|
+
---
|
|
305
|
+
|
|
306
|
+
## Multiple Leaderboards
|
|
307
|
+
|
|
308
|
+
If your game has multiple leaderboards (e.g., per-level or per-mode):
|
|
309
|
+
|
|
310
|
+
```typescript
|
|
311
|
+
// Submit to different leaderboards
|
|
312
|
+
await client.submitScore(guid, name, score, 'Level 1');
|
|
313
|
+
await client.submitScore(guid, name, score, 'Endless Mode');
|
|
314
|
+
await client.submitScore(guid, name, score, 'Weekly Challenge');
|
|
315
|
+
|
|
316
|
+
// Get specific leaderboards
|
|
317
|
+
const level1 = await client.getLeaderboard('Level 1');
|
|
318
|
+
const endless = await client.getLeaderboard('Endless Mode', 50);
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
---
|
|
322
|
+
|
|
230
323
|
## Phaser.js Integration Example
|
|
231
324
|
|
|
232
325
|
```typescript
|
|
233
326
|
import { KeeperBoardClient, PlayerIdentity } from 'keeperboard-sdk';
|
|
234
327
|
|
|
235
|
-
const
|
|
328
|
+
const client = new KeeperBoardClient({
|
|
236
329
|
apiUrl: 'https://keeperboard.vercel.app',
|
|
237
330
|
apiKey: 'kb_prod_your_api_key',
|
|
238
331
|
});
|
|
239
332
|
|
|
240
|
-
const
|
|
333
|
+
const identity = new PlayerIdentity();
|
|
241
334
|
|
|
242
335
|
class GameOverScene extends Phaser.Scene {
|
|
243
336
|
private score: number = 0;
|
|
@@ -247,34 +340,25 @@ class GameOverScene extends Phaser.Scene {
|
|
|
247
340
|
}
|
|
248
341
|
|
|
249
342
|
async create() {
|
|
250
|
-
const playerGuid =
|
|
251
|
-
const playerName =
|
|
343
|
+
const playerGuid = identity.getOrCreatePlayerGuid();
|
|
344
|
+
const playerName = identity.getPlayerName() ?? 'Anonymous';
|
|
252
345
|
|
|
253
346
|
// Submit score
|
|
254
|
-
const result = await
|
|
255
|
-
playerGuid,
|
|
256
|
-
playerName,
|
|
257
|
-
this.score,
|
|
258
|
-
);
|
|
347
|
+
const result = await client.submitScore(playerGuid, playerName, this.score);
|
|
259
348
|
|
|
260
|
-
// Display
|
|
349
|
+
// Display rank
|
|
261
350
|
this.add
|
|
262
|
-
.text(400, 200, `Your Rank: #${result.rank}`, {
|
|
263
|
-
fontSize: '32px',
|
|
264
|
-
})
|
|
351
|
+
.text(400, 200, `Your Rank: #${result.rank}`, { fontSize: '32px' })
|
|
265
352
|
.setOrigin(0.5);
|
|
266
353
|
|
|
267
354
|
if (result.is_new_high_score) {
|
|
268
355
|
this.add
|
|
269
|
-
.text(400, 250, 'NEW HIGH SCORE!', {
|
|
270
|
-
fontSize: '24px',
|
|
271
|
-
color: '#ffff00',
|
|
272
|
-
})
|
|
356
|
+
.text(400, 250, 'NEW HIGH SCORE!', { fontSize: '24px', color: '#ffff00' })
|
|
273
357
|
.setOrigin(0.5);
|
|
274
358
|
}
|
|
275
359
|
|
|
276
|
-
// Display
|
|
277
|
-
const leaderboard = await
|
|
360
|
+
// Display top 10
|
|
361
|
+
const leaderboard = await client.getLeaderboard();
|
|
278
362
|
|
|
279
363
|
leaderboard.entries.forEach((entry, index) => {
|
|
280
364
|
const isMe = entry.player_guid === playerGuid;
|
|
@@ -285,44 +369,26 @@ class GameOverScene extends Phaser.Scene {
|
|
|
285
369
|
400,
|
|
286
370
|
350 + index * 30,
|
|
287
371
|
`#${entry.rank} ${entry.player_name}: ${entry.score}`,
|
|
288
|
-
{ fontSize: '18px', color }
|
|
372
|
+
{ fontSize: '18px', color }
|
|
289
373
|
)
|
|
290
374
|
.setOrigin(0.5);
|
|
291
375
|
});
|
|
376
|
+
|
|
377
|
+
// Show player's rank if not in top 10
|
|
378
|
+
const player = await client.getPlayerRank(playerGuid);
|
|
379
|
+
if (player && player.rank > 10) {
|
|
380
|
+
this.add
|
|
381
|
+
.text(400, 660, `... #${player.rank} ${playerName}: ${player.score}`, {
|
|
382
|
+
fontSize: '18px',
|
|
383
|
+
color: '#00ff00',
|
|
384
|
+
})
|
|
385
|
+
.setOrigin(0.5);
|
|
386
|
+
}
|
|
292
387
|
}
|
|
293
388
|
}
|
|
294
389
|
```
|
|
295
390
|
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
If your game has multiple leaderboards (e.g., per-level or per-mode), use the `leaderboardSlug` parameter:
|
|
299
|
-
|
|
300
|
-
```typescript
|
|
301
|
-
// Submit to a specific leaderboard
|
|
302
|
-
await client.submitScore(guid, name, score, undefined, 'level-1-scores');
|
|
303
|
-
await client.submitScore(guid, name, score, undefined, 'endless-mode');
|
|
304
|
-
|
|
305
|
-
// Get a specific leaderboard
|
|
306
|
-
const level1 = await client.getLeaderboard(10, 0, 'level-1-scores');
|
|
307
|
-
const endless = await client.getLeaderboard(10, 0, 'endless-mode');
|
|
308
|
-
```
|
|
309
|
-
|
|
310
|
-
## TypeScript Types
|
|
311
|
-
|
|
312
|
-
All types are exported for your convenience:
|
|
313
|
-
|
|
314
|
-
```typescript
|
|
315
|
-
import type {
|
|
316
|
-
KeeperBoardConfig,
|
|
317
|
-
ScoreSubmission,
|
|
318
|
-
ScoreResponse,
|
|
319
|
-
LeaderboardEntry,
|
|
320
|
-
LeaderboardResponse,
|
|
321
|
-
PlayerResponse,
|
|
322
|
-
ClaimResponse,
|
|
323
|
-
HealthResponse,
|
|
324
|
-
} from 'keeperboard-sdk';
|
|
325
|
-
```
|
|
391
|
+
---
|
|
326
392
|
|
|
327
393
|
## Development
|
|
328
394
|
|
|
@@ -336,10 +402,29 @@ npm run typecheck
|
|
|
336
402
|
# Build
|
|
337
403
|
npm run build
|
|
338
404
|
|
|
405
|
+
# Run tests (requires .env with Supabase credentials)
|
|
406
|
+
npm test
|
|
407
|
+
|
|
339
408
|
# Clean
|
|
340
409
|
npm run clean
|
|
341
410
|
```
|
|
342
411
|
|
|
412
|
+
### Running Tests
|
|
413
|
+
|
|
414
|
+
Copy `.env.example` to `.env` and fill in your Supabase credentials:
|
|
415
|
+
|
|
416
|
+
```bash
|
|
417
|
+
cp .env.example .env
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
Then run:
|
|
421
|
+
|
|
422
|
+
```bash
|
|
423
|
+
npm test
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
---
|
|
427
|
+
|
|
343
428
|
## License
|
|
344
429
|
|
|
345
430
|
MIT
|
package/dist/index.d.mts
CHANGED
|
@@ -18,16 +18,8 @@ interface ScoreSubmission {
|
|
|
18
18
|
/** Optional metadata to attach to the score */
|
|
19
19
|
metadata?: Record<string, unknown>;
|
|
20
20
|
}
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
player_name: string;
|
|
24
|
-
}
|
|
25
|
-
interface ClaimRequest {
|
|
26
|
-
/** Player GUID to assign to the migrated score */
|
|
27
|
-
player_guid: string;
|
|
28
|
-
/** Player name to match against migrated scores */
|
|
29
|
-
player_name: string;
|
|
30
|
-
}
|
|
21
|
+
/** Reset schedule options for leaderboards */
|
|
22
|
+
type ResetSchedule = 'none' | 'daily' | 'weekly' | 'monthly';
|
|
31
23
|
interface ScoreResponse {
|
|
32
24
|
/** Score ID in the database */
|
|
33
25
|
id: string;
|
|
@@ -55,8 +47,16 @@ interface LeaderboardEntry {
|
|
|
55
47
|
interface LeaderboardResponse {
|
|
56
48
|
/** Array of leaderboard entries */
|
|
57
49
|
entries: LeaderboardEntry[];
|
|
58
|
-
/** Total number of scores
|
|
50
|
+
/** Total number of scores in this version/period */
|
|
59
51
|
total_count: number;
|
|
52
|
+
/** The reset schedule of this leaderboard */
|
|
53
|
+
reset_schedule: ResetSchedule;
|
|
54
|
+
/** Current version number — only present when reset_schedule is not 'none' */
|
|
55
|
+
version?: number;
|
|
56
|
+
/** Oldest available version number — only present when reset_schedule is not 'none' */
|
|
57
|
+
oldest_version?: number;
|
|
58
|
+
/** ISO timestamp of when the next reset occurs — only present when reset_schedule is not 'none' */
|
|
59
|
+
next_reset?: string;
|
|
60
60
|
}
|
|
61
61
|
interface PlayerResponse {
|
|
62
62
|
/** Score ID in the database */
|
|
@@ -88,16 +88,6 @@ interface HealthResponse {
|
|
|
88
88
|
/** Server timestamp */
|
|
89
89
|
timestamp: string;
|
|
90
90
|
}
|
|
91
|
-
interface ApiSuccessResponse<T> {
|
|
92
|
-
success: true;
|
|
93
|
-
data: T;
|
|
94
|
-
}
|
|
95
|
-
interface ApiErrorResponse {
|
|
96
|
-
success: false;
|
|
97
|
-
error: string;
|
|
98
|
-
code: string;
|
|
99
|
-
}
|
|
100
|
-
type ApiResponse<T> = ApiSuccessResponse<T> | ApiErrorResponse;
|
|
101
91
|
declare class KeeperBoardError extends Error {
|
|
102
92
|
readonly code: string;
|
|
103
93
|
readonly statusCode: number;
|
|
@@ -114,63 +104,102 @@ declare class KeeperBoardClient {
|
|
|
114
104
|
private readonly apiKey;
|
|
115
105
|
constructor(config: KeeperBoardConfig);
|
|
116
106
|
/**
|
|
117
|
-
* Submit a score to the leaderboard.
|
|
107
|
+
* Submit a score to the default leaderboard.
|
|
108
|
+
* Only updates if the new score is higher than the existing one.
|
|
109
|
+
*/
|
|
110
|
+
submitScore(playerGuid: string, playerName: string, score: number): Promise<ScoreResponse>;
|
|
111
|
+
/**
|
|
112
|
+
* Submit a score to a specific leaderboard.
|
|
118
113
|
* Only updates if the new score is higher than the existing one.
|
|
114
|
+
*/
|
|
115
|
+
submitScore(playerGuid: string, playerName: string, score: number, leaderboard: string): Promise<ScoreResponse>;
|
|
116
|
+
/**
|
|
117
|
+
* Submit a score with metadata.
|
|
118
|
+
* Only updates if the new score is higher than the existing one.
|
|
119
|
+
*/
|
|
120
|
+
submitScore(playerGuid: string, playerName: string, score: number, leaderboard: string, metadata: Record<string, unknown>): Promise<ScoreResponse>;
|
|
121
|
+
/**
|
|
122
|
+
* Get the default leaderboard (top 10 entries).
|
|
119
123
|
*
|
|
120
|
-
* @
|
|
121
|
-
*
|
|
122
|
-
* @param score - Score value
|
|
123
|
-
* @param metadata - Optional metadata to attach
|
|
124
|
-
* @param leaderboardSlug - Optional leaderboard slug (uses default if not specified)
|
|
125
|
-
* @returns Score response with rank and whether it's a new high score
|
|
124
|
+
* @example
|
|
125
|
+
* const lb = await client.getLeaderboard();
|
|
126
126
|
*/
|
|
127
|
-
|
|
127
|
+
getLeaderboard(): Promise<LeaderboardResponse>;
|
|
128
128
|
/**
|
|
129
|
-
* Get
|
|
129
|
+
* Get a specific leaderboard by name (top 10 entries).
|
|
130
130
|
*
|
|
131
|
-
* @
|
|
132
|
-
*
|
|
133
|
-
* @param leaderboardSlug - Optional leaderboard slug (uses default if not specified)
|
|
134
|
-
* @returns Leaderboard entries with total count
|
|
131
|
+
* @example
|
|
132
|
+
* const lb = await client.getLeaderboard('Weekly Best');
|
|
135
133
|
*/
|
|
136
|
-
getLeaderboard(
|
|
134
|
+
getLeaderboard(name: string): Promise<LeaderboardResponse>;
|
|
137
135
|
/**
|
|
138
|
-
* Get a
|
|
136
|
+
* Get a leaderboard with custom limit.
|
|
139
137
|
*
|
|
140
|
-
* @
|
|
141
|
-
*
|
|
142
|
-
* @returns Player's score and rank, or null if not found
|
|
138
|
+
* @example
|
|
139
|
+
* const lb = await client.getLeaderboard('Weekly Best', 25);
|
|
143
140
|
*/
|
|
144
|
-
|
|
141
|
+
getLeaderboard(name: string, limit: number): Promise<LeaderboardResponse>;
|
|
145
142
|
/**
|
|
146
|
-
*
|
|
143
|
+
* Get a leaderboard with pagination.
|
|
147
144
|
*
|
|
148
|
-
* @
|
|
149
|
-
*
|
|
150
|
-
* @param leaderboardSlug - Optional leaderboard slug (uses default if not specified)
|
|
151
|
-
* @returns Updated player info
|
|
145
|
+
* @example
|
|
146
|
+
* const page2 = await client.getLeaderboard('Weekly Best', 10, 10);
|
|
152
147
|
*/
|
|
153
|
-
|
|
148
|
+
getLeaderboard(name: string, limit: number, offset: number): Promise<LeaderboardResponse>;
|
|
154
149
|
/**
|
|
155
|
-
*
|
|
156
|
-
* Used when scores were imported (e.g., from CSV) without player GUIDs.
|
|
150
|
+
* Get a specific version of a time-based leaderboard.
|
|
157
151
|
*
|
|
158
|
-
* @
|
|
159
|
-
*
|
|
160
|
-
*
|
|
161
|
-
* @returns Claim result with score and rank
|
|
152
|
+
* @example
|
|
153
|
+
* // Get last week's leaderboard (version 3)
|
|
154
|
+
* const lastWeek = await client.getLeaderboardVersion('Weekly Best', 3);
|
|
162
155
|
*/
|
|
163
|
-
|
|
156
|
+
getLeaderboardVersion(name: string, version: number): Promise<LeaderboardResponse>;
|
|
164
157
|
/**
|
|
165
|
-
*
|
|
166
|
-
|
|
158
|
+
* Get a specific version with custom limit.
|
|
159
|
+
*/
|
|
160
|
+
getLeaderboardVersion(name: string, version: number, limit: number): Promise<LeaderboardResponse>;
|
|
161
|
+
/**
|
|
162
|
+
* Get a specific version with pagination.
|
|
163
|
+
*/
|
|
164
|
+
getLeaderboardVersion(name: string, version: number, limit: number, offset: number): Promise<LeaderboardResponse>;
|
|
165
|
+
/**
|
|
166
|
+
* Get a player's rank and score on the default leaderboard.
|
|
167
|
+
* Returns null if the player has no score.
|
|
167
168
|
*
|
|
168
|
-
* @
|
|
169
|
+
* @example
|
|
170
|
+
* const player = await client.getPlayerRank(playerGuid);
|
|
171
|
+
* if (player) {
|
|
172
|
+
* console.log(`You are ranked #${player.rank}`);
|
|
173
|
+
* }
|
|
169
174
|
*/
|
|
170
|
-
|
|
175
|
+
getPlayerRank(playerGuid: string): Promise<PlayerResponse | null>;
|
|
176
|
+
/**
|
|
177
|
+
* Get a player's rank and score on a specific leaderboard.
|
|
178
|
+
* Returns null if the player has no score.
|
|
179
|
+
*/
|
|
180
|
+
getPlayerRank(playerGuid: string, leaderboard: string): Promise<PlayerResponse | null>;
|
|
181
|
+
/**
|
|
182
|
+
* Update a player's display name on the default leaderboard.
|
|
183
|
+
*/
|
|
184
|
+
updatePlayerName(playerGuid: string, newName: string): Promise<PlayerResponse>;
|
|
185
|
+
/**
|
|
186
|
+
* Update a player's display name on a specific leaderboard.
|
|
187
|
+
*/
|
|
188
|
+
updatePlayerName(playerGuid: string, newName: string, leaderboard: string): Promise<PlayerResponse>;
|
|
171
189
|
/**
|
|
172
|
-
*
|
|
190
|
+
* Claim a migrated score by matching player name.
|
|
191
|
+
* Used when scores were imported without player GUIDs.
|
|
173
192
|
*/
|
|
193
|
+
claimScore(playerGuid: string, playerName: string): Promise<ClaimResponse>;
|
|
194
|
+
/**
|
|
195
|
+
* Claim a migrated score on a specific leaderboard.
|
|
196
|
+
*/
|
|
197
|
+
claimScore(playerGuid: string, playerName: string, leaderboard: string): Promise<ClaimResponse>;
|
|
198
|
+
/**
|
|
199
|
+
* Check if the API is healthy.
|
|
200
|
+
* This endpoint does not require an API key.
|
|
201
|
+
*/
|
|
202
|
+
healthCheck(): Promise<HealthResponse>;
|
|
174
203
|
private request;
|
|
175
204
|
}
|
|
176
205
|
|
|
@@ -223,4 +252,4 @@ declare class PlayerIdentity {
|
|
|
223
252
|
private generateUUID;
|
|
224
253
|
}
|
|
225
254
|
|
|
226
|
-
export { type
|
|
255
|
+
export { type ClaimResponse, type HealthResponse, KeeperBoardClient, type KeeperBoardConfig, KeeperBoardError, type LeaderboardEntry, type LeaderboardResponse, PlayerIdentity, type PlayerIdentityConfig, type PlayerResponse, type ResetSchedule, type ScoreResponse, type ScoreSubmission };
|
package/dist/index.d.ts
CHANGED
|
@@ -18,16 +18,8 @@ interface ScoreSubmission {
|
|
|
18
18
|
/** Optional metadata to attach to the score */
|
|
19
19
|
metadata?: Record<string, unknown>;
|
|
20
20
|
}
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
player_name: string;
|
|
24
|
-
}
|
|
25
|
-
interface ClaimRequest {
|
|
26
|
-
/** Player GUID to assign to the migrated score */
|
|
27
|
-
player_guid: string;
|
|
28
|
-
/** Player name to match against migrated scores */
|
|
29
|
-
player_name: string;
|
|
30
|
-
}
|
|
21
|
+
/** Reset schedule options for leaderboards */
|
|
22
|
+
type ResetSchedule = 'none' | 'daily' | 'weekly' | 'monthly';
|
|
31
23
|
interface ScoreResponse {
|
|
32
24
|
/** Score ID in the database */
|
|
33
25
|
id: string;
|
|
@@ -55,8 +47,16 @@ interface LeaderboardEntry {
|
|
|
55
47
|
interface LeaderboardResponse {
|
|
56
48
|
/** Array of leaderboard entries */
|
|
57
49
|
entries: LeaderboardEntry[];
|
|
58
|
-
/** Total number of scores
|
|
50
|
+
/** Total number of scores in this version/period */
|
|
59
51
|
total_count: number;
|
|
52
|
+
/** The reset schedule of this leaderboard */
|
|
53
|
+
reset_schedule: ResetSchedule;
|
|
54
|
+
/** Current version number — only present when reset_schedule is not 'none' */
|
|
55
|
+
version?: number;
|
|
56
|
+
/** Oldest available version number — only present when reset_schedule is not 'none' */
|
|
57
|
+
oldest_version?: number;
|
|
58
|
+
/** ISO timestamp of when the next reset occurs — only present when reset_schedule is not 'none' */
|
|
59
|
+
next_reset?: string;
|
|
60
60
|
}
|
|
61
61
|
interface PlayerResponse {
|
|
62
62
|
/** Score ID in the database */
|
|
@@ -88,16 +88,6 @@ interface HealthResponse {
|
|
|
88
88
|
/** Server timestamp */
|
|
89
89
|
timestamp: string;
|
|
90
90
|
}
|
|
91
|
-
interface ApiSuccessResponse<T> {
|
|
92
|
-
success: true;
|
|
93
|
-
data: T;
|
|
94
|
-
}
|
|
95
|
-
interface ApiErrorResponse {
|
|
96
|
-
success: false;
|
|
97
|
-
error: string;
|
|
98
|
-
code: string;
|
|
99
|
-
}
|
|
100
|
-
type ApiResponse<T> = ApiSuccessResponse<T> | ApiErrorResponse;
|
|
101
91
|
declare class KeeperBoardError extends Error {
|
|
102
92
|
readonly code: string;
|
|
103
93
|
readonly statusCode: number;
|
|
@@ -114,63 +104,102 @@ declare class KeeperBoardClient {
|
|
|
114
104
|
private readonly apiKey;
|
|
115
105
|
constructor(config: KeeperBoardConfig);
|
|
116
106
|
/**
|
|
117
|
-
* Submit a score to the leaderboard.
|
|
107
|
+
* Submit a score to the default leaderboard.
|
|
108
|
+
* Only updates if the new score is higher than the existing one.
|
|
109
|
+
*/
|
|
110
|
+
submitScore(playerGuid: string, playerName: string, score: number): Promise<ScoreResponse>;
|
|
111
|
+
/**
|
|
112
|
+
* Submit a score to a specific leaderboard.
|
|
118
113
|
* Only updates if the new score is higher than the existing one.
|
|
114
|
+
*/
|
|
115
|
+
submitScore(playerGuid: string, playerName: string, score: number, leaderboard: string): Promise<ScoreResponse>;
|
|
116
|
+
/**
|
|
117
|
+
* Submit a score with metadata.
|
|
118
|
+
* Only updates if the new score is higher than the existing one.
|
|
119
|
+
*/
|
|
120
|
+
submitScore(playerGuid: string, playerName: string, score: number, leaderboard: string, metadata: Record<string, unknown>): Promise<ScoreResponse>;
|
|
121
|
+
/**
|
|
122
|
+
* Get the default leaderboard (top 10 entries).
|
|
119
123
|
*
|
|
120
|
-
* @
|
|
121
|
-
*
|
|
122
|
-
* @param score - Score value
|
|
123
|
-
* @param metadata - Optional metadata to attach
|
|
124
|
-
* @param leaderboardSlug - Optional leaderboard slug (uses default if not specified)
|
|
125
|
-
* @returns Score response with rank and whether it's a new high score
|
|
124
|
+
* @example
|
|
125
|
+
* const lb = await client.getLeaderboard();
|
|
126
126
|
*/
|
|
127
|
-
|
|
127
|
+
getLeaderboard(): Promise<LeaderboardResponse>;
|
|
128
128
|
/**
|
|
129
|
-
* Get
|
|
129
|
+
* Get a specific leaderboard by name (top 10 entries).
|
|
130
130
|
*
|
|
131
|
-
* @
|
|
132
|
-
*
|
|
133
|
-
* @param leaderboardSlug - Optional leaderboard slug (uses default if not specified)
|
|
134
|
-
* @returns Leaderboard entries with total count
|
|
131
|
+
* @example
|
|
132
|
+
* const lb = await client.getLeaderboard('Weekly Best');
|
|
135
133
|
*/
|
|
136
|
-
getLeaderboard(
|
|
134
|
+
getLeaderboard(name: string): Promise<LeaderboardResponse>;
|
|
137
135
|
/**
|
|
138
|
-
* Get a
|
|
136
|
+
* Get a leaderboard with custom limit.
|
|
139
137
|
*
|
|
140
|
-
* @
|
|
141
|
-
*
|
|
142
|
-
* @returns Player's score and rank, or null if not found
|
|
138
|
+
* @example
|
|
139
|
+
* const lb = await client.getLeaderboard('Weekly Best', 25);
|
|
143
140
|
*/
|
|
144
|
-
|
|
141
|
+
getLeaderboard(name: string, limit: number): Promise<LeaderboardResponse>;
|
|
145
142
|
/**
|
|
146
|
-
*
|
|
143
|
+
* Get a leaderboard with pagination.
|
|
147
144
|
*
|
|
148
|
-
* @
|
|
149
|
-
*
|
|
150
|
-
* @param leaderboardSlug - Optional leaderboard slug (uses default if not specified)
|
|
151
|
-
* @returns Updated player info
|
|
145
|
+
* @example
|
|
146
|
+
* const page2 = await client.getLeaderboard('Weekly Best', 10, 10);
|
|
152
147
|
*/
|
|
153
|
-
|
|
148
|
+
getLeaderboard(name: string, limit: number, offset: number): Promise<LeaderboardResponse>;
|
|
154
149
|
/**
|
|
155
|
-
*
|
|
156
|
-
* Used when scores were imported (e.g., from CSV) without player GUIDs.
|
|
150
|
+
* Get a specific version of a time-based leaderboard.
|
|
157
151
|
*
|
|
158
|
-
* @
|
|
159
|
-
*
|
|
160
|
-
*
|
|
161
|
-
* @returns Claim result with score and rank
|
|
152
|
+
* @example
|
|
153
|
+
* // Get last week's leaderboard (version 3)
|
|
154
|
+
* const lastWeek = await client.getLeaderboardVersion('Weekly Best', 3);
|
|
162
155
|
*/
|
|
163
|
-
|
|
156
|
+
getLeaderboardVersion(name: string, version: number): Promise<LeaderboardResponse>;
|
|
164
157
|
/**
|
|
165
|
-
*
|
|
166
|
-
|
|
158
|
+
* Get a specific version with custom limit.
|
|
159
|
+
*/
|
|
160
|
+
getLeaderboardVersion(name: string, version: number, limit: number): Promise<LeaderboardResponse>;
|
|
161
|
+
/**
|
|
162
|
+
* Get a specific version with pagination.
|
|
163
|
+
*/
|
|
164
|
+
getLeaderboardVersion(name: string, version: number, limit: number, offset: number): Promise<LeaderboardResponse>;
|
|
165
|
+
/**
|
|
166
|
+
* Get a player's rank and score on the default leaderboard.
|
|
167
|
+
* Returns null if the player has no score.
|
|
167
168
|
*
|
|
168
|
-
* @
|
|
169
|
+
* @example
|
|
170
|
+
* const player = await client.getPlayerRank(playerGuid);
|
|
171
|
+
* if (player) {
|
|
172
|
+
* console.log(`You are ranked #${player.rank}`);
|
|
173
|
+
* }
|
|
169
174
|
*/
|
|
170
|
-
|
|
175
|
+
getPlayerRank(playerGuid: string): Promise<PlayerResponse | null>;
|
|
176
|
+
/**
|
|
177
|
+
* Get a player's rank and score on a specific leaderboard.
|
|
178
|
+
* Returns null if the player has no score.
|
|
179
|
+
*/
|
|
180
|
+
getPlayerRank(playerGuid: string, leaderboard: string): Promise<PlayerResponse | null>;
|
|
181
|
+
/**
|
|
182
|
+
* Update a player's display name on the default leaderboard.
|
|
183
|
+
*/
|
|
184
|
+
updatePlayerName(playerGuid: string, newName: string): Promise<PlayerResponse>;
|
|
185
|
+
/**
|
|
186
|
+
* Update a player's display name on a specific leaderboard.
|
|
187
|
+
*/
|
|
188
|
+
updatePlayerName(playerGuid: string, newName: string, leaderboard: string): Promise<PlayerResponse>;
|
|
171
189
|
/**
|
|
172
|
-
*
|
|
190
|
+
* Claim a migrated score by matching player name.
|
|
191
|
+
* Used when scores were imported without player GUIDs.
|
|
173
192
|
*/
|
|
193
|
+
claimScore(playerGuid: string, playerName: string): Promise<ClaimResponse>;
|
|
194
|
+
/**
|
|
195
|
+
* Claim a migrated score on a specific leaderboard.
|
|
196
|
+
*/
|
|
197
|
+
claimScore(playerGuid: string, playerName: string, leaderboard: string): Promise<ClaimResponse>;
|
|
198
|
+
/**
|
|
199
|
+
* Check if the API is healthy.
|
|
200
|
+
* This endpoint does not require an API key.
|
|
201
|
+
*/
|
|
202
|
+
healthCheck(): Promise<HealthResponse>;
|
|
174
203
|
private request;
|
|
175
204
|
}
|
|
176
205
|
|
|
@@ -223,4 +252,4 @@ declare class PlayerIdentity {
|
|
|
223
252
|
private generateUUID;
|
|
224
253
|
}
|
|
225
254
|
|
|
226
|
-
export { type
|
|
255
|
+
export { type ClaimResponse, type HealthResponse, KeeperBoardClient, type KeeperBoardConfig, KeeperBoardError, type LeaderboardEntry, type LeaderboardResponse, PlayerIdentity, type PlayerIdentityConfig, type PlayerResponse, type ResetSchedule, type ScoreResponse, type ScoreSubmission };
|
package/dist/index.js
CHANGED
|
@@ -42,21 +42,10 @@ var KeeperBoardClient = class {
|
|
|
42
42
|
this.apiUrl = config.apiUrl.replace(/\/$/, "");
|
|
43
43
|
this.apiKey = config.apiKey;
|
|
44
44
|
}
|
|
45
|
-
|
|
46
|
-
* Submit a score to the leaderboard.
|
|
47
|
-
* Only updates if the new score is higher than the existing one.
|
|
48
|
-
*
|
|
49
|
-
* @param playerGuid - Unique player identifier
|
|
50
|
-
* @param playerName - Player display name
|
|
51
|
-
* @param score - Score value
|
|
52
|
-
* @param metadata - Optional metadata to attach
|
|
53
|
-
* @param leaderboardSlug - Optional leaderboard slug (uses default if not specified)
|
|
54
|
-
* @returns Score response with rank and whether it's a new high score
|
|
55
|
-
*/
|
|
56
|
-
async submitScore(playerGuid, playerName, score, metadata, leaderboardSlug) {
|
|
45
|
+
async submitScore(playerGuid, playerName, score, leaderboard, metadata) {
|
|
57
46
|
const params = new URLSearchParams();
|
|
58
|
-
if (
|
|
59
|
-
params.set("leaderboard",
|
|
47
|
+
if (leaderboard) {
|
|
48
|
+
params.set("leaderboard", leaderboard);
|
|
60
49
|
}
|
|
61
50
|
const url = `${this.apiUrl}/api/v1/scores${params.toString() ? "?" + params.toString() : ""}`;
|
|
62
51
|
const body = {
|
|
@@ -65,49 +54,38 @@ var KeeperBoardClient = class {
|
|
|
65
54
|
score,
|
|
66
55
|
...metadata && { metadata }
|
|
67
56
|
};
|
|
68
|
-
|
|
57
|
+
return this.request(url, {
|
|
69
58
|
method: "POST",
|
|
70
59
|
body: JSON.stringify(body)
|
|
71
60
|
});
|
|
72
|
-
return response;
|
|
73
61
|
}
|
|
74
|
-
|
|
75
|
-
* Get the leaderboard entries with pagination.
|
|
76
|
-
*
|
|
77
|
-
* @param limit - Maximum number of entries to return (default: 10, max: 100)
|
|
78
|
-
* @param offset - Pagination offset (default: 0)
|
|
79
|
-
* @param leaderboardSlug - Optional leaderboard slug (uses default if not specified)
|
|
80
|
-
* @returns Leaderboard entries with total count
|
|
81
|
-
*/
|
|
82
|
-
async getLeaderboard(limit = 10, offset = 0, leaderboardSlug) {
|
|
62
|
+
async getLeaderboard(name, limit = 10, offset = 0) {
|
|
83
63
|
const params = new URLSearchParams();
|
|
84
64
|
params.set("limit", String(Math.min(limit, 100)));
|
|
85
65
|
params.set("offset", String(offset));
|
|
86
|
-
if (
|
|
87
|
-
params.set("leaderboard",
|
|
66
|
+
if (name) {
|
|
67
|
+
params.set("leaderboard", name);
|
|
88
68
|
}
|
|
89
69
|
const url = `${this.apiUrl}/api/v1/leaderboard?${params.toString()}`;
|
|
90
|
-
return this.request(url, {
|
|
91
|
-
method: "GET"
|
|
92
|
-
});
|
|
70
|
+
return this.request(url, { method: "GET" });
|
|
93
71
|
}
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
72
|
+
async getLeaderboardVersion(name, version, limit = 10, offset = 0) {
|
|
73
|
+
const params = new URLSearchParams();
|
|
74
|
+
params.set("leaderboard", name);
|
|
75
|
+
params.set("version", String(version));
|
|
76
|
+
params.set("limit", String(Math.min(limit, 100)));
|
|
77
|
+
params.set("offset", String(offset));
|
|
78
|
+
const url = `${this.apiUrl}/api/v1/leaderboard?${params.toString()}`;
|
|
79
|
+
return this.request(url, { method: "GET" });
|
|
80
|
+
}
|
|
81
|
+
async getPlayerRank(playerGuid, leaderboard) {
|
|
102
82
|
const params = new URLSearchParams();
|
|
103
|
-
if (
|
|
104
|
-
params.set("leaderboard",
|
|
83
|
+
if (leaderboard) {
|
|
84
|
+
params.set("leaderboard", leaderboard);
|
|
105
85
|
}
|
|
106
86
|
const url = `${this.apiUrl}/api/v1/player/${encodeURIComponent(playerGuid)}${params.toString() ? "?" + params.toString() : ""}`;
|
|
107
87
|
try {
|
|
108
|
-
return await this.request(url, {
|
|
109
|
-
method: "GET"
|
|
110
|
-
});
|
|
88
|
+
return await this.request(url, { method: "GET" });
|
|
111
89
|
} catch (error) {
|
|
112
90
|
if (error instanceof KeeperBoardError && error.code === "NOT_FOUND") {
|
|
113
91
|
return null;
|
|
@@ -115,18 +93,10 @@ var KeeperBoardClient = class {
|
|
|
115
93
|
throw error;
|
|
116
94
|
}
|
|
117
95
|
}
|
|
118
|
-
|
|
119
|
-
* Update a player's display name.
|
|
120
|
-
*
|
|
121
|
-
* @param playerGuid - Player's unique identifier
|
|
122
|
-
* @param newName - New display name
|
|
123
|
-
* @param leaderboardSlug - Optional leaderboard slug (uses default if not specified)
|
|
124
|
-
* @returns Updated player info
|
|
125
|
-
*/
|
|
126
|
-
async updatePlayerName(playerGuid, newName, leaderboardSlug) {
|
|
96
|
+
async updatePlayerName(playerGuid, newName, leaderboard) {
|
|
127
97
|
const params = new URLSearchParams();
|
|
128
|
-
if (
|
|
129
|
-
params.set("leaderboard",
|
|
98
|
+
if (leaderboard) {
|
|
99
|
+
params.set("leaderboard", leaderboard);
|
|
130
100
|
}
|
|
131
101
|
const url = `${this.apiUrl}/api/v1/player/${encodeURIComponent(playerGuid)}${params.toString() ? "?" + params.toString() : ""}`;
|
|
132
102
|
return this.request(url, {
|
|
@@ -134,19 +104,10 @@ var KeeperBoardClient = class {
|
|
|
134
104
|
body: JSON.stringify({ player_name: newName })
|
|
135
105
|
});
|
|
136
106
|
}
|
|
137
|
-
|
|
138
|
-
* Claim a migrated score by matching player name.
|
|
139
|
-
* Used when scores were imported (e.g., from CSV) without player GUIDs.
|
|
140
|
-
*
|
|
141
|
-
* @param playerGuid - Player GUID to assign to the claimed score
|
|
142
|
-
* @param playerName - Player name to match against migrated scores
|
|
143
|
-
* @param leaderboardSlug - Optional leaderboard slug (uses default if not specified)
|
|
144
|
-
* @returns Claim result with score and rank
|
|
145
|
-
*/
|
|
146
|
-
async claimScore(playerGuid, playerName, leaderboardSlug) {
|
|
107
|
+
async claimScore(playerGuid, playerName, leaderboard) {
|
|
147
108
|
const params = new URLSearchParams();
|
|
148
|
-
if (
|
|
149
|
-
params.set("leaderboard",
|
|
109
|
+
if (leaderboard) {
|
|
110
|
+
params.set("leaderboard", leaderboard);
|
|
150
111
|
}
|
|
151
112
|
const url = `${this.apiUrl}/api/v1/claim${params.toString() ? "?" + params.toString() : ""}`;
|
|
152
113
|
return this.request(url, {
|
|
@@ -157,19 +118,18 @@ var KeeperBoardClient = class {
|
|
|
157
118
|
})
|
|
158
119
|
});
|
|
159
120
|
}
|
|
121
|
+
// ============================================
|
|
122
|
+
// HEALTH CHECK
|
|
123
|
+
// ============================================
|
|
160
124
|
/**
|
|
161
125
|
* Check if the API is healthy.
|
|
162
126
|
* This endpoint does not require an API key.
|
|
163
|
-
*
|
|
164
|
-
* @returns Health status with version and timestamp
|
|
165
127
|
*/
|
|
166
128
|
async healthCheck() {
|
|
167
129
|
const url = `${this.apiUrl}/api/v1/health`;
|
|
168
130
|
const response = await fetch(url, {
|
|
169
131
|
method: "GET",
|
|
170
|
-
headers: {
|
|
171
|
-
Accept: "application/json"
|
|
172
|
-
}
|
|
132
|
+
headers: { Accept: "application/json" }
|
|
173
133
|
});
|
|
174
134
|
const json = await response.json();
|
|
175
135
|
if (!json.success) {
|
|
@@ -177,9 +137,9 @@ var KeeperBoardClient = class {
|
|
|
177
137
|
}
|
|
178
138
|
return json.data;
|
|
179
139
|
}
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
140
|
+
// ============================================
|
|
141
|
+
// INTERNAL
|
|
142
|
+
// ============================================
|
|
183
143
|
async request(url, options) {
|
|
184
144
|
const headers = {
|
|
185
145
|
"Content-Type": "application/json",
|
|
@@ -188,10 +148,7 @@ var KeeperBoardClient = class {
|
|
|
188
148
|
};
|
|
189
149
|
const response = await fetch(url, {
|
|
190
150
|
...options,
|
|
191
|
-
headers: {
|
|
192
|
-
...headers,
|
|
193
|
-
...options.headers || {}
|
|
194
|
-
}
|
|
151
|
+
headers: { ...headers, ...options.headers || {} }
|
|
195
152
|
});
|
|
196
153
|
const json = await response.json();
|
|
197
154
|
if (!json.success) {
|
package/dist/index.mjs
CHANGED
|
@@ -14,21 +14,10 @@ var KeeperBoardClient = class {
|
|
|
14
14
|
this.apiUrl = config.apiUrl.replace(/\/$/, "");
|
|
15
15
|
this.apiKey = config.apiKey;
|
|
16
16
|
}
|
|
17
|
-
|
|
18
|
-
* Submit a score to the leaderboard.
|
|
19
|
-
* Only updates if the new score is higher than the existing one.
|
|
20
|
-
*
|
|
21
|
-
* @param playerGuid - Unique player identifier
|
|
22
|
-
* @param playerName - Player display name
|
|
23
|
-
* @param score - Score value
|
|
24
|
-
* @param metadata - Optional metadata to attach
|
|
25
|
-
* @param leaderboardSlug - Optional leaderboard slug (uses default if not specified)
|
|
26
|
-
* @returns Score response with rank and whether it's a new high score
|
|
27
|
-
*/
|
|
28
|
-
async submitScore(playerGuid, playerName, score, metadata, leaderboardSlug) {
|
|
17
|
+
async submitScore(playerGuid, playerName, score, leaderboard, metadata) {
|
|
29
18
|
const params = new URLSearchParams();
|
|
30
|
-
if (
|
|
31
|
-
params.set("leaderboard",
|
|
19
|
+
if (leaderboard) {
|
|
20
|
+
params.set("leaderboard", leaderboard);
|
|
32
21
|
}
|
|
33
22
|
const url = `${this.apiUrl}/api/v1/scores${params.toString() ? "?" + params.toString() : ""}`;
|
|
34
23
|
const body = {
|
|
@@ -37,49 +26,38 @@ var KeeperBoardClient = class {
|
|
|
37
26
|
score,
|
|
38
27
|
...metadata && { metadata }
|
|
39
28
|
};
|
|
40
|
-
|
|
29
|
+
return this.request(url, {
|
|
41
30
|
method: "POST",
|
|
42
31
|
body: JSON.stringify(body)
|
|
43
32
|
});
|
|
44
|
-
return response;
|
|
45
33
|
}
|
|
46
|
-
|
|
47
|
-
* Get the leaderboard entries with pagination.
|
|
48
|
-
*
|
|
49
|
-
* @param limit - Maximum number of entries to return (default: 10, max: 100)
|
|
50
|
-
* @param offset - Pagination offset (default: 0)
|
|
51
|
-
* @param leaderboardSlug - Optional leaderboard slug (uses default if not specified)
|
|
52
|
-
* @returns Leaderboard entries with total count
|
|
53
|
-
*/
|
|
54
|
-
async getLeaderboard(limit = 10, offset = 0, leaderboardSlug) {
|
|
34
|
+
async getLeaderboard(name, limit = 10, offset = 0) {
|
|
55
35
|
const params = new URLSearchParams();
|
|
56
36
|
params.set("limit", String(Math.min(limit, 100)));
|
|
57
37
|
params.set("offset", String(offset));
|
|
58
|
-
if (
|
|
59
|
-
params.set("leaderboard",
|
|
38
|
+
if (name) {
|
|
39
|
+
params.set("leaderboard", name);
|
|
60
40
|
}
|
|
61
41
|
const url = `${this.apiUrl}/api/v1/leaderboard?${params.toString()}`;
|
|
62
|
-
return this.request(url, {
|
|
63
|
-
method: "GET"
|
|
64
|
-
});
|
|
42
|
+
return this.request(url, { method: "GET" });
|
|
65
43
|
}
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
44
|
+
async getLeaderboardVersion(name, version, limit = 10, offset = 0) {
|
|
45
|
+
const params = new URLSearchParams();
|
|
46
|
+
params.set("leaderboard", name);
|
|
47
|
+
params.set("version", String(version));
|
|
48
|
+
params.set("limit", String(Math.min(limit, 100)));
|
|
49
|
+
params.set("offset", String(offset));
|
|
50
|
+
const url = `${this.apiUrl}/api/v1/leaderboard?${params.toString()}`;
|
|
51
|
+
return this.request(url, { method: "GET" });
|
|
52
|
+
}
|
|
53
|
+
async getPlayerRank(playerGuid, leaderboard) {
|
|
74
54
|
const params = new URLSearchParams();
|
|
75
|
-
if (
|
|
76
|
-
params.set("leaderboard",
|
|
55
|
+
if (leaderboard) {
|
|
56
|
+
params.set("leaderboard", leaderboard);
|
|
77
57
|
}
|
|
78
58
|
const url = `${this.apiUrl}/api/v1/player/${encodeURIComponent(playerGuid)}${params.toString() ? "?" + params.toString() : ""}`;
|
|
79
59
|
try {
|
|
80
|
-
return await this.request(url, {
|
|
81
|
-
method: "GET"
|
|
82
|
-
});
|
|
60
|
+
return await this.request(url, { method: "GET" });
|
|
83
61
|
} catch (error) {
|
|
84
62
|
if (error instanceof KeeperBoardError && error.code === "NOT_FOUND") {
|
|
85
63
|
return null;
|
|
@@ -87,18 +65,10 @@ var KeeperBoardClient = class {
|
|
|
87
65
|
throw error;
|
|
88
66
|
}
|
|
89
67
|
}
|
|
90
|
-
|
|
91
|
-
* Update a player's display name.
|
|
92
|
-
*
|
|
93
|
-
* @param playerGuid - Player's unique identifier
|
|
94
|
-
* @param newName - New display name
|
|
95
|
-
* @param leaderboardSlug - Optional leaderboard slug (uses default if not specified)
|
|
96
|
-
* @returns Updated player info
|
|
97
|
-
*/
|
|
98
|
-
async updatePlayerName(playerGuid, newName, leaderboardSlug) {
|
|
68
|
+
async updatePlayerName(playerGuid, newName, leaderboard) {
|
|
99
69
|
const params = new URLSearchParams();
|
|
100
|
-
if (
|
|
101
|
-
params.set("leaderboard",
|
|
70
|
+
if (leaderboard) {
|
|
71
|
+
params.set("leaderboard", leaderboard);
|
|
102
72
|
}
|
|
103
73
|
const url = `${this.apiUrl}/api/v1/player/${encodeURIComponent(playerGuid)}${params.toString() ? "?" + params.toString() : ""}`;
|
|
104
74
|
return this.request(url, {
|
|
@@ -106,19 +76,10 @@ var KeeperBoardClient = class {
|
|
|
106
76
|
body: JSON.stringify({ player_name: newName })
|
|
107
77
|
});
|
|
108
78
|
}
|
|
109
|
-
|
|
110
|
-
* Claim a migrated score by matching player name.
|
|
111
|
-
* Used when scores were imported (e.g., from CSV) without player GUIDs.
|
|
112
|
-
*
|
|
113
|
-
* @param playerGuid - Player GUID to assign to the claimed score
|
|
114
|
-
* @param playerName - Player name to match against migrated scores
|
|
115
|
-
* @param leaderboardSlug - Optional leaderboard slug (uses default if not specified)
|
|
116
|
-
* @returns Claim result with score and rank
|
|
117
|
-
*/
|
|
118
|
-
async claimScore(playerGuid, playerName, leaderboardSlug) {
|
|
79
|
+
async claimScore(playerGuid, playerName, leaderboard) {
|
|
119
80
|
const params = new URLSearchParams();
|
|
120
|
-
if (
|
|
121
|
-
params.set("leaderboard",
|
|
81
|
+
if (leaderboard) {
|
|
82
|
+
params.set("leaderboard", leaderboard);
|
|
122
83
|
}
|
|
123
84
|
const url = `${this.apiUrl}/api/v1/claim${params.toString() ? "?" + params.toString() : ""}`;
|
|
124
85
|
return this.request(url, {
|
|
@@ -129,19 +90,18 @@ var KeeperBoardClient = class {
|
|
|
129
90
|
})
|
|
130
91
|
});
|
|
131
92
|
}
|
|
93
|
+
// ============================================
|
|
94
|
+
// HEALTH CHECK
|
|
95
|
+
// ============================================
|
|
132
96
|
/**
|
|
133
97
|
* Check if the API is healthy.
|
|
134
98
|
* This endpoint does not require an API key.
|
|
135
|
-
*
|
|
136
|
-
* @returns Health status with version and timestamp
|
|
137
99
|
*/
|
|
138
100
|
async healthCheck() {
|
|
139
101
|
const url = `${this.apiUrl}/api/v1/health`;
|
|
140
102
|
const response = await fetch(url, {
|
|
141
103
|
method: "GET",
|
|
142
|
-
headers: {
|
|
143
|
-
Accept: "application/json"
|
|
144
|
-
}
|
|
104
|
+
headers: { Accept: "application/json" }
|
|
145
105
|
});
|
|
146
106
|
const json = await response.json();
|
|
147
107
|
if (!json.success) {
|
|
@@ -149,9 +109,9 @@ var KeeperBoardClient = class {
|
|
|
149
109
|
}
|
|
150
110
|
return json.data;
|
|
151
111
|
}
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
112
|
+
// ============================================
|
|
113
|
+
// INTERNAL
|
|
114
|
+
// ============================================
|
|
155
115
|
async request(url, options) {
|
|
156
116
|
const headers = {
|
|
157
117
|
"Content-Type": "application/json",
|
|
@@ -160,10 +120,7 @@ var KeeperBoardClient = class {
|
|
|
160
120
|
};
|
|
161
121
|
const response = await fetch(url, {
|
|
162
122
|
...options,
|
|
163
|
-
headers: {
|
|
164
|
-
...headers,
|
|
165
|
-
...options.headers || {}
|
|
166
|
-
}
|
|
123
|
+
headers: { ...headers, ...options.headers || {} }
|
|
167
124
|
});
|
|
168
125
|
const json = await response.json();
|
|
169
126
|
if (!json.success) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "keeperboard",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.3",
|
|
4
4
|
"description": "TypeScript client SDK for KeeperBoard leaderboard-as-a-service",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.mjs",
|
|
@@ -19,6 +19,8 @@
|
|
|
19
19
|
"build": "tsup src/index.ts --format cjs,esm --dts",
|
|
20
20
|
"typecheck": "tsc --noEmit",
|
|
21
21
|
"clean": "rm -rf dist",
|
|
22
|
+
"test": "vitest run",
|
|
23
|
+
"test:watch": "vitest",
|
|
22
24
|
"prepublishOnly": "npm run build"
|
|
23
25
|
},
|
|
24
26
|
"keywords": [
|
|
@@ -42,8 +44,11 @@
|
|
|
42
44
|
"url": "https://github.com/clauderoy790/keeperboard/issues"
|
|
43
45
|
},
|
|
44
46
|
"devDependencies": {
|
|
47
|
+
"@supabase/supabase-js": "^2.95.3",
|
|
48
|
+
"dotenv": "^17.2.4",
|
|
45
49
|
"tsup": "^8.0.0",
|
|
46
|
-
"typescript": "^5.0.0"
|
|
50
|
+
"typescript": "^5.0.0",
|
|
51
|
+
"vitest": "^4.0.18"
|
|
47
52
|
},
|
|
48
53
|
"engines": {
|
|
49
54
|
"node": ">=18"
|