star-leaderboard 0.1.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 ADDED
@@ -0,0 +1,258 @@
1
+ # Star Leaderboard
2
+
3
+ A simple leaderboard SDK for web games that "just works" on Star.
4
+
5
+ `star-leaderboard` handles score submission and leaderboard display with a mobile-first design. It's designed to be predictable, easy to use, and LLM-friendly.
6
+
7
+ This package is part of the **Star SDK**.
8
+
9
+ ## Features
10
+
11
+ - **Bulletproof:** API failures won't crash your game - errors are logged but never thrown.
12
+ - **Zero Config on Star:** When running inside the Star platform, gameId is auto-detected.
13
+ - **Tiny API Surface:** A small, guessable API (`submit`, `show`, `getScores`). No try/catch needed.
14
+ - **Dual Mode:** Works inside Star platform (via postMessage) or standalone (direct API).
15
+ - **SSR-Safe:** Can be imported in server-side environments without errors.
16
+ - **Zero Dependencies:** Pure TypeScript, under 2KB minified.
17
+
18
+ ## Install
19
+
20
+ ```bash
21
+ yarn add star-leaderboard
22
+ # or
23
+ npm install star-leaderboard
24
+ ```
25
+
26
+ ## Quick Starts
27
+
28
+ ### 1. Vanilla JS on Star Platform (30 seconds)
29
+
30
+ This is the fastest way to add leaderboards to a Star game.
31
+
32
+ ```javascript
33
+ import { createLeaderboard } from 'star-leaderboard';
34
+
35
+ const leaderboard = createLeaderboard();
36
+
37
+ // When the game ends, submit the score and show the leaderboard
38
+ function gameOver(finalScore) {
39
+ leaderboard.submit(finalScore);
40
+ leaderboard.show();
41
+ }
42
+ ```
43
+
44
+ ### 2. With Async Result
45
+
46
+ If you want to know the player's rank after submitting:
47
+
48
+ ```javascript
49
+ import { createLeaderboard } from 'star-leaderboard';
50
+
51
+ const leaderboard = createLeaderboard();
52
+
53
+ async function gameOver(finalScore) {
54
+ const { success, rank } = await leaderboard.submit(finalScore);
55
+
56
+ if (success && rank) {
57
+ console.log(`You ranked #${rank}!`);
58
+ }
59
+
60
+ leaderboard.show();
61
+ }
62
+ ```
63
+
64
+ ### 3. Next.js / React (Client Component)
65
+
66
+ ```tsx
67
+ /* app/game/page.tsx */
68
+ "use client";
69
+ import { useEffect, useMemo } from 'react';
70
+ import { createLeaderboard } from 'star-leaderboard';
71
+
72
+ export default function Game() {
73
+ const leaderboard = useMemo(() => createLeaderboard(), []);
74
+
75
+ useEffect(() => {
76
+ return () => leaderboard.destroy();
77
+ }, [leaderboard]);
78
+
79
+ const handleGameOver = async (score: number) => {
80
+ await leaderboard.submit(score);
81
+ leaderboard.show();
82
+ };
83
+
84
+ return (
85
+ <div className="h-screen w-screen">
86
+ {/* Your game here */}
87
+ <button onClick={() => handleGameOver(1250)}>
88
+ End Game (Score: 1250)
89
+ </button>
90
+ </div>
91
+ );
92
+ }
93
+ ```
94
+
95
+ ### 4. Standalone Use (Outside Star Platform)
96
+
97
+ For games hosted outside Star that want to use the Star leaderboard API:
98
+
99
+ ```javascript
100
+ import { createLeaderboard } from 'star-leaderboard';
101
+
102
+ const leaderboard = createLeaderboard({
103
+ gameId: 'your-game-uuid',
104
+ apiBase: 'https://buildwithstar.com'
105
+ });
106
+
107
+ // Fetch and display scores in your own UI
108
+ const { scores, you, config } = await leaderboard.getScores({
109
+ timeframe: 'weekly',
110
+ limit: 10
111
+ });
112
+
113
+ scores.forEach(entry => {
114
+ console.log(`#${entry.rank} ${entry.playerName}: ${entry.score}`);
115
+ });
116
+
117
+ if (you) {
118
+ console.log(`Your best: #${you.rank} with ${you.score}`);
119
+ }
120
+ ```
121
+
122
+ ## Common Recipes
123
+
124
+ ### Submit Score and Show Leaderboard
125
+
126
+ ```javascript
127
+ // Fire and forget - simplest approach
128
+ leaderboard.submit(score);
129
+ leaderboard.show();
130
+
131
+ // Or wait for result
132
+ const result = await leaderboard.submit(score);
133
+ if (result.success) {
134
+ leaderboard.show();
135
+ }
136
+ ```
137
+
138
+ ### Custom Leaderboard UI
139
+
140
+ ```javascript
141
+ // Fetch scores for custom rendering
142
+ const data = await leaderboard.getScores({
143
+ timeframe: 'all_time',
144
+ limit: 100
145
+ });
146
+
147
+ // Build your own leaderboard UI
148
+ renderCustomLeaderboard(data.scores, data.config);
149
+ ```
150
+
151
+ ### Share Leaderboard
152
+
153
+ ```javascript
154
+ // Generate a shareable link
155
+ const { shareUrl } = await leaderboard.share({
156
+ score: 1250,
157
+ gameTitle: 'My Awesome Game'
158
+ });
159
+
160
+ // Use Web Share API or open in new tab
161
+ if (navigator.share) {
162
+ navigator.share({ url: shareUrl });
163
+ } else {
164
+ window.open(shareUrl, '_blank');
165
+ }
166
+ ```
167
+
168
+ ### Listen for Submit Events
169
+
170
+ ```javascript
171
+ const unsubscribe = leaderboard.onSubmit((result) => {
172
+ if (result.success) {
173
+ showConfetti();
174
+ }
175
+ });
176
+
177
+ // Later, clean up
178
+ unsubscribe();
179
+ ```
180
+
181
+ ## API Reference
182
+
183
+ ### `createLeaderboard(options?)`
184
+
185
+ Creates a leaderboard instance.
186
+
187
+ **Options:**
188
+ - `gameId?: string` - Game ID (auto-detected on Star platform)
189
+ - `apiBase?: string` - Base URL for API calls (default: relative paths)
190
+
191
+ **Returns:** `StarLeaderboard` instance
192
+
193
+ ### `StarLeaderboard`
194
+
195
+ | Method | Description |
196
+ |--------|-------------|
197
+ | `submit(score)` | Submit a score. Returns `Promise<SubmitResult>` |
198
+ | `show()` | Show the platform leaderboard UI (Star platform only) |
199
+ | `getScores(options?)` | Fetch leaderboard data. Returns `Promise<LeaderboardData>` |
200
+ | `share(options?)` | Generate a shareable link. Returns `Promise<ShareResult>` |
201
+ | `onSubmit(fn)` | Subscribe to submit events. Returns unsubscribe function |
202
+ | `destroy()` | Clean up resources |
203
+
204
+ **Aliases:**
205
+ - `submitScore` → alias for `submit`
206
+ - `showLeaderboard` → alias for `show`
207
+
208
+ ### Types
209
+
210
+ ```typescript
211
+ interface SubmitResult {
212
+ success: boolean;
213
+ rank?: number;
214
+ scoreId?: string;
215
+ error?: string;
216
+ }
217
+
218
+ interface LeaderboardData {
219
+ scores: ScoreEntry[];
220
+ config?: LeaderboardConfig;
221
+ timeframe: 'weekly' | 'all_time';
222
+ you?: ScoreEntry | null;
223
+ weekResetTime?: number | null;
224
+ }
225
+
226
+ interface ScoreEntry {
227
+ id: string;
228
+ playerName: string | null;
229
+ score: number;
230
+ rank: number;
231
+ submittedAt: string;
232
+ }
233
+ ```
234
+
235
+ ## Error Handling Philosophy
236
+
237
+ Star Leaderboard is designed to **never crash your game**. All methods gracefully handle failures:
238
+
239
+ ```javascript
240
+ // ✅ This never throws - even if the API is down!
241
+ const result = await leaderboard.submit(score);
242
+
243
+ if (!result.success) {
244
+ // ⚠️ Logged to console, error in result
245
+ console.log('Submission failed:', result.error);
246
+ }
247
+
248
+ // Game continues working
249
+ leaderboard.show(); // ✅ Still works (shows cached/empty state)
250
+ ```
251
+
252
+ **No try/catch needed.** Failed API calls are logged to the console with clear warnings, but your game keeps running.
253
+
254
+ ## Known Limitations
255
+
256
+ - **Platform UI only on Star:** The `show()` method only works inside the Star platform iframe. For standalone use, build custom UI with `getScores()`.
257
+ - **No offline support:** Scores require network connectivity to submit.
258
+ - **Weekly reset:** Weekly leaderboards reset Monday 02:00 UTC (Sunday 9pm ET).
package/dist/index.cjs ADDED
@@ -0,0 +1 @@
1
+ "use strict";var S=Object.defineProperty;var I=Object.getOwnPropertyDescriptor;var k=Object.getOwnPropertyNames;var R=Object.prototype.hasOwnProperty;var T=(n,t)=>{for(var d in t)S(n,d,{get:t[d],enumerable:!0})},P=(n,t,d,s)=>{if(t&&typeof t=="object"||typeof t=="function")for(let e of k(t))!R.call(n,e)&&e!==d&&S(n,e,{get:()=>t[e],enumerable:!(s=I(t,e))||s.enumerable});return n};var v=n=>P(S({},"__esModule",{value:!0}),n);var _={};T(_,{VERSION:()=>E,createLeaderboard:()=>h,default:()=>U});module.exports=v(_);var x="star_leaderboard_sdk",L="star_leaderboard_parent",O="star_game_runtime";function y(){let n=null,t=!1,d=0,s=new Map;function e(i,r){try{window.parent.postMessage({source:x,type:i,payload:r},"*")}catch{}}function u(i,r){try{window.parent.postMessage({source:O,type:i,payload:r},"*")}catch{}}function c(i){if(!i.data||typeof i.data!="object")return;let{source:r,type:a,payload:o}=i.data;if(r===L)switch(a){case"context":o?.gameId&&(n=o.gameId,t=!0);break;case"submit_result":{let m=o?.id,f=s.get(m);f&&(clearTimeout(f.timeoutId),s.delete(m),f.resolve({success:o?.success??!1,rank:o?.rank,scoreId:o?.scoreId,error:o?.error}));break}}}return window.addEventListener("message",c),e("request_context"),{get gameId(){return n},get ready(){return t},async submit(i){let r=++d;return new Promise(a=>{let o=setTimeout(()=>{s.has(r)&&(s.delete(r),a({success:!1,error:"Timeout waiting for response"}))},1e4);s.set(r,{resolve:a,timeoutId:o}),e("submit_score",{id:r,score:i})})},show(){u("star_show_leaderboard")},destroy(){window.removeEventListener("message",c);for(let[i,r]of s)clearTimeout(r.timeoutId),r.resolve({success:!1,error:"SDK destroyed"});s.clear()}}}function b(n=""){async function t(d,s,e){let u=`${n}${s}`,c={method:d,headers:{"Content-Type":"application/json"}};e!==void 0&&(c.body=JSON.stringify(e));let i=await fetch(u,c);if(!i.ok){let r=await i.json().catch(()=>({}));throw new Error(r.error||`HTTP ${i.status}`)}return i.json()}return{async submit(d,s){try{let e=encodeURIComponent(d),u=await t("POST",`/api/sdk/leaderboard/${e}/submit`,{score:s});return{success:u.success,rank:u.rank,scoreId:u.scoreId}}catch(e){return{success:!1,error:e instanceof Error?e.message:"Unknown error"}}},async getScores(d,s={}){try{let e=new URLSearchParams;s.limit&&e.set("limit",String(s.limit)),s.timeframe&&e.set("timeframe",s.timeframe);let u=e.toString(),i=`/api/sdk/leaderboard/${encodeURIComponent(d)}${u?`?${u}`:""}`,r=await t("GET",i);return{scores:r.scores.map((a,o)=>({id:a.id,playerName:a.playerName,score:a.score,rank:a.rank??o+1,submittedAt:a.submittedAt})),config:r.config,timeframe:r.timeframe,you:r.you?{id:r.you.id,playerName:r.you.playerName,score:r.you.score,rank:r.you.rank??0,submittedAt:r.you.submittedAt}:null,weekResetTime:r.weekResetTime}}catch{return{scores:[],timeframe:s.timeframe??"weekly",you:null}}},async share(d,s={}){try{let e=encodeURIComponent(d);return{success:!0,shareUrl:(await t("POST",`/api/sdk/leaderboard/${e}/share`,{gameTitle:s.gameTitle,score:s.score,playerName:s.playerName})).shareUrl}}catch(e){return{success:!1,error:e instanceof Error?e.message:"Unknown error"}}}}}function p(){return typeof window<"u"}function l(n,...t){p()&&typeof console<"u"&&console.warn(`[star-leaderboard] ${n}`,...t)}function g(n,...t){p()&&typeof console<"u"&&console.error(`[star-leaderboard] ${n}`,...t)}function C(){try{return window.parent===window?!1:typeof window.submitStarScore=="function"}catch{return!1}}function w(n={}){let t=C(),d=n.gameId??null,s=n.apiBase??"",e=null,u=null,c=new Set;t?e=y():u=b(s);function i(){return d??e?.gameId??null}let r={get ready(){return t?e?.ready??!1:d!==null},get gameId(){return i()},async submit(a){if(typeof a!="number"||!isFinite(a))return l("Invalid score value:",a),{success:!1,error:"Invalid score value"};try{let o;if(t&&e)o=await e.submit(a);else if(u){let m=i();if(!m)return l("gameId is required for standalone mode"),{success:!1,error:"gameId is required"};o=await u.submit(m,a)}else return{success:!1,error:"Not initialized"};for(let m of c)try{m(o)}catch(f){g("Submit handler error:",f)}return o}catch(o){let m=o instanceof Error?o.message:"Unknown error";return g("Failed to submit score:",o),{success:!1,error:m}}},async getScores(a={}){let o=i();return o?(u||(u=b(s)),u.getScores(o,a)):(l("gameId is required to fetch scores"),{scores:[],timeframe:a.timeframe??"weekly",you:null})},show(){t&&e?e.show():l("show() requires Star platform context. Use getScores() to build custom UI.")},async share(a={}){let o=i();if(!o)return l("gameId is required for sharing"),{success:!1,error:"gameId is required"};u||(u=b(s));try{return await u.share(o,a)}catch(m){return g("Failed to generate share link:",m),{success:!1,error:m instanceof Error?m.message:"Unknown error"}}},get submitScore(){return r.submit},get showLeaderboard(){return r.show},onSubmit(a){return c.add(a),()=>{c.delete(a)}},destroy(){e&&(e.destroy(),e=null),c.clear()}};return r}var E="0.0.1";function A(){let n=()=>{},t=d=>()=>Promise.resolve(d);return{ready:!1,gameId:null,submit:t({success:!1,error:"SSR environment"}),getScores:t({scores:[],timeframe:"weekly",you:null}),show:n,share:t({success:!1,error:"SSR environment"}),get submitScore(){return this.submit},get showLeaderboard(){return this.show},onSubmit:()=>n,destroy:n}}function h(n){return p()?w(n):A()}var U=h;0&&(module.exports={VERSION,createLeaderboard});
@@ -0,0 +1,228 @@
1
+ /**
2
+ * Star Leaderboard SDK
3
+ *
4
+ * Submit scores and display leaderboards for Star games.
5
+ *
6
+ * Design goals:
7
+ * - LLM-friendly: Obvious method names, copy-paste examples
8
+ * - Never crashes: All errors handled gracefully
9
+ * - Dual mode: Works inside Star platform or standalone
10
+ * - SSR-safe: No-op in server environments
11
+ */
12
+ declare const VERSION = "0.0.1";
13
+ /**
14
+ * Value type for leaderboard scores.
15
+ * The system auto-detects this based on the game.
16
+ */
17
+ type ValueType = 'score' | 'time' | 'moves' | 'attempts' | 'strokes';
18
+ /**
19
+ * Timeframe for fetching scores.
20
+ */
21
+ type Timeframe = 'weekly' | 'all_time';
22
+ /**
23
+ * Configuration for the leaderboard SDK.
24
+ */
25
+ interface LeaderboardOptions {
26
+ /**
27
+ * Game ID - auto-detected on Star platform, required for standalone use.
28
+ * If not provided, SDK will attempt to get it from platform context.
29
+ */
30
+ gameId?: string;
31
+ /**
32
+ * Base URL for API calls. Defaults to relative paths for Star platform.
33
+ * Set to 'https://buildwithstar.com' for standalone use.
34
+ */
35
+ apiBase?: string;
36
+ }
37
+ /**
38
+ * Result of submitting a score.
39
+ */
40
+ interface SubmitResult {
41
+ /** Whether the submission succeeded */
42
+ success: boolean;
43
+ /** Player's rank on the leaderboard (1 = first place) */
44
+ rank?: number;
45
+ /** Unique ID of the submitted score */
46
+ scoreId?: string;
47
+ /** Error message if submission failed */
48
+ error?: string;
49
+ }
50
+ /**
51
+ * A single score entry on the leaderboard.
52
+ */
53
+ interface ScoreEntry {
54
+ /** Unique ID of the score */
55
+ id: string;
56
+ /** Player's display name */
57
+ playerName: string | null;
58
+ /** The score value */
59
+ score: number;
60
+ /** Player's rank (1 = first place) */
61
+ rank: number;
62
+ /** ISO timestamp when score was submitted */
63
+ submittedAt: string;
64
+ }
65
+ /**
66
+ * Configuration for the leaderboard (auto-detected by AI).
67
+ */
68
+ interface LeaderboardConfig {
69
+ /** Sort order: DESC for "higher is better", ASC for "lower is better" */
70
+ sort?: 'ASC' | 'DESC';
71
+ /** Type of value being tracked */
72
+ valueType?: ValueType;
73
+ }
74
+ /**
75
+ * Full leaderboard data payload.
76
+ */
77
+ interface LeaderboardData {
78
+ /** Top scores */
79
+ scores: ScoreEntry[];
80
+ /** Leaderboard configuration */
81
+ config?: LeaderboardConfig;
82
+ /** Current timeframe */
83
+ timeframe: Timeframe;
84
+ /** Current user's score (if outside top scores) */
85
+ you?: ScoreEntry | null;
86
+ /** Unix timestamp (ms) when weekly leaderboard resets */
87
+ weekResetTime?: number | null;
88
+ }
89
+ /**
90
+ * Options for fetching scores.
91
+ */
92
+ interface GetScoresOptions {
93
+ /** Time period: 'weekly' (default) or 'all_time' */
94
+ timeframe?: Timeframe;
95
+ /** Maximum scores to return (default: 10) */
96
+ limit?: number;
97
+ }
98
+ /**
99
+ * Options for sharing the leaderboard.
100
+ */
101
+ interface ShareOptions {
102
+ /** Game title for the share card */
103
+ gameTitle?: string;
104
+ /** Score to highlight */
105
+ score?: number;
106
+ /** Player name to display */
107
+ playerName?: string;
108
+ }
109
+ /**
110
+ * Result of generating a share link.
111
+ */
112
+ interface ShareResult {
113
+ /** Whether share link was generated */
114
+ success: boolean;
115
+ /** Shareable URL */
116
+ shareUrl?: string;
117
+ /** Error message if failed */
118
+ error?: string;
119
+ }
120
+ /**
121
+ * The main Star Leaderboard SDK interface.
122
+ */
123
+ interface StarLeaderboard {
124
+ /** Whether the SDK is ready to use */
125
+ readonly ready: boolean;
126
+ /** Current game ID */
127
+ readonly gameId: string | null;
128
+ /**
129
+ * Submit a score to the leaderboard.
130
+ *
131
+ * @param score - The score value to submit
132
+ * @returns Result with success status and rank
133
+ *
134
+ * @example
135
+ * ```javascript
136
+ * const { success, rank } = await leaderboard.submit(1250);
137
+ * if (success) console.log(`You're ranked #${rank}!`);
138
+ * ```
139
+ */
140
+ submit(score: number): Promise<SubmitResult>;
141
+ /**
142
+ * Fetch leaderboard scores.
143
+ *
144
+ * @param options - Fetch options (timeframe, limit)
145
+ * @returns Leaderboard data with scores
146
+ *
147
+ * @example
148
+ * ```javascript
149
+ * const { scores, you } = await leaderboard.getScores({ timeframe: 'weekly' });
150
+ * scores.forEach(s => console.log(`#${s.rank} ${s.playerName}: ${s.score}`));
151
+ * ```
152
+ */
153
+ getScores(options?: GetScoresOptions): Promise<LeaderboardData>;
154
+ /**
155
+ * Show the platform leaderboard UI.
156
+ * Only works inside Star platform; logs warning otherwise.
157
+ *
158
+ * @example
159
+ * ```javascript
160
+ * // After game over
161
+ * leaderboard.submit(score);
162
+ * leaderboard.show();
163
+ * ```
164
+ */
165
+ show(): void;
166
+ /**
167
+ * Generate a shareable link for the leaderboard.
168
+ *
169
+ * @param options - Share options
170
+ * @returns Result with shareable URL
171
+ *
172
+ * @example
173
+ * ```javascript
174
+ * const { shareUrl } = await leaderboard.share({ score: 1250 });
175
+ * navigator.share?.({ url: shareUrl });
176
+ * ```
177
+ */
178
+ share(options?: ShareOptions): Promise<ShareResult>;
179
+ /** Alias for submit() - for LLM discoverability */
180
+ submitScore: StarLeaderboard['submit'];
181
+ /** Alias for show() - for LLM discoverability */
182
+ showLeaderboard: StarLeaderboard['show'];
183
+ /**
184
+ * Subscribe to score submission results.
185
+ *
186
+ * @param fn - Callback function
187
+ * @returns Unsubscribe function
188
+ */
189
+ onSubmit(fn: (result: SubmitResult) => void): () => void;
190
+ /**
191
+ * Clean up SDK resources.
192
+ */
193
+ destroy(): void;
194
+ }
195
+ /**
196
+ * Creates a Star Leaderboard SDK instance.
197
+ *
198
+ * @param options - Configuration options
199
+ * @returns StarLeaderboard instance
200
+ *
201
+ * @example Platform use (inside Star iframe):
202
+ * ```javascript
203
+ * import { createLeaderboard } from 'star-leaderboard';
204
+ *
205
+ * const leaderboard = createLeaderboard();
206
+ *
207
+ * // Submit a score when game ends
208
+ * await leaderboard.submit(1250);
209
+ *
210
+ * // Show the leaderboard UI
211
+ * leaderboard.show();
212
+ * ```
213
+ *
214
+ * @example Standalone use:
215
+ * ```javascript
216
+ * import { createLeaderboard } from 'star-leaderboard';
217
+ *
218
+ * const leaderboard = createLeaderboard({
219
+ * gameId: 'your-game-id',
220
+ * apiBase: 'https://buildwithstar.com'
221
+ * });
222
+ *
223
+ * const { scores } = await leaderboard.getScores({ timeframe: 'weekly' });
224
+ * ```
225
+ */
226
+ declare function createLeaderboard(options?: LeaderboardOptions): StarLeaderboard;
227
+
228
+ export { type GetScoresOptions, type LeaderboardConfig, type LeaderboardData, type LeaderboardOptions, type ScoreEntry, type ShareOptions, type ShareResult, type StarLeaderboard, type SubmitResult, type Timeframe, VERSION, type ValueType, createLeaderboard, createLeaderboard as default };
@@ -0,0 +1,228 @@
1
+ /**
2
+ * Star Leaderboard SDK
3
+ *
4
+ * Submit scores and display leaderboards for Star games.
5
+ *
6
+ * Design goals:
7
+ * - LLM-friendly: Obvious method names, copy-paste examples
8
+ * - Never crashes: All errors handled gracefully
9
+ * - Dual mode: Works inside Star platform or standalone
10
+ * - SSR-safe: No-op in server environments
11
+ */
12
+ declare const VERSION = "0.0.1";
13
+ /**
14
+ * Value type for leaderboard scores.
15
+ * The system auto-detects this based on the game.
16
+ */
17
+ type ValueType = 'score' | 'time' | 'moves' | 'attempts' | 'strokes';
18
+ /**
19
+ * Timeframe for fetching scores.
20
+ */
21
+ type Timeframe = 'weekly' | 'all_time';
22
+ /**
23
+ * Configuration for the leaderboard SDK.
24
+ */
25
+ interface LeaderboardOptions {
26
+ /**
27
+ * Game ID - auto-detected on Star platform, required for standalone use.
28
+ * If not provided, SDK will attempt to get it from platform context.
29
+ */
30
+ gameId?: string;
31
+ /**
32
+ * Base URL for API calls. Defaults to relative paths for Star platform.
33
+ * Set to 'https://buildwithstar.com' for standalone use.
34
+ */
35
+ apiBase?: string;
36
+ }
37
+ /**
38
+ * Result of submitting a score.
39
+ */
40
+ interface SubmitResult {
41
+ /** Whether the submission succeeded */
42
+ success: boolean;
43
+ /** Player's rank on the leaderboard (1 = first place) */
44
+ rank?: number;
45
+ /** Unique ID of the submitted score */
46
+ scoreId?: string;
47
+ /** Error message if submission failed */
48
+ error?: string;
49
+ }
50
+ /**
51
+ * A single score entry on the leaderboard.
52
+ */
53
+ interface ScoreEntry {
54
+ /** Unique ID of the score */
55
+ id: string;
56
+ /** Player's display name */
57
+ playerName: string | null;
58
+ /** The score value */
59
+ score: number;
60
+ /** Player's rank (1 = first place) */
61
+ rank: number;
62
+ /** ISO timestamp when score was submitted */
63
+ submittedAt: string;
64
+ }
65
+ /**
66
+ * Configuration for the leaderboard (auto-detected by AI).
67
+ */
68
+ interface LeaderboardConfig {
69
+ /** Sort order: DESC for "higher is better", ASC for "lower is better" */
70
+ sort?: 'ASC' | 'DESC';
71
+ /** Type of value being tracked */
72
+ valueType?: ValueType;
73
+ }
74
+ /**
75
+ * Full leaderboard data payload.
76
+ */
77
+ interface LeaderboardData {
78
+ /** Top scores */
79
+ scores: ScoreEntry[];
80
+ /** Leaderboard configuration */
81
+ config?: LeaderboardConfig;
82
+ /** Current timeframe */
83
+ timeframe: Timeframe;
84
+ /** Current user's score (if outside top scores) */
85
+ you?: ScoreEntry | null;
86
+ /** Unix timestamp (ms) when weekly leaderboard resets */
87
+ weekResetTime?: number | null;
88
+ }
89
+ /**
90
+ * Options for fetching scores.
91
+ */
92
+ interface GetScoresOptions {
93
+ /** Time period: 'weekly' (default) or 'all_time' */
94
+ timeframe?: Timeframe;
95
+ /** Maximum scores to return (default: 10) */
96
+ limit?: number;
97
+ }
98
+ /**
99
+ * Options for sharing the leaderboard.
100
+ */
101
+ interface ShareOptions {
102
+ /** Game title for the share card */
103
+ gameTitle?: string;
104
+ /** Score to highlight */
105
+ score?: number;
106
+ /** Player name to display */
107
+ playerName?: string;
108
+ }
109
+ /**
110
+ * Result of generating a share link.
111
+ */
112
+ interface ShareResult {
113
+ /** Whether share link was generated */
114
+ success: boolean;
115
+ /** Shareable URL */
116
+ shareUrl?: string;
117
+ /** Error message if failed */
118
+ error?: string;
119
+ }
120
+ /**
121
+ * The main Star Leaderboard SDK interface.
122
+ */
123
+ interface StarLeaderboard {
124
+ /** Whether the SDK is ready to use */
125
+ readonly ready: boolean;
126
+ /** Current game ID */
127
+ readonly gameId: string | null;
128
+ /**
129
+ * Submit a score to the leaderboard.
130
+ *
131
+ * @param score - The score value to submit
132
+ * @returns Result with success status and rank
133
+ *
134
+ * @example
135
+ * ```javascript
136
+ * const { success, rank } = await leaderboard.submit(1250);
137
+ * if (success) console.log(`You're ranked #${rank}!`);
138
+ * ```
139
+ */
140
+ submit(score: number): Promise<SubmitResult>;
141
+ /**
142
+ * Fetch leaderboard scores.
143
+ *
144
+ * @param options - Fetch options (timeframe, limit)
145
+ * @returns Leaderboard data with scores
146
+ *
147
+ * @example
148
+ * ```javascript
149
+ * const { scores, you } = await leaderboard.getScores({ timeframe: 'weekly' });
150
+ * scores.forEach(s => console.log(`#${s.rank} ${s.playerName}: ${s.score}`));
151
+ * ```
152
+ */
153
+ getScores(options?: GetScoresOptions): Promise<LeaderboardData>;
154
+ /**
155
+ * Show the platform leaderboard UI.
156
+ * Only works inside Star platform; logs warning otherwise.
157
+ *
158
+ * @example
159
+ * ```javascript
160
+ * // After game over
161
+ * leaderboard.submit(score);
162
+ * leaderboard.show();
163
+ * ```
164
+ */
165
+ show(): void;
166
+ /**
167
+ * Generate a shareable link for the leaderboard.
168
+ *
169
+ * @param options - Share options
170
+ * @returns Result with shareable URL
171
+ *
172
+ * @example
173
+ * ```javascript
174
+ * const { shareUrl } = await leaderboard.share({ score: 1250 });
175
+ * navigator.share?.({ url: shareUrl });
176
+ * ```
177
+ */
178
+ share(options?: ShareOptions): Promise<ShareResult>;
179
+ /** Alias for submit() - for LLM discoverability */
180
+ submitScore: StarLeaderboard['submit'];
181
+ /** Alias for show() - for LLM discoverability */
182
+ showLeaderboard: StarLeaderboard['show'];
183
+ /**
184
+ * Subscribe to score submission results.
185
+ *
186
+ * @param fn - Callback function
187
+ * @returns Unsubscribe function
188
+ */
189
+ onSubmit(fn: (result: SubmitResult) => void): () => void;
190
+ /**
191
+ * Clean up SDK resources.
192
+ */
193
+ destroy(): void;
194
+ }
195
+ /**
196
+ * Creates a Star Leaderboard SDK instance.
197
+ *
198
+ * @param options - Configuration options
199
+ * @returns StarLeaderboard instance
200
+ *
201
+ * @example Platform use (inside Star iframe):
202
+ * ```javascript
203
+ * import { createLeaderboard } from 'star-leaderboard';
204
+ *
205
+ * const leaderboard = createLeaderboard();
206
+ *
207
+ * // Submit a score when game ends
208
+ * await leaderboard.submit(1250);
209
+ *
210
+ * // Show the leaderboard UI
211
+ * leaderboard.show();
212
+ * ```
213
+ *
214
+ * @example Standalone use:
215
+ * ```javascript
216
+ * import { createLeaderboard } from 'star-leaderboard';
217
+ *
218
+ * const leaderboard = createLeaderboard({
219
+ * gameId: 'your-game-id',
220
+ * apiBase: 'https://buildwithstar.com'
221
+ * });
222
+ *
223
+ * const { scores } = await leaderboard.getScores({ timeframe: 'weekly' });
224
+ * ```
225
+ */
226
+ declare function createLeaderboard(options?: LeaderboardOptions): StarLeaderboard;
227
+
228
+ export { type GetScoresOptions, type LeaderboardConfig, type LeaderboardData, type LeaderboardOptions, type ScoreEntry, type ShareOptions, type ShareResult, type StarLeaderboard, type SubmitResult, type Timeframe, VERSION, type ValueType, createLeaderboard, createLeaderboard as default };
package/dist/index.mjs ADDED
@@ -0,0 +1 @@
1
+ var w="star_leaderboard_sdk",h="star_leaderboard_parent",I="star_game_runtime";function S(){let u=null,i=!1,d=0,s=new Map;function e(n,r){try{window.parent.postMessage({source:w,type:n,payload:r},"*")}catch{}}function a(n,r){try{window.parent.postMessage({source:I,type:n,payload:r},"*")}catch{}}function c(n){if(!n.data||typeof n.data!="object")return;let{source:r,type:o,payload:t}=n.data;if(r===h)switch(o){case"context":t?.gameId&&(u=t.gameId,i=!0);break;case"submit_result":{let m=t?.id,f=s.get(m);f&&(clearTimeout(f.timeoutId),s.delete(m),f.resolve({success:t?.success??!1,rank:t?.rank,scoreId:t?.scoreId,error:t?.error}));break}}}return window.addEventListener("message",c),e("request_context"),{get gameId(){return u},get ready(){return i},async submit(n){let r=++d;return new Promise(o=>{let t=setTimeout(()=>{s.has(r)&&(s.delete(r),o({success:!1,error:"Timeout waiting for response"}))},1e4);s.set(r,{resolve:o,timeoutId:t}),e("submit_score",{id:r,score:n})})},show(){a("star_show_leaderboard")},destroy(){window.removeEventListener("message",c);for(let[n,r]of s)clearTimeout(r.timeoutId),r.resolve({success:!1,error:"SDK destroyed"});s.clear()}}}function b(u=""){async function i(d,s,e){let a=`${u}${s}`,c={method:d,headers:{"Content-Type":"application/json"}};e!==void 0&&(c.body=JSON.stringify(e));let n=await fetch(a,c);if(!n.ok){let r=await n.json().catch(()=>({}));throw new Error(r.error||`HTTP ${n.status}`)}return n.json()}return{async submit(d,s){try{let e=encodeURIComponent(d),a=await i("POST",`/api/sdk/leaderboard/${e}/submit`,{score:s});return{success:a.success,rank:a.rank,scoreId:a.scoreId}}catch(e){return{success:!1,error:e instanceof Error?e.message:"Unknown error"}}},async getScores(d,s={}){try{let e=new URLSearchParams;s.limit&&e.set("limit",String(s.limit)),s.timeframe&&e.set("timeframe",s.timeframe);let a=e.toString(),n=`/api/sdk/leaderboard/${encodeURIComponent(d)}${a?`?${a}`:""}`,r=await i("GET",n);return{scores:r.scores.map((o,t)=>({id:o.id,playerName:o.playerName,score:o.score,rank:o.rank??t+1,submittedAt:o.submittedAt})),config:r.config,timeframe:r.timeframe,you:r.you?{id:r.you.id,playerName:r.you.playerName,score:r.you.score,rank:r.you.rank??0,submittedAt:r.you.submittedAt}:null,weekResetTime:r.weekResetTime}}catch{return{scores:[],timeframe:s.timeframe??"weekly",you:null}}},async share(d,s={}){try{let e=encodeURIComponent(d);return{success:!0,shareUrl:(await i("POST",`/api/sdk/leaderboard/${e}/share`,{gameTitle:s.gameTitle,score:s.score,playerName:s.playerName})).shareUrl}}catch(e){return{success:!1,error:e instanceof Error?e.message:"Unknown error"}}}}}function p(){return typeof window<"u"}function l(u,...i){p()&&typeof console<"u"&&console.warn(`[star-leaderboard] ${u}`,...i)}function g(u,...i){p()&&typeof console<"u"&&console.error(`[star-leaderboard] ${u}`,...i)}function k(){try{return window.parent===window?!1:typeof window.submitStarScore=="function"}catch{return!1}}function y(u={}){let i=k(),d=u.gameId??null,s=u.apiBase??"",e=null,a=null,c=new Set;i?e=S():a=b(s);function n(){return d??e?.gameId??null}let r={get ready(){return i?e?.ready??!1:d!==null},get gameId(){return n()},async submit(o){if(typeof o!="number"||!isFinite(o))return l("Invalid score value:",o),{success:!1,error:"Invalid score value"};try{let t;if(i&&e)t=await e.submit(o);else if(a){let m=n();if(!m)return l("gameId is required for standalone mode"),{success:!1,error:"gameId is required"};t=await a.submit(m,o)}else return{success:!1,error:"Not initialized"};for(let m of c)try{m(t)}catch(f){g("Submit handler error:",f)}return t}catch(t){let m=t instanceof Error?t.message:"Unknown error";return g("Failed to submit score:",t),{success:!1,error:m}}},async getScores(o={}){let t=n();return t?(a||(a=b(s)),a.getScores(t,o)):(l("gameId is required to fetch scores"),{scores:[],timeframe:o.timeframe??"weekly",you:null})},show(){i&&e?e.show():l("show() requires Star platform context. Use getScores() to build custom UI.")},async share(o={}){let t=n();if(!t)return l("gameId is required for sharing"),{success:!1,error:"gameId is required"};a||(a=b(s));try{return await a.share(t,o)}catch(m){return g("Failed to generate share link:",m),{success:!1,error:m instanceof Error?m.message:"Unknown error"}}},get submitScore(){return r.submit},get showLeaderboard(){return r.show},onSubmit(o){return c.add(o),()=>{c.delete(o)}},destroy(){e&&(e.destroy(),e=null),c.clear()}};return r}var _="0.0.1";function R(){let u=()=>{},i=d=>()=>Promise.resolve(d);return{ready:!1,gameId:null,submit:i({success:!1,error:"SSR environment"}),getScores:i({scores:[],timeframe:"weekly",you:null}),show:u,share:i({success:!1,error:"SSR environment"}),get submitScore(){return this.submit},get showLeaderboard(){return this.show},onSubmit:()=>u,destroy:u}}function T(u){return p()?y(u):R()}var N=T;export{_ as VERSION,T as createLeaderboard,N as default};
package/package.json ADDED
@@ -0,0 +1,56 @@
1
+ {
2
+ "name": "star-leaderboard",
3
+ "version": "0.1.0",
4
+ "private": false,
5
+ "description": "Leaderboard SDK for Star games - submit scores and display rankings.",
6
+ "type": "module",
7
+ "sideEffects": false,
8
+ "starSdk": {
9
+ "internalImport": "import { createLeaderboard } from '/star-sdk/v1/leaderboard.js';",
10
+ "publicImport": "import { createLeaderboard } from 'star-leaderboard';",
11
+ "publicPath": "dist/index.mjs",
12
+ "outputs": [
13
+ { "src": "dist/index.mjs", "dest": "v1/leaderboard.js" }
14
+ ],
15
+ "skill": {
16
+ "name": "star-leaderboard-sdk",
17
+ "description": "Star Leaderboard SDK for competitive game rankings. Use when implementing score submission, leaderboard display, high scores, or sharing game results."
18
+ }
19
+ },
20
+ "exports": {
21
+ ".": {
22
+ "types": "./dist/index.d.ts",
23
+ "import": "./dist/index.mjs",
24
+ "require": "./dist/index.cjs"
25
+ }
26
+ },
27
+ "module": "./dist/index.mjs",
28
+ "main": "./dist/index.cjs",
29
+ "types": "./dist/index.d.ts",
30
+ "files": [
31
+ "dist"
32
+ ],
33
+ "scripts": {
34
+ "build": "tsup",
35
+ "clean": "rm -rf dist",
36
+ "test": "jest",
37
+ "prepublishOnly": "node ../../apps/web/scripts/process-sdks.js --package star-leaderboard --public-only && yarn build"
38
+ },
39
+ "keywords": [
40
+ "leaderboard",
41
+ "scores",
42
+ "highscore",
43
+ "ranking",
44
+ "games",
45
+ "star-sdk"
46
+ ],
47
+ "dependencies": {},
48
+ "devDependencies": {
49
+ "@types/jest": "^29.5.12",
50
+ "jest": "^29.7.0",
51
+ "jest-environment-jsdom": "^29.7.0",
52
+ "ts-jest": "^29.1.2",
53
+ "tsup": "^8.0.2",
54
+ "typescript": "^5.4.5"
55
+ }
56
+ }