keeperboard 1.0.3 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +197 -296
- package/dist/index.d.mts +337 -111
- package/dist/index.d.ts +337 -111
- package/dist/index.js +453 -49
- package/dist/index.mjs +448 -48
- package/package.json +3 -3
package/dist/index.d.mts
CHANGED
|
@@ -1,91 +1,202 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Type definitions for KeeperBoard SDK.
|
|
3
|
-
*
|
|
3
|
+
*
|
|
4
|
+
* Public types use camelCase. Internal types (prefixed with Api*) match the
|
|
5
|
+
* snake_case shapes returned by the KeeperBoard REST API and are used only
|
|
6
|
+
* for deserialization inside the client.
|
|
4
7
|
*/
|
|
5
8
|
interface KeeperBoardConfig {
|
|
6
|
-
/** Base URL of the KeeperBoard API (e.g., "https://keeperboard.vercel.app") */
|
|
7
|
-
apiUrl: string;
|
|
8
9
|
/** API key from the KeeperBoard dashboard (e.g., "kb_dev_abc123...") */
|
|
9
10
|
apiKey: string;
|
|
11
|
+
/** Default leaderboard name — used when no leaderboard is specified in method calls */
|
|
12
|
+
defaultLeaderboard?: string;
|
|
13
|
+
/** @internal Base URL override for testing. Do not use in production. */
|
|
14
|
+
apiUrl?: string;
|
|
10
15
|
}
|
|
16
|
+
interface SubmitScoreOptions {
|
|
17
|
+
playerGuid: string;
|
|
18
|
+
playerName: string;
|
|
19
|
+
score: number;
|
|
20
|
+
/** Leaderboard name. Falls back to `defaultLeaderboard` from config. */
|
|
21
|
+
leaderboard?: string;
|
|
22
|
+
metadata?: Record<string, unknown>;
|
|
23
|
+
}
|
|
24
|
+
interface GetLeaderboardOptions {
|
|
25
|
+
/** Leaderboard name. Falls back to `defaultLeaderboard` from config. */
|
|
26
|
+
leaderboard?: string;
|
|
27
|
+
/** Max entries to return (1–100, default 10). */
|
|
28
|
+
limit?: number;
|
|
29
|
+
/** Offset for pagination (default 0). */
|
|
30
|
+
offset?: number;
|
|
31
|
+
/** Fetch a specific version of a time-based leaderboard. */
|
|
32
|
+
version?: number;
|
|
33
|
+
}
|
|
34
|
+
interface GetPlayerRankOptions {
|
|
35
|
+
playerGuid: string;
|
|
36
|
+
/** Leaderboard name. Falls back to `defaultLeaderboard` from config. */
|
|
37
|
+
leaderboard?: string;
|
|
38
|
+
}
|
|
39
|
+
interface UpdatePlayerNameOptions {
|
|
40
|
+
playerGuid: string;
|
|
41
|
+
newName: string;
|
|
42
|
+
/** Leaderboard name. Falls back to `defaultLeaderboard` from config. */
|
|
43
|
+
leaderboard?: string;
|
|
44
|
+
}
|
|
45
|
+
interface ClaimScoreOptions {
|
|
46
|
+
playerGuid: string;
|
|
47
|
+
playerName: string;
|
|
48
|
+
/** Leaderboard name. Falls back to `defaultLeaderboard` from config. */
|
|
49
|
+
leaderboard?: string;
|
|
50
|
+
}
|
|
51
|
+
interface ScoreResult {
|
|
52
|
+
id: string;
|
|
53
|
+
playerGuid: string;
|
|
54
|
+
playerName: string;
|
|
55
|
+
score: number;
|
|
56
|
+
rank: number;
|
|
57
|
+
isNewHighScore: boolean;
|
|
58
|
+
}
|
|
59
|
+
interface LeaderboardEntry {
|
|
60
|
+
rank: number;
|
|
61
|
+
playerGuid: string;
|
|
62
|
+
playerName: string;
|
|
63
|
+
score: number;
|
|
64
|
+
}
|
|
65
|
+
/** Reset schedule options for leaderboards */
|
|
66
|
+
type ResetSchedule = 'none' | 'daily' | 'weekly' | 'monthly';
|
|
67
|
+
interface LeaderboardResult {
|
|
68
|
+
entries: LeaderboardEntry[];
|
|
69
|
+
totalCount: number;
|
|
70
|
+
resetSchedule: ResetSchedule;
|
|
71
|
+
/** Current version number — only present when resetSchedule is not 'none'. */
|
|
72
|
+
version?: number;
|
|
73
|
+
/** Oldest available version number — only present when resetSchedule is not 'none'. */
|
|
74
|
+
oldestVersion?: number;
|
|
75
|
+
/** ISO timestamp of when the next reset occurs — only present when resetSchedule is not 'none'. */
|
|
76
|
+
nextReset?: string;
|
|
77
|
+
}
|
|
78
|
+
interface PlayerResult {
|
|
79
|
+
id: string;
|
|
80
|
+
playerGuid: string;
|
|
81
|
+
playerName: string;
|
|
82
|
+
score: number;
|
|
83
|
+
rank: number;
|
|
84
|
+
}
|
|
85
|
+
interface ClaimResult {
|
|
86
|
+
claimed: boolean;
|
|
87
|
+
score: number;
|
|
88
|
+
rank: number;
|
|
89
|
+
playerName: string;
|
|
90
|
+
}
|
|
91
|
+
interface HealthResult {
|
|
92
|
+
service: string;
|
|
93
|
+
version: string;
|
|
94
|
+
timestamp: string;
|
|
95
|
+
}
|
|
96
|
+
interface SessionConfig {
|
|
97
|
+
/** API key from the KeeperBoard dashboard */
|
|
98
|
+
apiKey: string;
|
|
99
|
+
/** Leaderboard name (required — the session is bound to one board) */
|
|
100
|
+
leaderboard: string;
|
|
101
|
+
/** PlayerIdentity config for localStorage key prefix */
|
|
102
|
+
identity?: {
|
|
103
|
+
keyPrefix?: string;
|
|
104
|
+
};
|
|
105
|
+
/** Default player name when none has been set (default: "ANON") */
|
|
106
|
+
defaultPlayerName?: string;
|
|
107
|
+
/** TTL cache configuration for getSnapshot() */
|
|
108
|
+
cache?: {
|
|
109
|
+
ttlMs: number;
|
|
110
|
+
};
|
|
111
|
+
/** Retry queue configuration for failed score submissions */
|
|
112
|
+
retry?: {
|
|
113
|
+
maxAgeMs?: number;
|
|
114
|
+
};
|
|
115
|
+
/** @internal Base URL override for testing. */
|
|
116
|
+
apiUrl?: string;
|
|
117
|
+
}
|
|
118
|
+
type SessionScoreResult = {
|
|
119
|
+
success: true;
|
|
120
|
+
rank: number;
|
|
121
|
+
isNewHighScore: boolean;
|
|
122
|
+
} | {
|
|
123
|
+
success: false;
|
|
124
|
+
error: string;
|
|
125
|
+
};
|
|
126
|
+
interface SnapshotEntry {
|
|
127
|
+
rank: number;
|
|
128
|
+
playerGuid: string;
|
|
129
|
+
playerName: string;
|
|
130
|
+
score: number;
|
|
131
|
+
isCurrentPlayer: boolean;
|
|
132
|
+
}
|
|
133
|
+
interface SnapshotResult {
|
|
134
|
+
entries: SnapshotEntry[];
|
|
135
|
+
totalCount: number;
|
|
136
|
+
/** Player's own rank info — included only when the player is outside the top N. */
|
|
137
|
+
playerRank: PlayerResult | null;
|
|
138
|
+
}
|
|
139
|
+
interface NameValidationOptions {
|
|
140
|
+
/** Minimum length after sanitization (default 2). */
|
|
141
|
+
minLength?: number;
|
|
142
|
+
/** Maximum length — input is truncated to this (default 12). */
|
|
143
|
+
maxLength?: number;
|
|
144
|
+
/** Convert to uppercase (default true). */
|
|
145
|
+
uppercase?: boolean;
|
|
146
|
+
/** Regex of allowed characters applied after case conversion (default /[^A-Z0-9_]/g removes non-matching). */
|
|
147
|
+
allowedPattern?: RegExp;
|
|
148
|
+
}
|
|
149
|
+
/** @internal */
|
|
11
150
|
interface ScoreSubmission {
|
|
12
|
-
/** Unique player identifier (UUID or custom string) */
|
|
13
151
|
player_guid: string;
|
|
14
|
-
/** Player display name */
|
|
15
152
|
player_name: string;
|
|
16
|
-
/** Score value */
|
|
17
153
|
score: number;
|
|
18
|
-
/** Optional metadata to attach to the score */
|
|
19
154
|
metadata?: Record<string, unknown>;
|
|
20
155
|
}
|
|
21
|
-
/**
|
|
22
|
-
|
|
23
|
-
interface ScoreResponse {
|
|
24
|
-
/** Score ID in the database */
|
|
156
|
+
/** @internal */
|
|
157
|
+
interface ApiScoreResponse {
|
|
25
158
|
id: string;
|
|
26
|
-
/** Player's unique identifier */
|
|
27
159
|
player_guid: string;
|
|
28
|
-
/** Player's display name */
|
|
29
160
|
player_name: string;
|
|
30
|
-
/** Current score value */
|
|
31
161
|
score: number;
|
|
32
|
-
/** Player's rank on the leaderboard */
|
|
33
162
|
rank: number;
|
|
34
|
-
/** Whether this submission resulted in a new high score */
|
|
35
163
|
is_new_high_score: boolean;
|
|
36
164
|
}
|
|
37
|
-
|
|
38
|
-
|
|
165
|
+
/** @internal */
|
|
166
|
+
interface ApiLeaderboardEntry {
|
|
39
167
|
rank: number;
|
|
40
|
-
/** Player's unique identifier */
|
|
41
168
|
player_guid: string;
|
|
42
|
-
/** Player's display name */
|
|
43
169
|
player_name: string;
|
|
44
|
-
/** Score value */
|
|
45
170
|
score: number;
|
|
46
171
|
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
entries:
|
|
50
|
-
/** Total number of scores in this version/period */
|
|
172
|
+
/** @internal */
|
|
173
|
+
interface ApiLeaderboardResponse {
|
|
174
|
+
entries: ApiLeaderboardEntry[];
|
|
51
175
|
total_count: number;
|
|
52
|
-
/** The reset schedule of this leaderboard */
|
|
53
176
|
reset_schedule: ResetSchedule;
|
|
54
|
-
/** Current version number — only present when reset_schedule is not 'none' */
|
|
55
177
|
version?: number;
|
|
56
|
-
/** Oldest available version number — only present when reset_schedule is not 'none' */
|
|
57
178
|
oldest_version?: number;
|
|
58
|
-
/** ISO timestamp of when the next reset occurs — only present when reset_schedule is not 'none' */
|
|
59
179
|
next_reset?: string;
|
|
60
180
|
}
|
|
61
|
-
|
|
62
|
-
|
|
181
|
+
/** @internal */
|
|
182
|
+
interface ApiPlayerResponse {
|
|
63
183
|
id: string;
|
|
64
|
-
/** Player's unique identifier */
|
|
65
184
|
player_guid: string;
|
|
66
|
-
/** Player's display name */
|
|
67
185
|
player_name: string;
|
|
68
|
-
/** Player's score */
|
|
69
186
|
score: number;
|
|
70
|
-
/** Player's rank on the leaderboard */
|
|
71
187
|
rank: number;
|
|
72
188
|
}
|
|
73
|
-
|
|
74
|
-
|
|
189
|
+
/** @internal */
|
|
190
|
+
interface ApiClaimResponse {
|
|
75
191
|
claimed: boolean;
|
|
76
|
-
/** The claimed score value */
|
|
77
192
|
score: number;
|
|
78
|
-
/** Player's rank after claiming */
|
|
79
193
|
rank: number;
|
|
80
|
-
/** The player name that was matched */
|
|
81
194
|
player_name: string;
|
|
82
195
|
}
|
|
83
|
-
|
|
84
|
-
|
|
196
|
+
/** @internal */
|
|
197
|
+
interface ApiHealthResponse {
|
|
85
198
|
service: string;
|
|
86
|
-
/** API version */
|
|
87
199
|
version: string;
|
|
88
|
-
/** Server timestamp */
|
|
89
200
|
timestamp: string;
|
|
90
201
|
}
|
|
91
202
|
declare class KeeperBoardError extends Error {
|
|
@@ -93,114 +204,155 @@ declare class KeeperBoardError extends Error {
|
|
|
93
204
|
readonly statusCode: number;
|
|
94
205
|
constructor(message: string, code: string, statusCode: number);
|
|
95
206
|
}
|
|
207
|
+
/** @deprecated Use `ApiScoreResponse` (internal) or `ScoreResult` (public). */
|
|
208
|
+
type ScoreResponse = ApiScoreResponse;
|
|
209
|
+
/** @deprecated Use `ApiLeaderboardResponse` (internal) or `LeaderboardResult` (public). */
|
|
210
|
+
type LeaderboardResponse = ApiLeaderboardResponse;
|
|
211
|
+
/** @deprecated Use `ApiPlayerResponse` (internal) or `PlayerResult` (public). */
|
|
212
|
+
type PlayerResponse = ApiPlayerResponse;
|
|
213
|
+
/** @deprecated Use `ApiClaimResponse` (internal) or `ClaimResult` (public). */
|
|
214
|
+
type ClaimResponse = ApiClaimResponse;
|
|
215
|
+
/** @deprecated Use `ApiHealthResponse` (internal) or `HealthResult` (public). */
|
|
216
|
+
type HealthResponse = ApiHealthResponse;
|
|
96
217
|
|
|
97
218
|
/**
|
|
98
219
|
* KeeperBoard API client for TypeScript/JavaScript games.
|
|
99
220
|
* Works in any browser environment using the fetch API.
|
|
221
|
+
*
|
|
222
|
+
* All public methods accept options objects and return camelCase results.
|
|
223
|
+
* A `defaultLeaderboard` can be set in the config to avoid passing it every call.
|
|
100
224
|
*/
|
|
101
225
|
|
|
102
226
|
declare class KeeperBoardClient {
|
|
227
|
+
private static readonly DEFAULT_API_URL;
|
|
103
228
|
private readonly apiUrl;
|
|
104
229
|
private readonly apiKey;
|
|
230
|
+
private readonly defaultLeaderboard?;
|
|
105
231
|
constructor(config: KeeperBoardConfig);
|
|
106
232
|
/**
|
|
107
|
-
* Submit a score
|
|
108
|
-
* Only updates if the new score is higher than the existing one.
|
|
109
|
-
*/
|
|
110
|
-
submitScore(playerGuid: string, playerName: string, score: number): Promise<ScoreResponse>;
|
|
111
|
-
/**
|
|
112
|
-
* Submit a score to a specific leaderboard.
|
|
113
|
-
* Only updates if the new score is higher than the existing one.
|
|
114
|
-
*/
|
|
115
|
-
submitScore(playerGuid: string, playerName: string, score: number, leaderboard: string): Promise<ScoreResponse>;
|
|
116
|
-
/**
|
|
117
|
-
* Submit a score with metadata.
|
|
118
|
-
* Only updates if the new score is higher than the existing one.
|
|
119
|
-
*/
|
|
120
|
-
submitScore(playerGuid: string, playerName: string, score: number, leaderboard: string, metadata: Record<string, unknown>): Promise<ScoreResponse>;
|
|
121
|
-
/**
|
|
122
|
-
* Get the default leaderboard (top 10 entries).
|
|
233
|
+
* Submit a score. Only updates if the new score is higher than the existing one.
|
|
123
234
|
*
|
|
124
235
|
* @example
|
|
125
|
-
* const
|
|
236
|
+
* const result = await client.submitScore({
|
|
237
|
+
* playerGuid: 'abc-123',
|
|
238
|
+
* playerName: 'ACE',
|
|
239
|
+
* score: 1500,
|
|
240
|
+
* });
|
|
241
|
+
* console.log(result.rank, result.isNewHighScore);
|
|
126
242
|
*/
|
|
127
|
-
|
|
243
|
+
submitScore(options: SubmitScoreOptions): Promise<ScoreResult>;
|
|
128
244
|
/**
|
|
129
|
-
* Get a
|
|
245
|
+
* Get a leaderboard. Supports pagination and version-based lookups for
|
|
246
|
+
* time-based boards.
|
|
130
247
|
*
|
|
131
248
|
* @example
|
|
132
|
-
*
|
|
133
|
-
|
|
134
|
-
getLeaderboard(name: string): Promise<LeaderboardResponse>;
|
|
135
|
-
/**
|
|
136
|
-
* Get a leaderboard with custom limit.
|
|
249
|
+
* // Top 10 on default board
|
|
250
|
+
* const lb = await client.getLeaderboard();
|
|
137
251
|
*
|
|
138
|
-
*
|
|
139
|
-
* const lb = await client.getLeaderboard('Weekly
|
|
252
|
+
* // Top 25 on a specific board
|
|
253
|
+
* const lb = await client.getLeaderboard({ leaderboard: 'Weekly', limit: 25 });
|
|
254
|
+
*
|
|
255
|
+
* // Historical version
|
|
256
|
+
* const lb = await client.getLeaderboard({ leaderboard: 'Weekly', version: 3 });
|
|
140
257
|
*/
|
|
141
|
-
getLeaderboard(
|
|
258
|
+
getLeaderboard(options?: GetLeaderboardOptions): Promise<LeaderboardResult>;
|
|
142
259
|
/**
|
|
143
|
-
* Get a
|
|
260
|
+
* Get a player's rank and score. Returns `null` if the player has no score.
|
|
144
261
|
*
|
|
145
262
|
* @example
|
|
146
|
-
* const
|
|
263
|
+
* const player = await client.getPlayerRank({ playerGuid: 'abc-123' });
|
|
264
|
+
* if (player) console.log(`Rank #${player.rank}`);
|
|
147
265
|
*/
|
|
148
|
-
|
|
266
|
+
getPlayerRank(options: GetPlayerRankOptions): Promise<PlayerResult | null>;
|
|
149
267
|
/**
|
|
150
|
-
*
|
|
268
|
+
* Update a player's display name.
|
|
151
269
|
*
|
|
152
270
|
* @example
|
|
153
|
-
*
|
|
154
|
-
*
|
|
271
|
+
* const player = await client.updatePlayerName({
|
|
272
|
+
* playerGuid: 'abc-123',
|
|
273
|
+
* newName: 'MAVERICK',
|
|
274
|
+
* });
|
|
155
275
|
*/
|
|
156
|
-
|
|
276
|
+
updatePlayerName(options: UpdatePlayerNameOptions): Promise<PlayerResult>;
|
|
157
277
|
/**
|
|
158
|
-
*
|
|
278
|
+
* Claim a migrated score by matching player name.
|
|
279
|
+
* Used when scores were imported without player GUIDs.
|
|
159
280
|
*/
|
|
160
|
-
|
|
281
|
+
claimScore(options: ClaimScoreOptions): Promise<ClaimResult>;
|
|
161
282
|
/**
|
|
162
|
-
*
|
|
283
|
+
* Check if the API is healthy. Does not require an API key.
|
|
163
284
|
*/
|
|
164
|
-
|
|
285
|
+
healthCheck(): Promise<HealthResult>;
|
|
286
|
+
private mapScoreResponse;
|
|
287
|
+
private mapLeaderboardResponse;
|
|
288
|
+
private mapPlayerResponse;
|
|
289
|
+
private mapClaimResponse;
|
|
290
|
+
private request;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* High-level KeeperBoard API for browser games.
|
|
295
|
+
* Wraps KeeperBoardClient with automatic identity management, caching,
|
|
296
|
+
* retry queue, and name validation.
|
|
297
|
+
*
|
|
298
|
+
* Recommended for most consumers. For server-side or advanced use,
|
|
299
|
+
* use KeeperBoardClient directly.
|
|
300
|
+
*/
|
|
301
|
+
|
|
302
|
+
declare class KeeperBoardSession {
|
|
303
|
+
private readonly client;
|
|
304
|
+
private readonly identity;
|
|
305
|
+
private readonly leaderboard;
|
|
306
|
+
private readonly defaultPlayerName;
|
|
307
|
+
private readonly cache;
|
|
308
|
+
private readonly retryQueue;
|
|
309
|
+
private isSubmitting;
|
|
310
|
+
constructor(config: SessionConfig);
|
|
311
|
+
/** Get or create a persistent player GUID. */
|
|
312
|
+
getPlayerGuid(): string;
|
|
313
|
+
/** Get the stored player name, falling back to defaultPlayerName. */
|
|
314
|
+
getPlayerName(): string;
|
|
315
|
+
/** Store a player name locally. Does NOT update the server — call updatePlayerName() for that. */
|
|
316
|
+
setPlayerName(name: string): void;
|
|
317
|
+
/** Validate a name using configurable rules. Returns sanitized string or null. */
|
|
318
|
+
validateName(input: string, options?: NameValidationOptions): string | null;
|
|
165
319
|
/**
|
|
166
|
-
*
|
|
167
|
-
* Returns
|
|
320
|
+
* Submit a score. Identity and leaderboard are auto-injected.
|
|
321
|
+
* Returns a discriminated union: `{ success: true, rank, isNewHighScore }` or `{ success: false, error }`.
|
|
168
322
|
*
|
|
169
|
-
*
|
|
170
|
-
*
|
|
171
|
-
* if (player) {
|
|
172
|
-
* console.log(`You are ranked #${player.rank}`);
|
|
173
|
-
* }
|
|
174
|
-
*/
|
|
175
|
-
getPlayerRank(playerGuid: string): Promise<PlayerResponse | null>;
|
|
176
|
-
/**
|
|
177
|
-
* Get a player's rank and score on a specific leaderboard.
|
|
178
|
-
* Returns null if the player has no score.
|
|
323
|
+
* If retry is enabled, failed submissions are saved to localStorage for later retry.
|
|
324
|
+
* Prevents concurrent double-submissions.
|
|
179
325
|
*/
|
|
180
|
-
|
|
326
|
+
submitScore(score: number, metadata?: Record<string, unknown>): Promise<SessionScoreResult>;
|
|
181
327
|
/**
|
|
182
|
-
*
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
* Update a player's display name on a specific leaderboard.
|
|
328
|
+
* Get a combined snapshot: leaderboard entries (with `isCurrentPlayer` flag)
|
|
329
|
+
* plus the current player's rank if they're outside the top N.
|
|
330
|
+
*
|
|
331
|
+
* Uses cache if enabled and fresh.
|
|
187
332
|
*/
|
|
188
|
-
|
|
333
|
+
getSnapshot(options?: {
|
|
334
|
+
limit?: number;
|
|
335
|
+
}): Promise<SnapshotResult>;
|
|
189
336
|
/**
|
|
190
|
-
*
|
|
191
|
-
*
|
|
337
|
+
* Update the player's name on the server and locally.
|
|
338
|
+
* Returns true on success, false on failure.
|
|
192
339
|
*/
|
|
193
|
-
|
|
340
|
+
updatePlayerName(newName: string): Promise<boolean>;
|
|
194
341
|
/**
|
|
195
|
-
*
|
|
342
|
+
* Retry submitting a pending score (from a previous failed submission).
|
|
343
|
+
* Call this on app startup.
|
|
196
344
|
*/
|
|
197
|
-
|
|
345
|
+
retryPendingScore(): Promise<SessionScoreResult | null>;
|
|
346
|
+
/** Check if there's a pending score in the retry queue. */
|
|
347
|
+
hasPendingScore(): boolean;
|
|
198
348
|
/**
|
|
199
|
-
*
|
|
200
|
-
*
|
|
349
|
+
* Pre-fetch snapshot data in the background for instant display later.
|
|
350
|
+
* No-op if cache is disabled or already fresh.
|
|
201
351
|
*/
|
|
202
|
-
|
|
203
|
-
|
|
352
|
+
prefetch(): void;
|
|
353
|
+
/** Escape hatch: access the underlying KeeperBoardClient. */
|
|
354
|
+
getClient(): KeeperBoardClient;
|
|
355
|
+
private fetchSnapshot;
|
|
204
356
|
}
|
|
205
357
|
|
|
206
358
|
/**
|
|
@@ -252,4 +404,78 @@ declare class PlayerIdentity {
|
|
|
252
404
|
private generateUUID;
|
|
253
405
|
}
|
|
254
406
|
|
|
255
|
-
|
|
407
|
+
/**
|
|
408
|
+
* Pure-function name validation with configurable rules.
|
|
409
|
+
* Returns the sanitized name or null if invalid after sanitization.
|
|
410
|
+
*/
|
|
411
|
+
|
|
412
|
+
/**
|
|
413
|
+
* Validate and sanitize a player name.
|
|
414
|
+
*
|
|
415
|
+
* 1. Trims whitespace
|
|
416
|
+
* 2. Optionally converts to uppercase (default: yes)
|
|
417
|
+
* 3. Strips characters not matching `allowedPattern`
|
|
418
|
+
* 4. Truncates to `maxLength`
|
|
419
|
+
* 5. Returns `null` if result is shorter than `minLength`
|
|
420
|
+
*
|
|
421
|
+
* @example
|
|
422
|
+
* validateName(' Ace Pilot! ') // 'ACE_PILOT' → wait, no spaces allowed → 'ACEPILOT'
|
|
423
|
+
* validateName('ab') // 'AB'
|
|
424
|
+
* validateName('x') // null (too short)
|
|
425
|
+
*/
|
|
426
|
+
declare function validateName(input: string, options?: NameValidationOptions): string | null;
|
|
427
|
+
|
|
428
|
+
/**
|
|
429
|
+
* Generic TTL cache with in-flight deduplication and background refresh.
|
|
430
|
+
*/
|
|
431
|
+
declare class Cache<T> {
|
|
432
|
+
private data;
|
|
433
|
+
private fetchedAt;
|
|
434
|
+
private inflight;
|
|
435
|
+
private readonly ttlMs;
|
|
436
|
+
constructor(ttlMs: number);
|
|
437
|
+
/**
|
|
438
|
+
* Get cached value if fresh, otherwise fetch via the provided function.
|
|
439
|
+
* Deduplicates concurrent calls — only one fetch runs at a time.
|
|
440
|
+
*/
|
|
441
|
+
getOrFetch(fetchFn: () => Promise<T>): Promise<T>;
|
|
442
|
+
/**
|
|
443
|
+
* Trigger a background refresh without awaiting the result.
|
|
444
|
+
* Returns immediately. If a fetch is already in flight, does nothing.
|
|
445
|
+
*/
|
|
446
|
+
refreshInBackground(fetchFn: () => Promise<T>): void;
|
|
447
|
+
/** Invalidate the cache, forcing the next getOrFetch to re-fetch. */
|
|
448
|
+
invalidate(): void;
|
|
449
|
+
/** Get the cached value without fetching. Returns undefined if empty or stale. */
|
|
450
|
+
get(): T | undefined;
|
|
451
|
+
/** Get the cached value even if stale. Returns undefined only if never fetched. */
|
|
452
|
+
getStale(): T | undefined;
|
|
453
|
+
/** Check if the cache has fresh (non-expired) data. */
|
|
454
|
+
isFresh(): boolean;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
/**
|
|
458
|
+
* localStorage-based retry queue for failed score submissions.
|
|
459
|
+
* Persists a single pending score and auto-expires after maxAge.
|
|
460
|
+
*/
|
|
461
|
+
declare class RetryQueue {
|
|
462
|
+
private readonly storageKey;
|
|
463
|
+
private readonly maxAgeMs;
|
|
464
|
+
constructor(storageKey: string, maxAgeMs?: number);
|
|
465
|
+
/** Save a failed score for later retry. */
|
|
466
|
+
save(score: number, metadata?: Record<string, unknown>): void;
|
|
467
|
+
/**
|
|
468
|
+
* Get the pending score, or null if none exists or it has expired.
|
|
469
|
+
* Automatically clears expired entries.
|
|
470
|
+
*/
|
|
471
|
+
get(): {
|
|
472
|
+
score: number;
|
|
473
|
+
metadata?: Record<string, unknown>;
|
|
474
|
+
} | null;
|
|
475
|
+
/** Check if there's a pending score. */
|
|
476
|
+
hasPending(): boolean;
|
|
477
|
+
/** Clear the pending score. */
|
|
478
|
+
clear(): void;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
export { Cache, type ClaimResponse, type ClaimResult, type ClaimScoreOptions, type GetLeaderboardOptions, type GetPlayerRankOptions, type HealthResponse, type HealthResult, KeeperBoardClient, type KeeperBoardConfig, KeeperBoardError, KeeperBoardSession, type LeaderboardEntry, type LeaderboardResponse, type LeaderboardResult, type NameValidationOptions, PlayerIdentity, type PlayerIdentityConfig, type PlayerResponse, type PlayerResult, type ResetSchedule, RetryQueue, type ScoreResponse, type ScoreResult, type ScoreSubmission, type SessionConfig, type SessionScoreResult, type SnapshotEntry, type SnapshotResult, type SubmitScoreOptions, type UpdatePlayerNameOptions, validateName };
|