lightleaderboard
v1.0.0
Published
Official JavaScript/TypeScript SDK for LightLeaderboard — leaderboard-as-a-service for game developers
Maintainers
Readme
lightleaderboard
Official JavaScript / TypeScript SDK for LightLeaderboard — leaderboard-as-a-service for game developers.
Works in Node.js 18+, browsers, and any runtime with the Web Crypto API and native fetch (Deno, Bun, Cloudflare Workers, etc.).
Installation
npm install lightleaderboard
# or
yarn add lightleaderboard
# or
pnpm add lightleaderboardQuick start
import { LightLeaderboard } from 'lightleaderboard';
const lb = new LightLeaderboard({
apiKey: 'YOUR_API_KEY', // from the LightLeaderboard dashboard
gameId: 'YOUR_GAME_ID', // your game's reference ID
});
// Submit a score and get back the player's rank immediately
const result = await lb.submitScore({
score: 9500,
playerRefId: 'player-123',
playerName: 'Alice',
});
console.log(`Rank #${result.rank} of ${result.totalPlayers}!`);
// → "Rank #4 of 1024!"
// Fetch the top 10
const { entries } = await lb.getLeaderboard({ limit: 10 });
entries.forEach(e => {
console.log(`#${e.rank} ${e.playerName} ${e.score}`);
});API reference
new LightLeaderboard(config)
| Option | Type | Required | Description |
|--------|------|----------|-------------|
| apiKey | string | ✓ | Your game's API key from the dashboard |
| gameId | string | ✓ | Your game's reference ID |
| baseUrl | string | | Override the API base URL |
| scoreSecret | string | | Auto-sign scores with HMAC-SHA256 (see Score signing) |
submitScore(options)
Submit a score. Returns rank, personal-best flag, and total player count in a single call.
const result = await lb.submitScore({
score: 9500,
playerRefId: 'player-123', // your internal player ID
playerName: 'Alice', // display name
playTimeMs: 120_000, // run duration
seasonId: 'season-3', // optional season bucket
teamId: 'team-red', // optional team bucket
submissionId: crypto.randomUUID(), // idempotency key
metadata: { level: 5, combo: 12 }, // arbitrary JSON
});
console.log(result.rank); // 4 (player's current rank)
console.log(result.isPersonalBest); // true
console.log(result.totalPlayers); // 1024
console.log(result.deduped); // true if submissionId was already seenResponse fields
| Field | Type | Description |
|-------|------|-------------|
| id | number | Database ID of the created entry |
| rank | number \| null | Player's rank right after submission (null if no playerRefId) |
| isPersonalBest | boolean | Whether this beats the player's previous best |
| totalPlayers | number \| null | Total unique players on this leaderboard |
| deduped | boolean? | true when submissionId was already seen |
getLeaderboard(options?)
Fetch the leaderboard. Returns one entry per player (their best score) by default.
const { entries } = await lb.getLeaderboard({
limit: 20, // 1–100, default 20
offset: 0, // pagination
period: 'weekly', // 'all' | 'weekly' | 'monthly'
season: 'season-3',
team: 'team-red',
allEntries: false, // true → return every raw submission
});
// Each entry has a `rank` field (1-based, offset-aware)
entries.forEach(e => console.log(e.rank, e.playerName, e.score));Pagination example
// Page 2 of a weekly board
const page2 = await lb.getLeaderboard({ limit: 20, offset: 20, period: 'weekly' });getPlayerRank(playerRefId, options?)
Get a player's rank, score, and percentile in a single call.
const rank = await lb.getPlayerRank('player-123', { period: 'weekly' });
console.log(rank.rank); // 4
console.log(rank.score); // 9500
console.log(rank.totalPlayers); // 1024
console.log(rank.percentile); // 99.7 → top 0.3%percentile is 0–100, higher is better. rank 1 of 100 → percentile 100.
getCentricLeaderboard(playerRefId, options?)
Fetch the leaderboard centered on a specific player — the entries immediately above and below them. Great for in-game "you vs. your neighbors" screens.
const { entries, playerRank } = await lb.getCentricLeaderboard('player-123', {
limit: 11, // 5 above + player + 5 below
});getPlayer(playerRefId)
Fetch a player's profile.
const profile = await lb.getPlayer('player-123');
console.log(profile.playerName, profile.avatarUrl, profile.country);updatePlayer(playerRefId, options)
Create or update a player's profile. Fields are merged — omitted fields keep their current value.
await lb.updatePlayer('player-123', {
playerName: 'Alice',
avatarUrl: 'https://example.com/avatar.png',
country: 'US',
level: 42,
device: 'mobile',
});getPlayerScores(playerRefId, options?)
Fetch all submissions a player has made, ordered newest first. Useful for progression graphs and run-history screens.
const { entries, bestScore, total } = await lb.getPlayerScores('player-123', {
limit: 50, // 1–200, default 50
offset: 0,
});
console.log(`${total} total runs, best: ${bestScore}`);
entries.forEach(e => console.log(e.score, e.createdAt));Score signing
If you enable "Require signed scores" on your game in the dashboard, every submission must include a valid HMAC-SHA256 signature. Pass scoreSecret to the constructor and the SDK handles signing automatically:
const lb = new LightLeaderboard({
apiKey: 'YOUR_API_KEY',
gameId: 'YOUR_GAME_ID',
scoreSecret: 'YOUR_SCORE_SECRET', // from the dashboard → Signature Secret
});
// Scores are now automatically signed — no extra code needed
await lb.submitScore({ score: 9500, playerRefId: 'p1' });Signing uses the Web Crypto API (crypto.subtle), available natively in Node 18+, modern browsers, Bun, Deno, and Cloudflare Workers.
Error handling
All methods throw LightLeaderboardError on failure.
import { LightLeaderboard, LightLeaderboardError } from 'lightleaderboard';
try {
await lb.submitScore({ score: 9500 });
} catch (err) {
if (err instanceof LightLeaderboardError) {
console.error(err.message); // human-readable message from the API
console.error(err.status); // HTTP status code
console.error(err.response); // raw response body
if (err.isAuthError) console.error('Check your API key');
if (err.isRateLimitError) console.error('Slow down score submissions');
if (err.isBillingError) console.error('Free tier limit reached');
if (err.isValidationError) console.error('Invalid score data');
}
}TypeScript
The SDK is written in TypeScript and ships full type declarations. All options and return types are exported:
import type {
SubmitScoreOptions,
SubmitScoreResult,
GetLeaderboardOptions,
GetLeaderboardResult,
PlayerRankResult,
LeaderboardEntry,
} from 'lightleaderboard';License
MIT
