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