@openleaderboard/sdk 0.2.0 → 0.4.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/dist/index.d.ts CHANGED
@@ -16,6 +16,14 @@ export interface RankEntry {
16
16
  score: number;
17
17
  rank: number;
18
18
  exact: boolean;
19
+ nickname?: string;
20
+ }
21
+ /** A registered player: server-minted id + nickname unique per app (case-insensitive). */
22
+ export interface User {
23
+ user_id: string;
24
+ nickname: string;
25
+ created_at?: string;
26
+ updated_at?: string;
19
27
  }
20
28
  export interface SubmitResult {
21
29
  accepted: boolean;
@@ -30,6 +38,13 @@ export interface QueryOpts {
30
38
  export interface SubmitOpts {
31
39
  segments?: string[];
32
40
  idem?: string;
41
+ /**
42
+ * Event time of the score. Determines which time-window bucket it lands in
43
+ * (e.g. the daily board) — set it to the session start so a run that crosses
44
+ * midnight counts for the day it began, rather than when it was submitted.
45
+ * Accepts a Date or an ISO-8601 string; defaults to server receive time.
46
+ */
47
+ time?: Date | string;
33
48
  }
34
49
  export interface WindowDef {
35
50
  kind: string;
@@ -71,6 +86,10 @@ export declare class LeaderboardError extends Error {
71
86
  export declare class NotFoundError extends LeaderboardError {
72
87
  constructor(message: string);
73
88
  }
89
+ /** Thrown when a nickname is already claimed in this app (HTTP 409). */
90
+ export declare class NicknameTakenError extends LeaderboardError {
91
+ constructor(message: string);
92
+ }
74
93
  export declare class LeaderboardClient {
75
94
  private readonly apiKey;
76
95
  private readonly opts;
@@ -98,6 +117,23 @@ export declare class LeaderboardClient {
98
117
  getNeighbors(board: string, member: string, k: number, q?: QueryOpts): Promise<RankEntry[]>;
99
118
  /** Rank an explicit set of members against each other (a friend leaderboard). */
100
119
  getFriends(board: string, members: string[], q?: QueryOpts): Promise<RankEntry[]>;
120
+ /**
121
+ * Register a player: mints a `plr_...` user id and claims a nickname
122
+ * (unique per app, case-insensitive). Submit scores with `user_id` as the
123
+ * member; reads then include the nickname. Throws {@link NicknameTakenError}
124
+ * if the name is claimed.
125
+ */
126
+ registerUser(nickname: string): Promise<User>;
127
+ /** Fetch a registered player by id. Throws {@link NotFoundError} if absent. */
128
+ getUser(userId: string): Promise<User>;
129
+ /** Resolve a nickname (case-insensitive) to its player. */
130
+ getUserByNickname(nickname: string): Promise<User>;
131
+ /**
132
+ * Change a player's nickname. The user id — and therefore board data and
133
+ * HMAC signatures — is unaffected. Throws {@link NicknameTakenError} on
134
+ * conflict.
135
+ */
136
+ renameUser(userId: string, nickname: string): Promise<User>;
101
137
  private send;
102
138
  }
103
139
  /**
package/dist/index.js CHANGED
@@ -26,6 +26,13 @@ export class NotFoundError extends LeaderboardError {
26
26
  this.name = "NotFoundError";
27
27
  }
28
28
  }
29
+ /** Thrown when a nickname is already claimed in this app (HTTP 409). */
30
+ export class NicknameTakenError extends LeaderboardError {
31
+ constructor(message) {
32
+ super(409, message);
33
+ this.name = "NicknameTakenError";
34
+ }
35
+ }
29
36
  export class LeaderboardClient {
30
37
  constructor(baseUrl, apiKey, opts = {}) {
31
38
  this.apiKey = apiKey;
@@ -58,6 +65,7 @@ export class LeaderboardClient {
58
65
  score,
59
66
  segments: opts.segments,
60
67
  idem: opts.idem,
68
+ time: opts.time instanceof Date ? opts.time.toISOString() : opts.time,
61
69
  };
62
70
  if (this.opts.signingSecret) {
63
71
  const ts = Math.floor(Date.now() / 1000);
@@ -102,6 +110,31 @@ export class LeaderboardClient {
102
110
  const r = await this.send("POST", `/v1/boards/${enc(board)}/friends${qs({ ...q })}`, { members });
103
111
  return r.entries ?? [];
104
112
  }
113
+ /**
114
+ * Register a player: mints a `plr_...` user id and claims a nickname
115
+ * (unique per app, case-insensitive). Submit scores with `user_id` as the
116
+ * member; reads then include the nickname. Throws {@link NicknameTakenError}
117
+ * if the name is claimed.
118
+ */
119
+ async registerUser(nickname) {
120
+ return this.send("POST", "/v1/users", { nickname });
121
+ }
122
+ /** Fetch a registered player by id. Throws {@link NotFoundError} if absent. */
123
+ async getUser(userId) {
124
+ return this.send("GET", `/v1/users/${enc(userId)}`);
125
+ }
126
+ /** Resolve a nickname (case-insensitive) to its player. */
127
+ async getUserByNickname(nickname) {
128
+ return this.send("GET", `/v1/users${qs({ nickname })}`);
129
+ }
130
+ /**
131
+ * Change a player's nickname. The user id — and therefore board data and
132
+ * HMAC signatures — is unaffected. Throws {@link NicknameTakenError} on
133
+ * conflict.
134
+ */
135
+ async renameUser(userId, nickname) {
136
+ return this.send("PATCH", `/v1/users/${enc(userId)}`, { nickname });
137
+ }
105
138
  async send(method, path, body) {
106
139
  const headers = { Authorization: `Bearer ${this.apiKey}` };
107
140
  let bodyStr;
@@ -113,6 +146,8 @@ export class LeaderboardClient {
113
146
  const text = await resp.text();
114
147
  if (resp.status === 404)
115
148
  throw new NotFoundError(text);
149
+ if (resp.status === 409)
150
+ throw new NicknameTakenError(text);
116
151
  if (!resp.ok)
117
152
  throw new LeaderboardError(resp.status, `${method} ${path} -> ${resp.status}: ${text}`);
118
153
  return text ? JSON.parse(text) : {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openleaderboard/sdk",
3
- "version": "0.2.0",
3
+ "version": "0.4.0",
4
4
  "description": "TypeScript client for the OpenLeaderboard API (browser + Node 18+).",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -12,7 +12,9 @@
12
12
  "import": "./dist/index.js"
13
13
  }
14
14
  },
15
- "files": ["dist"],
15
+ "files": [
16
+ "dist"
17
+ ],
16
18
  "scripts": {
17
19
  "build": "tsc -p tsconfig.json",
18
20
  "typecheck": "tsc -p tsconfig.json --noEmit",
@@ -20,7 +22,12 @@
20
22
  "test:hmac": "node test/hmac.mjs",
21
23
  "prepublishOnly": "npm run build"
22
24
  },
23
- "keywords": ["leaderboard", "gaming", "ranking", "api"],
25
+ "keywords": [
26
+ "leaderboard",
27
+ "gaming",
28
+ "ranking",
29
+ "api"
30
+ ],
24
31
  "license": "Apache-2.0",
25
32
  "repository": {
26
33
  "type": "git",
@@ -28,8 +35,16 @@
28
35
  "directory": "sdk/typescript"
29
36
  },
30
37
  "homepage": "https://github.com/kodeni-am/leaderboard/tree/main/sdk/typescript#readme",
31
- "bugs": { "url": "https://github.com/kodeni-am/leaderboard/issues" },
32
- "publishConfig": { "access": "public" },
33
- "engines": { "node": ">=18" },
34
- "devDependencies": { "typescript": "^5.5.0" }
38
+ "bugs": {
39
+ "url": "https://github.com/kodeni-am/leaderboard/issues"
40
+ },
41
+ "publishConfig": {
42
+ "access": "public"
43
+ },
44
+ "engines": {
45
+ "node": ">=18"
46
+ },
47
+ "devDependencies": {
48
+ "typescript": "^5.5.0"
49
+ }
35
50
  }