keeperboard-sdk 1.0.1

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 ADDED
@@ -0,0 +1,345 @@
1
+ # KeeperBoard SDK
2
+
3
+ TypeScript client SDK for [KeeperBoard](https://github.com/YOUR_USERNAME/keeperboard) — a free, open-source leaderboard-as-a-service for indie game developers.
4
+
5
+ Works with Phaser.js, vanilla JavaScript, and any TypeScript/JavaScript game running in the browser.
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ npm install keeperboard-sdk
11
+ ```
12
+
13
+ ### Alternative: Copy source directly
14
+
15
+ If you prefer not to use npm, copy the `src/` folder into your project:
16
+
17
+ ```typescript
18
+ import { KeeperBoardClient, PlayerIdentity } from './keeperboard/index';
19
+ ```
20
+
21
+ ## Quick Start
22
+
23
+ ### 1. Get your API key
24
+
25
+ 1. Sign up at your KeeperBoard dashboard
26
+ 2. Create a game
27
+ 3. Generate an API key for your environment (dev/prod)
28
+
29
+ ### 2. Initialize the client
30
+
31
+ ```typescript
32
+ import { KeeperBoardClient, PlayerIdentity } from 'keeperboard-sdk';
33
+
34
+ // Create the API client
35
+ const keeperboard = new KeeperBoardClient({
36
+ apiUrl: 'https://keeperboard.vercel.app',
37
+ apiKey: 'kb_dev_your_api_key_here',
38
+ });
39
+
40
+ // Helper for persistent player identity
41
+ const playerIdentity = new PlayerIdentity();
42
+ ```
43
+
44
+ ### 3. Submit a score
45
+
46
+ ```typescript
47
+ // Get or create a persistent player GUID
48
+ const playerGuid = playerIdentity.getOrCreatePlayerGuid();
49
+
50
+ // Submit a score (only updates if higher than existing)
51
+ const result = await keeperboard.submitScore(playerGuid, 'PlayerName', 1500);
52
+
53
+ console.log(`Rank: #${result.rank}`);
54
+ console.log(`New high score: ${result.is_new_high_score}`);
55
+ ```
56
+
57
+ ### 4. Display the leaderboard
58
+
59
+ ```typescript
60
+ const leaderboard = await keeperboard.getLeaderboard(10);
61
+
62
+ leaderboard.entries.forEach((entry) => {
63
+ console.log(`#${entry.rank} ${entry.player_name}: ${entry.score}`);
64
+ });
65
+ ```
66
+
67
+ ## API Reference
68
+
69
+ ### KeeperBoardClient
70
+
71
+ #### Constructor
72
+
73
+ ```typescript
74
+ const client = new KeeperBoardClient({
75
+ apiUrl: string, // Your KeeperBoard API URL
76
+ apiKey: string, // API key from dashboard (e.g., "kb_dev_...")
77
+ });
78
+ ```
79
+
80
+ #### Methods
81
+
82
+ ##### `submitScore(playerGuid, playerName, score, metadata?, leaderboardSlug?)`
83
+
84
+ Submit a score. Only updates if the new score is higher than the existing one.
85
+
86
+ ```typescript
87
+ const result = await client.submitScore(
88
+ 'player-uuid-123',
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
+ // }
104
+ ```
105
+
106
+ ##### `getLeaderboard(limit?, offset?, leaderboardSlug?)`
107
+
108
+ Get leaderboard entries with pagination.
109
+
110
+ ```typescript
111
+ const result = await client.getLeaderboard(
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
+ // }
122
+ ```
123
+
124
+ ##### `getPlayer(playerGuid, leaderboardSlug?)`
125
+
126
+ Get a player's score and rank. Returns `null` if not found.
127
+
128
+ ```typescript
129
+ const player = await client.getPlayer('player-uuid-123');
130
+
131
+ if (player) {
132
+ console.log(`Score: ${player.score}, Rank: #${player.rank}`);
133
+ }
134
+ ```
135
+
136
+ ##### `updatePlayerName(playerGuid, newName, leaderboardSlug?)`
137
+
138
+ Update a player's display name.
139
+
140
+ ```typescript
141
+ const result = await client.updatePlayerName(
142
+ 'player-uuid-123',
143
+ 'NewPlayerName',
144
+ );
145
+ ```
146
+
147
+ ##### `claimScore(playerGuid, playerName, leaderboardSlug?)`
148
+
149
+ Claim a migrated score by matching player name. Used when scores were imported without player GUIDs.
150
+
151
+ ```typescript
152
+ try {
153
+ const result = await client.claimScore(
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
+ }
163
+ ```
164
+
165
+ ##### `healthCheck()`
166
+
167
+ Check if the API is healthy. Does not require an API key.
168
+
169
+ ```typescript
170
+ const health = await client.healthCheck();
171
+ console.log(`API Version: ${health.version}`);
172
+ ```
173
+
174
+ ### PlayerIdentity
175
+
176
+ Helper for managing persistent player identity in localStorage.
177
+
178
+ ```typescript
179
+ const identity = new PlayerIdentity({
180
+ keyPrefix: 'mygame_', // optional, default: 'keeperboard_'
181
+ });
182
+
183
+ // Get or create a persistent player GUID
184
+ const guid = identity.getOrCreatePlayerGuid();
185
+
186
+ // Store/retrieve player name
187
+ identity.setPlayerName('PlayerName');
188
+ const name = identity.getPlayerName();
189
+
190
+ // Check if identity exists
191
+ if (identity.hasIdentity()) {
192
+ // returning player
193
+ }
194
+
195
+ // Clear identity (e.g., for "Sign Out")
196
+ identity.clear();
197
+ ```
198
+
199
+ ### Error Handling
200
+
201
+ All methods throw `KeeperBoardError` on failure:
202
+
203
+ ```typescript
204
+ import { KeeperBoardError } from 'keeperboard-sdk';
205
+
206
+ try {
207
+ await client.submitScore(playerGuid, name, score);
208
+ } catch (error) {
209
+ if (error instanceof KeeperBoardError) {
210
+ console.error(`Error [${error.code}]: ${error.message}`);
211
+
212
+ switch (error.code) {
213
+ case 'INVALID_API_KEY':
214
+ // Check your API key
215
+ break;
216
+ case 'NOT_FOUND':
217
+ // Player or leaderboard not found
218
+ break;
219
+ case 'INVALID_REQUEST':
220
+ // Check request parameters
221
+ break;
222
+ case 'ALREADY_CLAIMED':
223
+ // Player already has a score
224
+ break;
225
+ }
226
+ }
227
+ }
228
+ ```
229
+
230
+ ## Phaser.js Integration Example
231
+
232
+ ```typescript
233
+ import { KeeperBoardClient, PlayerIdentity } from 'keeperboard-sdk';
234
+
235
+ const keeperboard = new KeeperBoardClient({
236
+ apiUrl: 'https://keeperboard.vercel.app',
237
+ apiKey: 'kb_prod_your_api_key',
238
+ });
239
+
240
+ const playerIdentity = new PlayerIdentity();
241
+
242
+ class GameOverScene extends Phaser.Scene {
243
+ private score: number = 0;
244
+
245
+ init(data: { score: number }) {
246
+ this.score = data.score;
247
+ }
248
+
249
+ async create() {
250
+ const playerGuid = playerIdentity.getOrCreatePlayerGuid();
251
+ const playerName = playerIdentity.getPlayerName() ?? 'Anonymous';
252
+
253
+ // Submit score
254
+ const result = await keeperboard.submitScore(
255
+ playerGuid,
256
+ playerName,
257
+ this.score,
258
+ );
259
+
260
+ // Display result
261
+ this.add
262
+ .text(400, 200, `Your Rank: #${result.rank}`, {
263
+ fontSize: '32px',
264
+ })
265
+ .setOrigin(0.5);
266
+
267
+ if (result.is_new_high_score) {
268
+ this.add
269
+ .text(400, 250, 'NEW HIGH SCORE!', {
270
+ fontSize: '24px',
271
+ color: '#ffff00',
272
+ })
273
+ .setOrigin(0.5);
274
+ }
275
+
276
+ // Display leaderboard
277
+ const leaderboard = await keeperboard.getLeaderboard(10);
278
+
279
+ leaderboard.entries.forEach((entry, index) => {
280
+ const isMe = entry.player_guid === playerGuid;
281
+ const color = isMe ? '#00ff00' : '#ffffff';
282
+
283
+ this.add
284
+ .text(
285
+ 400,
286
+ 350 + index * 30,
287
+ `#${entry.rank} ${entry.player_name}: ${entry.score}`,
288
+ { fontSize: '18px', color },
289
+ )
290
+ .setOrigin(0.5);
291
+ });
292
+ }
293
+ }
294
+ ```
295
+
296
+ ## Multiple Leaderboards
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
+ ```
326
+
327
+ ## Development
328
+
329
+ ```bash
330
+ # Install dependencies
331
+ npm install
332
+
333
+ # Type check
334
+ npm run typecheck
335
+
336
+ # Build
337
+ npm run build
338
+
339
+ # Clean
340
+ npm run clean
341
+ ```
342
+
343
+ ## License
344
+
345
+ MIT
@@ -0,0 +1,226 @@
1
+ /**
2
+ * Type definitions for KeeperBoard SDK.
3
+ * Matches the API response shapes from the KeeperBoard public API.
4
+ */
5
+ interface KeeperBoardConfig {
6
+ /** Base URL of the KeeperBoard API (e.g., "https://keeperboard.vercel.app") */
7
+ apiUrl: string;
8
+ /** API key from the KeeperBoard dashboard (e.g., "kb_dev_abc123...") */
9
+ apiKey: string;
10
+ }
11
+ interface ScoreSubmission {
12
+ /** Unique player identifier (UUID or custom string) */
13
+ player_guid: string;
14
+ /** Player display name */
15
+ player_name: string;
16
+ /** Score value */
17
+ score: number;
18
+ /** Optional metadata to attach to the score */
19
+ metadata?: Record<string, unknown>;
20
+ }
21
+ interface PlayerNameUpdate {
22
+ /** New display name for the player */
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
+ }
31
+ interface ScoreResponse {
32
+ /** Score ID in the database */
33
+ id: string;
34
+ /** Player's unique identifier */
35
+ player_guid: string;
36
+ /** Player's display name */
37
+ player_name: string;
38
+ /** Current score value */
39
+ score: number;
40
+ /** Player's rank on the leaderboard */
41
+ rank: number;
42
+ /** Whether this submission resulted in a new high score */
43
+ is_new_high_score: boolean;
44
+ }
45
+ interface LeaderboardEntry {
46
+ /** Position on the leaderboard (1-indexed) */
47
+ rank: number;
48
+ /** Player's unique identifier */
49
+ player_guid: string;
50
+ /** Player's display name */
51
+ player_name: string;
52
+ /** Score value */
53
+ score: number;
54
+ }
55
+ interface LeaderboardResponse {
56
+ /** Array of leaderboard entries */
57
+ entries: LeaderboardEntry[];
58
+ /** Total number of scores on this leaderboard */
59
+ total_count: number;
60
+ }
61
+ interface PlayerResponse {
62
+ /** Score ID in the database */
63
+ id: string;
64
+ /** Player's unique identifier */
65
+ player_guid: string;
66
+ /** Player's display name */
67
+ player_name: string;
68
+ /** Player's score */
69
+ score: number;
70
+ /** Player's rank on the leaderboard */
71
+ rank: number;
72
+ }
73
+ interface ClaimResponse {
74
+ /** Whether the score was successfully claimed */
75
+ claimed: boolean;
76
+ /** The claimed score value */
77
+ score: number;
78
+ /** Player's rank after claiming */
79
+ rank: number;
80
+ /** The player name that was matched */
81
+ player_name: string;
82
+ }
83
+ interface HealthResponse {
84
+ /** Service name */
85
+ service: string;
86
+ /** API version */
87
+ version: string;
88
+ /** Server timestamp */
89
+ timestamp: string;
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
+ declare class KeeperBoardError extends Error {
102
+ readonly code: string;
103
+ readonly statusCode: number;
104
+ constructor(message: string, code: string, statusCode: number);
105
+ }
106
+
107
+ /**
108
+ * KeeperBoard API client for TypeScript/JavaScript games.
109
+ * Works in any browser environment using the fetch API.
110
+ */
111
+
112
+ declare class KeeperBoardClient {
113
+ private readonly apiUrl;
114
+ private readonly apiKey;
115
+ constructor(config: KeeperBoardConfig);
116
+ /**
117
+ * Submit a score to the leaderboard.
118
+ * Only updates if the new score is higher than the existing one.
119
+ *
120
+ * @param playerGuid - Unique player identifier
121
+ * @param playerName - Player display name
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
126
+ */
127
+ submitScore(playerGuid: string, playerName: string, score: number, metadata?: Record<string, unknown>, leaderboardSlug?: string): Promise<ScoreResponse>;
128
+ /**
129
+ * Get the leaderboard entries with pagination.
130
+ *
131
+ * @param limit - Maximum number of entries to return (default: 10, max: 100)
132
+ * @param offset - Pagination offset (default: 0)
133
+ * @param leaderboardSlug - Optional leaderboard slug (uses default if not specified)
134
+ * @returns Leaderboard entries with total count
135
+ */
136
+ getLeaderboard(limit?: number, offset?: number, leaderboardSlug?: string): Promise<LeaderboardResponse>;
137
+ /**
138
+ * Get a specific player's score and rank.
139
+ *
140
+ * @param playerGuid - Player's unique identifier
141
+ * @param leaderboardSlug - Optional leaderboard slug (uses default if not specified)
142
+ * @returns Player's score and rank, or null if not found
143
+ */
144
+ getPlayer(playerGuid: string, leaderboardSlug?: string): Promise<PlayerResponse | null>;
145
+ /**
146
+ * Update a player's display name.
147
+ *
148
+ * @param playerGuid - Player's unique identifier
149
+ * @param newName - New display name
150
+ * @param leaderboardSlug - Optional leaderboard slug (uses default if not specified)
151
+ * @returns Updated player info
152
+ */
153
+ updatePlayerName(playerGuid: string, newName: string, leaderboardSlug?: string): Promise<PlayerResponse>;
154
+ /**
155
+ * Claim a migrated score by matching player name.
156
+ * Used when scores were imported (e.g., from CSV) without player GUIDs.
157
+ *
158
+ * @param playerGuid - Player GUID to assign to the claimed score
159
+ * @param playerName - Player name to match against migrated scores
160
+ * @param leaderboardSlug - Optional leaderboard slug (uses default if not specified)
161
+ * @returns Claim result with score and rank
162
+ */
163
+ claimScore(playerGuid: string, playerName: string, leaderboardSlug?: string): Promise<ClaimResponse>;
164
+ /**
165
+ * Check if the API is healthy.
166
+ * This endpoint does not require an API key.
167
+ *
168
+ * @returns Health status with version and timestamp
169
+ */
170
+ healthCheck(): Promise<HealthResponse>;
171
+ /**
172
+ * Internal request helper with auth and error handling.
173
+ */
174
+ private request;
175
+ }
176
+
177
+ /**
178
+ * Helper class for managing player identity in localStorage.
179
+ * Provides persistent player GUID and name storage across game sessions.
180
+ */
181
+ interface PlayerIdentityConfig {
182
+ /** Prefix for localStorage keys (default: "keeperboard_") */
183
+ keyPrefix?: string;
184
+ }
185
+ declare class PlayerIdentity {
186
+ private readonly keyPrefix;
187
+ private readonly guidKey;
188
+ private readonly nameKey;
189
+ constructor(config?: PlayerIdentityConfig);
190
+ /**
191
+ * Get the stored player GUID, or null if none exists.
192
+ */
193
+ getPlayerGuid(): string | null;
194
+ /**
195
+ * Set the player GUID in localStorage.
196
+ */
197
+ setPlayerGuid(guid: string): void;
198
+ /**
199
+ * Get the stored player GUID, creating one if it doesn't exist.
200
+ * Uses crypto.randomUUID() for generating new GUIDs.
201
+ */
202
+ getOrCreatePlayerGuid(): string;
203
+ /**
204
+ * Get the stored player name, or null if none exists.
205
+ */
206
+ getPlayerName(): string | null;
207
+ /**
208
+ * Set the player name in localStorage.
209
+ */
210
+ setPlayerName(name: string): void;
211
+ /**
212
+ * Clear all stored player identity data.
213
+ */
214
+ clear(): void;
215
+ /**
216
+ * Check if player identity is stored.
217
+ */
218
+ hasIdentity(): boolean;
219
+ /**
220
+ * Generate a UUID v4.
221
+ * Uses crypto.randomUUID() if available, otherwise falls back to a manual implementation.
222
+ */
223
+ private generateUUID;
224
+ }
225
+
226
+ export { type ApiErrorResponse, type ApiResponse, type ApiSuccessResponse, type ClaimRequest, type ClaimResponse, type HealthResponse, KeeperBoardClient, type KeeperBoardConfig, KeeperBoardError, type LeaderboardEntry, type LeaderboardResponse, PlayerIdentity, type PlayerIdentityConfig, type PlayerNameUpdate, type PlayerResponse, type ScoreResponse, type ScoreSubmission };
@@ -0,0 +1,226 @@
1
+ /**
2
+ * Type definitions for KeeperBoard SDK.
3
+ * Matches the API response shapes from the KeeperBoard public API.
4
+ */
5
+ interface KeeperBoardConfig {
6
+ /** Base URL of the KeeperBoard API (e.g., "https://keeperboard.vercel.app") */
7
+ apiUrl: string;
8
+ /** API key from the KeeperBoard dashboard (e.g., "kb_dev_abc123...") */
9
+ apiKey: string;
10
+ }
11
+ interface ScoreSubmission {
12
+ /** Unique player identifier (UUID or custom string) */
13
+ player_guid: string;
14
+ /** Player display name */
15
+ player_name: string;
16
+ /** Score value */
17
+ score: number;
18
+ /** Optional metadata to attach to the score */
19
+ metadata?: Record<string, unknown>;
20
+ }
21
+ interface PlayerNameUpdate {
22
+ /** New display name for the player */
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
+ }
31
+ interface ScoreResponse {
32
+ /** Score ID in the database */
33
+ id: string;
34
+ /** Player's unique identifier */
35
+ player_guid: string;
36
+ /** Player's display name */
37
+ player_name: string;
38
+ /** Current score value */
39
+ score: number;
40
+ /** Player's rank on the leaderboard */
41
+ rank: number;
42
+ /** Whether this submission resulted in a new high score */
43
+ is_new_high_score: boolean;
44
+ }
45
+ interface LeaderboardEntry {
46
+ /** Position on the leaderboard (1-indexed) */
47
+ rank: number;
48
+ /** Player's unique identifier */
49
+ player_guid: string;
50
+ /** Player's display name */
51
+ player_name: string;
52
+ /** Score value */
53
+ score: number;
54
+ }
55
+ interface LeaderboardResponse {
56
+ /** Array of leaderboard entries */
57
+ entries: LeaderboardEntry[];
58
+ /** Total number of scores on this leaderboard */
59
+ total_count: number;
60
+ }
61
+ interface PlayerResponse {
62
+ /** Score ID in the database */
63
+ id: string;
64
+ /** Player's unique identifier */
65
+ player_guid: string;
66
+ /** Player's display name */
67
+ player_name: string;
68
+ /** Player's score */
69
+ score: number;
70
+ /** Player's rank on the leaderboard */
71
+ rank: number;
72
+ }
73
+ interface ClaimResponse {
74
+ /** Whether the score was successfully claimed */
75
+ claimed: boolean;
76
+ /** The claimed score value */
77
+ score: number;
78
+ /** Player's rank after claiming */
79
+ rank: number;
80
+ /** The player name that was matched */
81
+ player_name: string;
82
+ }
83
+ interface HealthResponse {
84
+ /** Service name */
85
+ service: string;
86
+ /** API version */
87
+ version: string;
88
+ /** Server timestamp */
89
+ timestamp: string;
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
+ declare class KeeperBoardError extends Error {
102
+ readonly code: string;
103
+ readonly statusCode: number;
104
+ constructor(message: string, code: string, statusCode: number);
105
+ }
106
+
107
+ /**
108
+ * KeeperBoard API client for TypeScript/JavaScript games.
109
+ * Works in any browser environment using the fetch API.
110
+ */
111
+
112
+ declare class KeeperBoardClient {
113
+ private readonly apiUrl;
114
+ private readonly apiKey;
115
+ constructor(config: KeeperBoardConfig);
116
+ /**
117
+ * Submit a score to the leaderboard.
118
+ * Only updates if the new score is higher than the existing one.
119
+ *
120
+ * @param playerGuid - Unique player identifier
121
+ * @param playerName - Player display name
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
126
+ */
127
+ submitScore(playerGuid: string, playerName: string, score: number, metadata?: Record<string, unknown>, leaderboardSlug?: string): Promise<ScoreResponse>;
128
+ /**
129
+ * Get the leaderboard entries with pagination.
130
+ *
131
+ * @param limit - Maximum number of entries to return (default: 10, max: 100)
132
+ * @param offset - Pagination offset (default: 0)
133
+ * @param leaderboardSlug - Optional leaderboard slug (uses default if not specified)
134
+ * @returns Leaderboard entries with total count
135
+ */
136
+ getLeaderboard(limit?: number, offset?: number, leaderboardSlug?: string): Promise<LeaderboardResponse>;
137
+ /**
138
+ * Get a specific player's score and rank.
139
+ *
140
+ * @param playerGuid - Player's unique identifier
141
+ * @param leaderboardSlug - Optional leaderboard slug (uses default if not specified)
142
+ * @returns Player's score and rank, or null if not found
143
+ */
144
+ getPlayer(playerGuid: string, leaderboardSlug?: string): Promise<PlayerResponse | null>;
145
+ /**
146
+ * Update a player's display name.
147
+ *
148
+ * @param playerGuid - Player's unique identifier
149
+ * @param newName - New display name
150
+ * @param leaderboardSlug - Optional leaderboard slug (uses default if not specified)
151
+ * @returns Updated player info
152
+ */
153
+ updatePlayerName(playerGuid: string, newName: string, leaderboardSlug?: string): Promise<PlayerResponse>;
154
+ /**
155
+ * Claim a migrated score by matching player name.
156
+ * Used when scores were imported (e.g., from CSV) without player GUIDs.
157
+ *
158
+ * @param playerGuid - Player GUID to assign to the claimed score
159
+ * @param playerName - Player name to match against migrated scores
160
+ * @param leaderboardSlug - Optional leaderboard slug (uses default if not specified)
161
+ * @returns Claim result with score and rank
162
+ */
163
+ claimScore(playerGuid: string, playerName: string, leaderboardSlug?: string): Promise<ClaimResponse>;
164
+ /**
165
+ * Check if the API is healthy.
166
+ * This endpoint does not require an API key.
167
+ *
168
+ * @returns Health status with version and timestamp
169
+ */
170
+ healthCheck(): Promise<HealthResponse>;
171
+ /**
172
+ * Internal request helper with auth and error handling.
173
+ */
174
+ private request;
175
+ }
176
+
177
+ /**
178
+ * Helper class for managing player identity in localStorage.
179
+ * Provides persistent player GUID and name storage across game sessions.
180
+ */
181
+ interface PlayerIdentityConfig {
182
+ /** Prefix for localStorage keys (default: "keeperboard_") */
183
+ keyPrefix?: string;
184
+ }
185
+ declare class PlayerIdentity {
186
+ private readonly keyPrefix;
187
+ private readonly guidKey;
188
+ private readonly nameKey;
189
+ constructor(config?: PlayerIdentityConfig);
190
+ /**
191
+ * Get the stored player GUID, or null if none exists.
192
+ */
193
+ getPlayerGuid(): string | null;
194
+ /**
195
+ * Set the player GUID in localStorage.
196
+ */
197
+ setPlayerGuid(guid: string): void;
198
+ /**
199
+ * Get the stored player GUID, creating one if it doesn't exist.
200
+ * Uses crypto.randomUUID() for generating new GUIDs.
201
+ */
202
+ getOrCreatePlayerGuid(): string;
203
+ /**
204
+ * Get the stored player name, or null if none exists.
205
+ */
206
+ getPlayerName(): string | null;
207
+ /**
208
+ * Set the player name in localStorage.
209
+ */
210
+ setPlayerName(name: string): void;
211
+ /**
212
+ * Clear all stored player identity data.
213
+ */
214
+ clear(): void;
215
+ /**
216
+ * Check if player identity is stored.
217
+ */
218
+ hasIdentity(): boolean;
219
+ /**
220
+ * Generate a UUID v4.
221
+ * Uses crypto.randomUUID() if available, otherwise falls back to a manual implementation.
222
+ */
223
+ private generateUUID;
224
+ }
225
+
226
+ export { type ApiErrorResponse, type ApiResponse, type ApiSuccessResponse, type ClaimRequest, type ClaimResponse, type HealthResponse, KeeperBoardClient, type KeeperBoardConfig, KeeperBoardError, type LeaderboardEntry, type LeaderboardResponse, PlayerIdentity, type PlayerIdentityConfig, type PlayerNameUpdate, type PlayerResponse, type ScoreResponse, type ScoreSubmission };
package/dist/index.js ADDED
@@ -0,0 +1,296 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ KeeperBoardClient: () => KeeperBoardClient,
24
+ KeeperBoardError: () => KeeperBoardError,
25
+ PlayerIdentity: () => PlayerIdentity
26
+ });
27
+ module.exports = __toCommonJS(index_exports);
28
+
29
+ // src/types.ts
30
+ var KeeperBoardError = class extends Error {
31
+ constructor(message, code, statusCode) {
32
+ super(message);
33
+ this.code = code;
34
+ this.statusCode = statusCode;
35
+ this.name = "KeeperBoardError";
36
+ }
37
+ };
38
+
39
+ // src/KeeperBoardClient.ts
40
+ var KeeperBoardClient = class {
41
+ constructor(config) {
42
+ this.apiUrl = config.apiUrl.replace(/\/$/, "");
43
+ this.apiKey = config.apiKey;
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) {
57
+ const params = new URLSearchParams();
58
+ if (leaderboardSlug) {
59
+ params.set("leaderboard", leaderboardSlug);
60
+ }
61
+ const url = `${this.apiUrl}/api/v1/scores${params.toString() ? "?" + params.toString() : ""}`;
62
+ const body = {
63
+ player_guid: playerGuid,
64
+ player_name: playerName,
65
+ score,
66
+ ...metadata && { metadata }
67
+ };
68
+ const response = await this.request(url, {
69
+ method: "POST",
70
+ body: JSON.stringify(body)
71
+ });
72
+ return response;
73
+ }
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) {
83
+ const params = new URLSearchParams();
84
+ params.set("limit", String(Math.min(limit, 100)));
85
+ params.set("offset", String(offset));
86
+ if (leaderboardSlug) {
87
+ params.set("leaderboard", leaderboardSlug);
88
+ }
89
+ const url = `${this.apiUrl}/api/v1/leaderboard?${params.toString()}`;
90
+ return this.request(url, {
91
+ method: "GET"
92
+ });
93
+ }
94
+ /**
95
+ * Get a specific player's score and rank.
96
+ *
97
+ * @param playerGuid - Player's unique identifier
98
+ * @param leaderboardSlug - Optional leaderboard slug (uses default if not specified)
99
+ * @returns Player's score and rank, or null if not found
100
+ */
101
+ async getPlayer(playerGuid, leaderboardSlug) {
102
+ const params = new URLSearchParams();
103
+ if (leaderboardSlug) {
104
+ params.set("leaderboard", leaderboardSlug);
105
+ }
106
+ const url = `${this.apiUrl}/api/v1/player/${encodeURIComponent(playerGuid)}${params.toString() ? "?" + params.toString() : ""}`;
107
+ try {
108
+ return await this.request(url, {
109
+ method: "GET"
110
+ });
111
+ } catch (error) {
112
+ if (error instanceof KeeperBoardError && error.code === "NOT_FOUND") {
113
+ return null;
114
+ }
115
+ throw error;
116
+ }
117
+ }
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) {
127
+ const params = new URLSearchParams();
128
+ if (leaderboardSlug) {
129
+ params.set("leaderboard", leaderboardSlug);
130
+ }
131
+ const url = `${this.apiUrl}/api/v1/player/${encodeURIComponent(playerGuid)}${params.toString() ? "?" + params.toString() : ""}`;
132
+ return this.request(url, {
133
+ method: "PUT",
134
+ body: JSON.stringify({ player_name: newName })
135
+ });
136
+ }
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) {
147
+ const params = new URLSearchParams();
148
+ if (leaderboardSlug) {
149
+ params.set("leaderboard", leaderboardSlug);
150
+ }
151
+ const url = `${this.apiUrl}/api/v1/claim${params.toString() ? "?" + params.toString() : ""}`;
152
+ return this.request(url, {
153
+ method: "POST",
154
+ body: JSON.stringify({
155
+ player_guid: playerGuid,
156
+ player_name: playerName
157
+ })
158
+ });
159
+ }
160
+ /**
161
+ * Check if the API is healthy.
162
+ * This endpoint does not require an API key.
163
+ *
164
+ * @returns Health status with version and timestamp
165
+ */
166
+ async healthCheck() {
167
+ const url = `${this.apiUrl}/api/v1/health`;
168
+ const response = await fetch(url, {
169
+ method: "GET",
170
+ headers: {
171
+ Accept: "application/json"
172
+ }
173
+ });
174
+ const json = await response.json();
175
+ if (!json.success) {
176
+ throw new KeeperBoardError(json.error, json.code, response.status);
177
+ }
178
+ return json.data;
179
+ }
180
+ /**
181
+ * Internal request helper with auth and error handling.
182
+ */
183
+ async request(url, options) {
184
+ const headers = {
185
+ "Content-Type": "application/json",
186
+ Accept: "application/json",
187
+ "X-API-Key": this.apiKey
188
+ };
189
+ const response = await fetch(url, {
190
+ ...options,
191
+ headers: {
192
+ ...headers,
193
+ ...options.headers || {}
194
+ }
195
+ });
196
+ const json = await response.json();
197
+ if (!json.success) {
198
+ throw new KeeperBoardError(json.error, json.code, response.status);
199
+ }
200
+ return json.data;
201
+ }
202
+ };
203
+
204
+ // src/PlayerIdentity.ts
205
+ var DEFAULT_KEY_PREFIX = "keeperboard_";
206
+ var PlayerIdentity = class {
207
+ constructor(config = {}) {
208
+ this.keyPrefix = config.keyPrefix ?? DEFAULT_KEY_PREFIX;
209
+ this.guidKey = `${this.keyPrefix}player_guid`;
210
+ this.nameKey = `${this.keyPrefix}player_name`;
211
+ }
212
+ /**
213
+ * Get the stored player GUID, or null if none exists.
214
+ */
215
+ getPlayerGuid() {
216
+ if (typeof window === "undefined" || !window.localStorage) {
217
+ return null;
218
+ }
219
+ return localStorage.getItem(this.guidKey);
220
+ }
221
+ /**
222
+ * Set the player GUID in localStorage.
223
+ */
224
+ setPlayerGuid(guid) {
225
+ if (typeof window === "undefined" || !window.localStorage) {
226
+ return;
227
+ }
228
+ localStorage.setItem(this.guidKey, guid);
229
+ }
230
+ /**
231
+ * Get the stored player GUID, creating one if it doesn't exist.
232
+ * Uses crypto.randomUUID() for generating new GUIDs.
233
+ */
234
+ getOrCreatePlayerGuid() {
235
+ let guid = this.getPlayerGuid();
236
+ if (!guid) {
237
+ guid = this.generateUUID();
238
+ this.setPlayerGuid(guid);
239
+ }
240
+ return guid;
241
+ }
242
+ /**
243
+ * Get the stored player name, or null if none exists.
244
+ */
245
+ getPlayerName() {
246
+ if (typeof window === "undefined" || !window.localStorage) {
247
+ return null;
248
+ }
249
+ return localStorage.getItem(this.nameKey);
250
+ }
251
+ /**
252
+ * Set the player name in localStorage.
253
+ */
254
+ setPlayerName(name) {
255
+ if (typeof window === "undefined" || !window.localStorage) {
256
+ return;
257
+ }
258
+ localStorage.setItem(this.nameKey, name);
259
+ }
260
+ /**
261
+ * Clear all stored player identity data.
262
+ */
263
+ clear() {
264
+ if (typeof window === "undefined" || !window.localStorage) {
265
+ return;
266
+ }
267
+ localStorage.removeItem(this.guidKey);
268
+ localStorage.removeItem(this.nameKey);
269
+ }
270
+ /**
271
+ * Check if player identity is stored.
272
+ */
273
+ hasIdentity() {
274
+ return this.getPlayerGuid() !== null;
275
+ }
276
+ /**
277
+ * Generate a UUID v4.
278
+ * Uses crypto.randomUUID() if available, otherwise falls back to a manual implementation.
279
+ */
280
+ generateUUID() {
281
+ if (typeof crypto !== "undefined" && crypto.randomUUID) {
282
+ return crypto.randomUUID();
283
+ }
284
+ return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
285
+ const r = Math.random() * 16 | 0;
286
+ const v = c === "x" ? r : r & 3 | 8;
287
+ return v.toString(16);
288
+ });
289
+ }
290
+ };
291
+ // Annotate the CommonJS export names for ESM import in node:
292
+ 0 && (module.exports = {
293
+ KeeperBoardClient,
294
+ KeeperBoardError,
295
+ PlayerIdentity
296
+ });
package/dist/index.mjs ADDED
@@ -0,0 +1,267 @@
1
+ // src/types.ts
2
+ var KeeperBoardError = class extends Error {
3
+ constructor(message, code, statusCode) {
4
+ super(message);
5
+ this.code = code;
6
+ this.statusCode = statusCode;
7
+ this.name = "KeeperBoardError";
8
+ }
9
+ };
10
+
11
+ // src/KeeperBoardClient.ts
12
+ var KeeperBoardClient = class {
13
+ constructor(config) {
14
+ this.apiUrl = config.apiUrl.replace(/\/$/, "");
15
+ this.apiKey = config.apiKey;
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) {
29
+ const params = new URLSearchParams();
30
+ if (leaderboardSlug) {
31
+ params.set("leaderboard", leaderboardSlug);
32
+ }
33
+ const url = `${this.apiUrl}/api/v1/scores${params.toString() ? "?" + params.toString() : ""}`;
34
+ const body = {
35
+ player_guid: playerGuid,
36
+ player_name: playerName,
37
+ score,
38
+ ...metadata && { metadata }
39
+ };
40
+ const response = await this.request(url, {
41
+ method: "POST",
42
+ body: JSON.stringify(body)
43
+ });
44
+ return response;
45
+ }
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) {
55
+ const params = new URLSearchParams();
56
+ params.set("limit", String(Math.min(limit, 100)));
57
+ params.set("offset", String(offset));
58
+ if (leaderboardSlug) {
59
+ params.set("leaderboard", leaderboardSlug);
60
+ }
61
+ const url = `${this.apiUrl}/api/v1/leaderboard?${params.toString()}`;
62
+ return this.request(url, {
63
+ method: "GET"
64
+ });
65
+ }
66
+ /**
67
+ * Get a specific player's score and rank.
68
+ *
69
+ * @param playerGuid - Player's unique identifier
70
+ * @param leaderboardSlug - Optional leaderboard slug (uses default if not specified)
71
+ * @returns Player's score and rank, or null if not found
72
+ */
73
+ async getPlayer(playerGuid, leaderboardSlug) {
74
+ const params = new URLSearchParams();
75
+ if (leaderboardSlug) {
76
+ params.set("leaderboard", leaderboardSlug);
77
+ }
78
+ const url = `${this.apiUrl}/api/v1/player/${encodeURIComponent(playerGuid)}${params.toString() ? "?" + params.toString() : ""}`;
79
+ try {
80
+ return await this.request(url, {
81
+ method: "GET"
82
+ });
83
+ } catch (error) {
84
+ if (error instanceof KeeperBoardError && error.code === "NOT_FOUND") {
85
+ return null;
86
+ }
87
+ throw error;
88
+ }
89
+ }
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) {
99
+ const params = new URLSearchParams();
100
+ if (leaderboardSlug) {
101
+ params.set("leaderboard", leaderboardSlug);
102
+ }
103
+ const url = `${this.apiUrl}/api/v1/player/${encodeURIComponent(playerGuid)}${params.toString() ? "?" + params.toString() : ""}`;
104
+ return this.request(url, {
105
+ method: "PUT",
106
+ body: JSON.stringify({ player_name: newName })
107
+ });
108
+ }
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) {
119
+ const params = new URLSearchParams();
120
+ if (leaderboardSlug) {
121
+ params.set("leaderboard", leaderboardSlug);
122
+ }
123
+ const url = `${this.apiUrl}/api/v1/claim${params.toString() ? "?" + params.toString() : ""}`;
124
+ return this.request(url, {
125
+ method: "POST",
126
+ body: JSON.stringify({
127
+ player_guid: playerGuid,
128
+ player_name: playerName
129
+ })
130
+ });
131
+ }
132
+ /**
133
+ * Check if the API is healthy.
134
+ * This endpoint does not require an API key.
135
+ *
136
+ * @returns Health status with version and timestamp
137
+ */
138
+ async healthCheck() {
139
+ const url = `${this.apiUrl}/api/v1/health`;
140
+ const response = await fetch(url, {
141
+ method: "GET",
142
+ headers: {
143
+ Accept: "application/json"
144
+ }
145
+ });
146
+ const json = await response.json();
147
+ if (!json.success) {
148
+ throw new KeeperBoardError(json.error, json.code, response.status);
149
+ }
150
+ return json.data;
151
+ }
152
+ /**
153
+ * Internal request helper with auth and error handling.
154
+ */
155
+ async request(url, options) {
156
+ const headers = {
157
+ "Content-Type": "application/json",
158
+ Accept: "application/json",
159
+ "X-API-Key": this.apiKey
160
+ };
161
+ const response = await fetch(url, {
162
+ ...options,
163
+ headers: {
164
+ ...headers,
165
+ ...options.headers || {}
166
+ }
167
+ });
168
+ const json = await response.json();
169
+ if (!json.success) {
170
+ throw new KeeperBoardError(json.error, json.code, response.status);
171
+ }
172
+ return json.data;
173
+ }
174
+ };
175
+
176
+ // src/PlayerIdentity.ts
177
+ var DEFAULT_KEY_PREFIX = "keeperboard_";
178
+ var PlayerIdentity = class {
179
+ constructor(config = {}) {
180
+ this.keyPrefix = config.keyPrefix ?? DEFAULT_KEY_PREFIX;
181
+ this.guidKey = `${this.keyPrefix}player_guid`;
182
+ this.nameKey = `${this.keyPrefix}player_name`;
183
+ }
184
+ /**
185
+ * Get the stored player GUID, or null if none exists.
186
+ */
187
+ getPlayerGuid() {
188
+ if (typeof window === "undefined" || !window.localStorage) {
189
+ return null;
190
+ }
191
+ return localStorage.getItem(this.guidKey);
192
+ }
193
+ /**
194
+ * Set the player GUID in localStorage.
195
+ */
196
+ setPlayerGuid(guid) {
197
+ if (typeof window === "undefined" || !window.localStorage) {
198
+ return;
199
+ }
200
+ localStorage.setItem(this.guidKey, guid);
201
+ }
202
+ /**
203
+ * Get the stored player GUID, creating one if it doesn't exist.
204
+ * Uses crypto.randomUUID() for generating new GUIDs.
205
+ */
206
+ getOrCreatePlayerGuid() {
207
+ let guid = this.getPlayerGuid();
208
+ if (!guid) {
209
+ guid = this.generateUUID();
210
+ this.setPlayerGuid(guid);
211
+ }
212
+ return guid;
213
+ }
214
+ /**
215
+ * Get the stored player name, or null if none exists.
216
+ */
217
+ getPlayerName() {
218
+ if (typeof window === "undefined" || !window.localStorage) {
219
+ return null;
220
+ }
221
+ return localStorage.getItem(this.nameKey);
222
+ }
223
+ /**
224
+ * Set the player name in localStorage.
225
+ */
226
+ setPlayerName(name) {
227
+ if (typeof window === "undefined" || !window.localStorage) {
228
+ return;
229
+ }
230
+ localStorage.setItem(this.nameKey, name);
231
+ }
232
+ /**
233
+ * Clear all stored player identity data.
234
+ */
235
+ clear() {
236
+ if (typeof window === "undefined" || !window.localStorage) {
237
+ return;
238
+ }
239
+ localStorage.removeItem(this.guidKey);
240
+ localStorage.removeItem(this.nameKey);
241
+ }
242
+ /**
243
+ * Check if player identity is stored.
244
+ */
245
+ hasIdentity() {
246
+ return this.getPlayerGuid() !== null;
247
+ }
248
+ /**
249
+ * Generate a UUID v4.
250
+ * Uses crypto.randomUUID() if available, otherwise falls back to a manual implementation.
251
+ */
252
+ generateUUID() {
253
+ if (typeof crypto !== "undefined" && crypto.randomUUID) {
254
+ return crypto.randomUUID();
255
+ }
256
+ return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
257
+ const r = Math.random() * 16 | 0;
258
+ const v = c === "x" ? r : r & 3 | 8;
259
+ return v.toString(16);
260
+ });
261
+ }
262
+ };
263
+ export {
264
+ KeeperBoardClient,
265
+ KeeperBoardError,
266
+ PlayerIdentity
267
+ };
package/package.json ADDED
@@ -0,0 +1,51 @@
1
+ {
2
+ "name": "keeperboard-sdk",
3
+ "version": "1.0.1",
4
+ "description": "TypeScript client SDK for KeeperBoard leaderboard-as-a-service",
5
+ "main": "dist/index.js",
6
+ "module": "dist/index.mjs",
7
+ "types": "dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "import": "./dist/index.mjs",
11
+ "require": "./dist/index.js",
12
+ "types": "./dist/index.d.ts"
13
+ }
14
+ },
15
+ "files": [
16
+ "dist"
17
+ ],
18
+ "scripts": {
19
+ "build": "tsup src/index.ts --format cjs,esm --dts",
20
+ "typecheck": "tsc --noEmit",
21
+ "clean": "rm -rf dist",
22
+ "prepublishOnly": "npm run build"
23
+ },
24
+ "keywords": [
25
+ "leaderboard",
26
+ "phaser",
27
+ "game",
28
+ "scores",
29
+ "typescript",
30
+ "sdk",
31
+ "keeperboard"
32
+ ],
33
+ "author": "Claude Roy",
34
+ "license": "MIT",
35
+ "repository": {
36
+ "type": "git",
37
+ "url": "git@github.com:clauderoy790/keeperboard.git",
38
+ "directory": "sdk"
39
+ },
40
+ "homepage": "https://github.com/clauderoy790/keeperboard#readme",
41
+ "bugs": {
42
+ "url": "https://github.com/clauderoy790/keeperboard/issues"
43
+ },
44
+ "devDependencies": {
45
+ "tsup": "^8.0.0",
46
+ "typescript": "^5.0.0"
47
+ },
48
+ "engines": {
49
+ "node": ">=18"
50
+ }
51
+ }