scorezilla 0.2.0 → 0.3.0-next.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/CHANGELOG.md CHANGED
@@ -1,5 +1,36 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.3.0-next.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#36](https://github.com/isco-tec/scorezilla-js/pull/36) [`19c2dcc`](https://github.com/isco-tec/scorezilla-js/commit/19c2dcc14d2000551d80498813b075172c8f4d66) Thanks [@isco-tec](https://github.com/isco-tec)! - feat(identity): preset helpers for `scorezilla/identity` (Phase 1)
8
+
9
+ New subpath export: `scorezilla/identity`. Three identity-strategy
10
+ presets ship as `stable`; one OAuth helper ships as a preview stub.
11
+
12
+ **Stable in this release:**
13
+ - `useAnonymousPlayer({ storageKey })` — generates a UUID, persists in
14
+ localStorage, same browser keeps the same id across reloads. Returns
15
+ `{ id, forget() }`. Privacy-safe by default (no PII).
16
+ - `usePromptedPlayer({ storageKey, prompt })` — `window.prompt()` on
17
+ first run, persists to localStorage. Returns `{ id, forget() } | null`
18
+ (null when SSR, no `prompt`, or user cancels).
19
+ - `useServerAuthoritative()` — no-op marker for snippets using the
20
+ HMAC-signed secure path (`scorezilla/server`). The browser SDK does
21
+ no identity work; the server picks the value.
22
+
23
+ **Preview stub in this release (throws on call):**
24
+ - `useAuthProvider({ provider: 'google' | 'github' })` — OAuth-backed
25
+ identity. Full implementation (Google + GitHub for v1) ships in a
26
+ follow-up `next` release before the 0.3.0 latest promote.
27
+
28
+ Per [ADR 0003](https://github.com/isco-tec/scorezilla/blob/main/docs/adr/0003-mcp-identity-axis.md). All helpers document where data is stored and
29
+ what `forget()` / `signOut()` does NOT do (server-side history is
30
+ retained — call admin delete-player for full erasure).
31
+
32
+ Closes upstream tracking issue isco-tec/scorezilla#125 (Phase 1).
33
+
3
34
  ## 0.2.0 — `scorezilla/server` HMAC adapter GA
4
35
 
5
36
  ### Minor Changes
@@ -0,0 +1,87 @@
1
+ 'use strict';
2
+
3
+ // src/identity.ts
4
+ var isBrowser = () => typeof window !== "undefined";
5
+ function readPersisted(key) {
6
+ if (!isBrowser()) return null;
7
+ try {
8
+ return window.localStorage.getItem(key);
9
+ } catch {
10
+ return null;
11
+ }
12
+ }
13
+ function writePersisted(key, value) {
14
+ if (!isBrowser()) return;
15
+ try {
16
+ window.localStorage.setItem(key, value);
17
+ } catch {
18
+ }
19
+ }
20
+ function removePersisted(key) {
21
+ if (!isBrowser()) return;
22
+ try {
23
+ window.localStorage.removeItem(key);
24
+ } catch {
25
+ }
26
+ }
27
+ function mintUuid() {
28
+ if (isBrowser() && typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
29
+ return crypto.randomUUID();
30
+ }
31
+ return `anon-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;
32
+ }
33
+ function requireStorageKey(fnName, options) {
34
+ if (!options || typeof options.storageKey !== "string" || options.storageKey.length === 0) {
35
+ throw new TypeError(`${fnName}: options.storageKey is required (non-empty string)`);
36
+ }
37
+ return options.storageKey;
38
+ }
39
+ function useAnonymousPlayer(options) {
40
+ const storageKey = requireStorageKey("useAnonymousPlayer", options);
41
+ let id = readPersisted(storageKey);
42
+ if (id === null || id.length === 0) {
43
+ id = mintUuid();
44
+ writePersisted(storageKey, id);
45
+ }
46
+ return {
47
+ id,
48
+ forget: () => removePersisted(storageKey)
49
+ };
50
+ }
51
+ function usePromptedPlayer(options) {
52
+ const storageKey = requireStorageKey("usePromptedPlayer", options);
53
+ if (typeof options.prompt !== "string" || options.prompt.length === 0) {
54
+ throw new TypeError("usePromptedPlayer: options.prompt is required (non-empty string)");
55
+ }
56
+ let id = readPersisted(storageKey);
57
+ if (id === null || id.length === 0) {
58
+ if (!isBrowser() || typeof window.prompt !== "function") {
59
+ return null;
60
+ }
61
+ const entered = window.prompt(options.prompt);
62
+ if (entered === null || entered.length === 0) {
63
+ return null;
64
+ }
65
+ id = entered;
66
+ writePersisted(storageKey, id);
67
+ }
68
+ return {
69
+ id,
70
+ forget: () => removePersisted(storageKey)
71
+ };
72
+ }
73
+ function useServerAuthoritative() {
74
+ return { source: "server-authoritative" };
75
+ }
76
+ function useAuthProvider(_options) {
77
+ throw new Error(
78
+ "useAuthProvider is not yet implemented in this 0.3.0-next preview. OAuth provider helpers (Google + GitHub for v1) ship in a follow-up release on the `next` dist-tag. Until then, drive your own OAuth flow and pass the resulting user identifier to submitScore directly."
79
+ );
80
+ }
81
+
82
+ exports.useAnonymousPlayer = useAnonymousPlayer;
83
+ exports.useAuthProvider = useAuthProvider;
84
+ exports.usePromptedPlayer = usePromptedPlayer;
85
+ exports.useServerAuthoritative = useServerAuthoritative;
86
+ //# sourceMappingURL=identity.cjs.map
87
+ //# sourceMappingURL=identity.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/identity.ts"],"names":[],"mappings":"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","file":"identity.cjs","sourcesContent":["/**\n * Identity preset helpers — opinionated, selectable ways for your game to\n * generate or fetch a `playerId` for score submission.\n *\n * Background: every Scorezilla score carries an opaque `playerId`. The SDK\n * doesn't care whether it's a UUID, a nickname, an email, or a server\n * session token. But _how_ your game decides on that value is a UX +\n * privacy decision the team should make explicitly. These presets are the\n * blessed patterns; pick one per integration.\n *\n * See ADR 0003 (MCP identity axis) for the design rationale:\n * https://github.com/isco-tec/scorezilla/blob/main/docs/adr/0003-mcp-identity-axis.md\n *\n * @module scorezilla/identity\n * @since 0.3.0\n */\n\nexport interface AnonymousPlayerOptions {\n /** localStorage key under which the generated UUID is persisted. */\n readonly storageKey: string;\n}\n\nexport interface PromptedPlayerOptions {\n /** localStorage key under which the user-entered name is persisted. */\n readonly storageKey: string;\n /** Message shown in `window.prompt()` on first run. */\n readonly prompt: string;\n}\n\n/**\n * Identity handle returned by the storage-backed presets.\n *\n * `forget()` clears the persisted value from browser storage. It does\n * **not** delete server-side score history for this player — to fully\n * erase a player's data, call the admin \"delete player\" endpoint.\n */\nexport interface PlayerHandle {\n readonly id: string;\n readonly forget: () => void;\n}\n\n/**\n * Marker returned by `useServerAuthoritative()` to signal that the\n * game's backend (not the browser) owns the `playerId` via the\n * HMAC-signed secure path (`scorezilla/server`).\n */\nexport interface ServerAuthoritativeMarker {\n readonly source: 'server-authoritative';\n}\n\nconst isBrowser = (): boolean => typeof window !== 'undefined';\n\nfunction readPersisted(key: string): string | null {\n if (!isBrowser()) return null;\n try {\n return window.localStorage.getItem(key);\n } catch {\n // Storage may throw in sandboxed iframes, privacy mode, or when the\n // user has disabled site data. Treat as \"missing\"; the caller will\n // mint or re-prompt.\n return null;\n }\n}\n\nfunction writePersisted(key: string, value: string): void {\n if (!isBrowser()) return;\n try {\n window.localStorage.setItem(key, value);\n } catch {\n // ignore; next call will re-mint or re-prompt\n }\n}\n\nfunction removePersisted(key: string): void {\n if (!isBrowser()) return;\n try {\n window.localStorage.removeItem(key);\n } catch {\n // ignore\n }\n}\n\nfunction mintUuid(): string {\n if (isBrowser() && typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {\n return crypto.randomUUID();\n }\n // Best-effort fallback: timestamp + random suffix. Not cryptographically\n // strong, but opaque enough for the identifier-only use case. The\n // browsers we target (Chrome 92+, Firefox 95+, Safari 15.4+) all have\n // crypto.randomUUID — this branch is reached only in non-browser\n // environments where useAnonymousPlayer shouldn't be called anyway.\n return `anon-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;\n}\n\nfunction requireStorageKey(fnName: string, options: { storageKey?: unknown } | undefined): string {\n if (!options || typeof options.storageKey !== 'string' || options.storageKey.length === 0) {\n throw new TypeError(`${fnName}: options.storageKey is required (non-empty string)`);\n }\n return options.storageKey;\n}\n\n/**\n * Anonymous player identity. Generates an opaque UUID on first run and\n * persists it in `localStorage` so the same browser keeps the same ID\n * across page reloads.\n *\n * **Privacy.** Stores a randomly-generated UUID in browser localStorage;\n * the value is sent to the API on every score submission and persisted\n * indefinitely in the player's score-history rows. No PII is collected.\n * `forget()` removes the localStorage entry; for full server-side erasure\n * call the admin \"delete player\" endpoint.\n *\n * @example\n * ```ts\n * import { Scorezilla } from 'scorezilla';\n * import { useAnonymousPlayer } from 'scorezilla/identity';\n *\n * const player = useAnonymousPlayer({ storageKey: 'mygame:player' });\n * const sz = new Scorezilla({ publicKey: 'pk_…' });\n * await sz.submitScore({ boardId, playerId: player.id, score: 42 });\n * ```\n *\n * @since 0.3.0\n * @stability stable\n */\nexport function useAnonymousPlayer(options: AnonymousPlayerOptions): PlayerHandle {\n const storageKey = requireStorageKey('useAnonymousPlayer', options);\n let id = readPersisted(storageKey);\n if (id === null || id.length === 0) {\n id = mintUuid();\n writePersisted(storageKey, id);\n }\n return {\n id,\n forget: () => removePersisted(storageKey),\n };\n}\n\n/**\n * Prompted player identity. On first run shows a `window.prompt()` asking\n * the user for a name, then persists it in `localStorage` for subsequent\n * visits. Returns `null` if there is no browser (SSR), no `window.prompt`,\n * or if the user cancelled / entered an empty value.\n *\n * **Privacy.** The user-entered string is stored in browser localStorage,\n * transmitted to the API on every score submission, and persisted\n * indefinitely on the leaderboard. The persisted value is whatever the\n * user typed — sanitize at the UI layer if you care. `forget()` clears\n * local state but does NOT delete server-side history.\n *\n * **UX caveat.** `window.prompt()` blocks the main thread and looks\n * dated in modern apps. For a polished flow, build your own inline form\n * and pass the result to `submitScore` directly — the preset is here to\n * cover quick prototypes and jam-style integrations.\n *\n * @example\n * ```ts\n * import { Scorezilla } from 'scorezilla';\n * import { usePromptedPlayer } from 'scorezilla/identity';\n *\n * const player = usePromptedPlayer({\n * storageKey: 'mygame:player',\n * prompt: 'Enter a name for the leaderboard:',\n * });\n *\n * if (player) {\n * const sz = new Scorezilla({ publicKey: 'pk_…' });\n * await sz.submitScore({ boardId, playerId: player.id, score: 42 });\n * }\n * ```\n *\n * @since 0.3.0\n * @stability stable\n */\nexport function usePromptedPlayer(options: PromptedPlayerOptions): PlayerHandle | null {\n const storageKey = requireStorageKey('usePromptedPlayer', options);\n if (typeof options.prompt !== 'string' || options.prompt.length === 0) {\n throw new TypeError('usePromptedPlayer: options.prompt is required (non-empty string)');\n }\n\n let id = readPersisted(storageKey);\n if (id === null || id.length === 0) {\n if (!isBrowser() || typeof window.prompt !== 'function') {\n return null;\n }\n const entered = window.prompt(options.prompt);\n if (entered === null || entered.length === 0) {\n return null;\n }\n id = entered;\n writePersisted(storageKey, id);\n }\n return {\n id,\n forget: () => removePersisted(storageKey),\n };\n}\n\n/**\n * Server-authoritative identity marker. Signals that the game's backend\n * is responsible for the `playerId` via the HMAC-signed secure path\n * (`scorezilla/server`). The browser SDK does no identity work — the\n * server picks the value, signs the submission, and posts.\n *\n * The return value is a no-op marker; you don't pass it anywhere. It\n * exists so MCP-returned snippets can emit a single line that\n * unambiguously says \"this game uses the secure path; identity is\n * server-authoritative.\"\n *\n * @example\n * ```ts\n * // Client (no identity helper needed):\n * import { useServerAuthoritative } from 'scorezilla/identity';\n * useServerAuthoritative();\n *\n * // Server (where the real work happens):\n * import { Scorezilla } from 'scorezilla/server';\n * const sz = new Scorezilla({ secretKey: process.env.SCOREZILLA_SECRET_KEY! });\n * await sz.submitScore({ boardId, playerId: serverDerivedId, score });\n * ```\n *\n * @since 0.3.0\n * @stability stable\n */\nexport function useServerAuthoritative(): ServerAuthoritativeMarker {\n return { source: 'server-authoritative' };\n}\n\n/**\n * OAuth-backed player identity. **Preview stub in 0.3.0-next.x** — throws\n * on call. Full implementation (Google + GitHub for v1, Apple + Discord\n * deferred) lands in a follow-up release on the `next` dist-tag, before\n * the `latest` 0.3.0 ships.\n *\n * Until then: drive your own OAuth flow and pass the resulting user\n * identifier to `submitScore` directly.\n *\n * @since 0.3.0\n * @stability preview\n */\nexport function useAuthProvider(_options: { readonly provider: 'google' | 'github' }): never {\n throw new Error(\n 'useAuthProvider is not yet implemented in this 0.3.0-next preview. ' +\n 'OAuth provider helpers (Google + GitHub for v1) ship in a follow-up ' +\n 'release on the `next` dist-tag. Until then, drive your own OAuth flow ' +\n 'and pass the resulting user identifier to submitScore directly.',\n );\n}\n"]}
@@ -0,0 +1,151 @@
1
+ /**
2
+ * Identity preset helpers — opinionated, selectable ways for your game to
3
+ * generate or fetch a `playerId` for score submission.
4
+ *
5
+ * Background: every Scorezilla score carries an opaque `playerId`. The SDK
6
+ * doesn't care whether it's a UUID, a nickname, an email, or a server
7
+ * session token. But _how_ your game decides on that value is a UX +
8
+ * privacy decision the team should make explicitly. These presets are the
9
+ * blessed patterns; pick one per integration.
10
+ *
11
+ * See ADR 0003 (MCP identity axis) for the design rationale:
12
+ * https://github.com/isco-tec/scorezilla/blob/main/docs/adr/0003-mcp-identity-axis.md
13
+ *
14
+ * @module scorezilla/identity
15
+ * @since 0.3.0
16
+ */
17
+ interface AnonymousPlayerOptions {
18
+ /** localStorage key under which the generated UUID is persisted. */
19
+ readonly storageKey: string;
20
+ }
21
+ interface PromptedPlayerOptions {
22
+ /** localStorage key under which the user-entered name is persisted. */
23
+ readonly storageKey: string;
24
+ /** Message shown in `window.prompt()` on first run. */
25
+ readonly prompt: string;
26
+ }
27
+ /**
28
+ * Identity handle returned by the storage-backed presets.
29
+ *
30
+ * `forget()` clears the persisted value from browser storage. It does
31
+ * **not** delete server-side score history for this player — to fully
32
+ * erase a player's data, call the admin "delete player" endpoint.
33
+ */
34
+ interface PlayerHandle {
35
+ readonly id: string;
36
+ readonly forget: () => void;
37
+ }
38
+ /**
39
+ * Marker returned by `useServerAuthoritative()` to signal that the
40
+ * game's backend (not the browser) owns the `playerId` via the
41
+ * HMAC-signed secure path (`scorezilla/server`).
42
+ */
43
+ interface ServerAuthoritativeMarker {
44
+ readonly source: 'server-authoritative';
45
+ }
46
+ /**
47
+ * Anonymous player identity. Generates an opaque UUID on first run and
48
+ * persists it in `localStorage` so the same browser keeps the same ID
49
+ * across page reloads.
50
+ *
51
+ * **Privacy.** Stores a randomly-generated UUID in browser localStorage;
52
+ * the value is sent to the API on every score submission and persisted
53
+ * indefinitely in the player's score-history rows. No PII is collected.
54
+ * `forget()` removes the localStorage entry; for full server-side erasure
55
+ * call the admin "delete player" endpoint.
56
+ *
57
+ * @example
58
+ * ```ts
59
+ * import { Scorezilla } from 'scorezilla';
60
+ * import { useAnonymousPlayer } from 'scorezilla/identity';
61
+ *
62
+ * const player = useAnonymousPlayer({ storageKey: 'mygame:player' });
63
+ * const sz = new Scorezilla({ publicKey: 'pk_…' });
64
+ * await sz.submitScore({ boardId, playerId: player.id, score: 42 });
65
+ * ```
66
+ *
67
+ * @since 0.3.0
68
+ * @stability stable
69
+ */
70
+ declare function useAnonymousPlayer(options: AnonymousPlayerOptions): PlayerHandle;
71
+ /**
72
+ * Prompted player identity. On first run shows a `window.prompt()` asking
73
+ * the user for a name, then persists it in `localStorage` for subsequent
74
+ * visits. Returns `null` if there is no browser (SSR), no `window.prompt`,
75
+ * or if the user cancelled / entered an empty value.
76
+ *
77
+ * **Privacy.** The user-entered string is stored in browser localStorage,
78
+ * transmitted to the API on every score submission, and persisted
79
+ * indefinitely on the leaderboard. The persisted value is whatever the
80
+ * user typed — sanitize at the UI layer if you care. `forget()` clears
81
+ * local state but does NOT delete server-side history.
82
+ *
83
+ * **UX caveat.** `window.prompt()` blocks the main thread and looks
84
+ * dated in modern apps. For a polished flow, build your own inline form
85
+ * and pass the result to `submitScore` directly — the preset is here to
86
+ * cover quick prototypes and jam-style integrations.
87
+ *
88
+ * @example
89
+ * ```ts
90
+ * import { Scorezilla } from 'scorezilla';
91
+ * import { usePromptedPlayer } from 'scorezilla/identity';
92
+ *
93
+ * const player = usePromptedPlayer({
94
+ * storageKey: 'mygame:player',
95
+ * prompt: 'Enter a name for the leaderboard:',
96
+ * });
97
+ *
98
+ * if (player) {
99
+ * const sz = new Scorezilla({ publicKey: 'pk_…' });
100
+ * await sz.submitScore({ boardId, playerId: player.id, score: 42 });
101
+ * }
102
+ * ```
103
+ *
104
+ * @since 0.3.0
105
+ * @stability stable
106
+ */
107
+ declare function usePromptedPlayer(options: PromptedPlayerOptions): PlayerHandle | null;
108
+ /**
109
+ * Server-authoritative identity marker. Signals that the game's backend
110
+ * is responsible for the `playerId` via the HMAC-signed secure path
111
+ * (`scorezilla/server`). The browser SDK does no identity work — the
112
+ * server picks the value, signs the submission, and posts.
113
+ *
114
+ * The return value is a no-op marker; you don't pass it anywhere. It
115
+ * exists so MCP-returned snippets can emit a single line that
116
+ * unambiguously says "this game uses the secure path; identity is
117
+ * server-authoritative."
118
+ *
119
+ * @example
120
+ * ```ts
121
+ * // Client (no identity helper needed):
122
+ * import { useServerAuthoritative } from 'scorezilla/identity';
123
+ * useServerAuthoritative();
124
+ *
125
+ * // Server (where the real work happens):
126
+ * import { Scorezilla } from 'scorezilla/server';
127
+ * const sz = new Scorezilla({ secretKey: process.env.SCOREZILLA_SECRET_KEY! });
128
+ * await sz.submitScore({ boardId, playerId: serverDerivedId, score });
129
+ * ```
130
+ *
131
+ * @since 0.3.0
132
+ * @stability stable
133
+ */
134
+ declare function useServerAuthoritative(): ServerAuthoritativeMarker;
135
+ /**
136
+ * OAuth-backed player identity. **Preview stub in 0.3.0-next.x** — throws
137
+ * on call. Full implementation (Google + GitHub for v1, Apple + Discord
138
+ * deferred) lands in a follow-up release on the `next` dist-tag, before
139
+ * the `latest` 0.3.0 ships.
140
+ *
141
+ * Until then: drive your own OAuth flow and pass the resulting user
142
+ * identifier to `submitScore` directly.
143
+ *
144
+ * @since 0.3.0
145
+ * @stability preview
146
+ */
147
+ declare function useAuthProvider(_options: {
148
+ readonly provider: 'google' | 'github';
149
+ }): never;
150
+
151
+ export { type AnonymousPlayerOptions, type PlayerHandle, type PromptedPlayerOptions, type ServerAuthoritativeMarker, useAnonymousPlayer, useAuthProvider, usePromptedPlayer, useServerAuthoritative };
@@ -0,0 +1,151 @@
1
+ /**
2
+ * Identity preset helpers — opinionated, selectable ways for your game to
3
+ * generate or fetch a `playerId` for score submission.
4
+ *
5
+ * Background: every Scorezilla score carries an opaque `playerId`. The SDK
6
+ * doesn't care whether it's a UUID, a nickname, an email, or a server
7
+ * session token. But _how_ your game decides on that value is a UX +
8
+ * privacy decision the team should make explicitly. These presets are the
9
+ * blessed patterns; pick one per integration.
10
+ *
11
+ * See ADR 0003 (MCP identity axis) for the design rationale:
12
+ * https://github.com/isco-tec/scorezilla/blob/main/docs/adr/0003-mcp-identity-axis.md
13
+ *
14
+ * @module scorezilla/identity
15
+ * @since 0.3.0
16
+ */
17
+ interface AnonymousPlayerOptions {
18
+ /** localStorage key under which the generated UUID is persisted. */
19
+ readonly storageKey: string;
20
+ }
21
+ interface PromptedPlayerOptions {
22
+ /** localStorage key under which the user-entered name is persisted. */
23
+ readonly storageKey: string;
24
+ /** Message shown in `window.prompt()` on first run. */
25
+ readonly prompt: string;
26
+ }
27
+ /**
28
+ * Identity handle returned by the storage-backed presets.
29
+ *
30
+ * `forget()` clears the persisted value from browser storage. It does
31
+ * **not** delete server-side score history for this player — to fully
32
+ * erase a player's data, call the admin "delete player" endpoint.
33
+ */
34
+ interface PlayerHandle {
35
+ readonly id: string;
36
+ readonly forget: () => void;
37
+ }
38
+ /**
39
+ * Marker returned by `useServerAuthoritative()` to signal that the
40
+ * game's backend (not the browser) owns the `playerId` via the
41
+ * HMAC-signed secure path (`scorezilla/server`).
42
+ */
43
+ interface ServerAuthoritativeMarker {
44
+ readonly source: 'server-authoritative';
45
+ }
46
+ /**
47
+ * Anonymous player identity. Generates an opaque UUID on first run and
48
+ * persists it in `localStorage` so the same browser keeps the same ID
49
+ * across page reloads.
50
+ *
51
+ * **Privacy.** Stores a randomly-generated UUID in browser localStorage;
52
+ * the value is sent to the API on every score submission and persisted
53
+ * indefinitely in the player's score-history rows. No PII is collected.
54
+ * `forget()` removes the localStorage entry; for full server-side erasure
55
+ * call the admin "delete player" endpoint.
56
+ *
57
+ * @example
58
+ * ```ts
59
+ * import { Scorezilla } from 'scorezilla';
60
+ * import { useAnonymousPlayer } from 'scorezilla/identity';
61
+ *
62
+ * const player = useAnonymousPlayer({ storageKey: 'mygame:player' });
63
+ * const sz = new Scorezilla({ publicKey: 'pk_…' });
64
+ * await sz.submitScore({ boardId, playerId: player.id, score: 42 });
65
+ * ```
66
+ *
67
+ * @since 0.3.0
68
+ * @stability stable
69
+ */
70
+ declare function useAnonymousPlayer(options: AnonymousPlayerOptions): PlayerHandle;
71
+ /**
72
+ * Prompted player identity. On first run shows a `window.prompt()` asking
73
+ * the user for a name, then persists it in `localStorage` for subsequent
74
+ * visits. Returns `null` if there is no browser (SSR), no `window.prompt`,
75
+ * or if the user cancelled / entered an empty value.
76
+ *
77
+ * **Privacy.** The user-entered string is stored in browser localStorage,
78
+ * transmitted to the API on every score submission, and persisted
79
+ * indefinitely on the leaderboard. The persisted value is whatever the
80
+ * user typed — sanitize at the UI layer if you care. `forget()` clears
81
+ * local state but does NOT delete server-side history.
82
+ *
83
+ * **UX caveat.** `window.prompt()` blocks the main thread and looks
84
+ * dated in modern apps. For a polished flow, build your own inline form
85
+ * and pass the result to `submitScore` directly — the preset is here to
86
+ * cover quick prototypes and jam-style integrations.
87
+ *
88
+ * @example
89
+ * ```ts
90
+ * import { Scorezilla } from 'scorezilla';
91
+ * import { usePromptedPlayer } from 'scorezilla/identity';
92
+ *
93
+ * const player = usePromptedPlayer({
94
+ * storageKey: 'mygame:player',
95
+ * prompt: 'Enter a name for the leaderboard:',
96
+ * });
97
+ *
98
+ * if (player) {
99
+ * const sz = new Scorezilla({ publicKey: 'pk_…' });
100
+ * await sz.submitScore({ boardId, playerId: player.id, score: 42 });
101
+ * }
102
+ * ```
103
+ *
104
+ * @since 0.3.0
105
+ * @stability stable
106
+ */
107
+ declare function usePromptedPlayer(options: PromptedPlayerOptions): PlayerHandle | null;
108
+ /**
109
+ * Server-authoritative identity marker. Signals that the game's backend
110
+ * is responsible for the `playerId` via the HMAC-signed secure path
111
+ * (`scorezilla/server`). The browser SDK does no identity work — the
112
+ * server picks the value, signs the submission, and posts.
113
+ *
114
+ * The return value is a no-op marker; you don't pass it anywhere. It
115
+ * exists so MCP-returned snippets can emit a single line that
116
+ * unambiguously says "this game uses the secure path; identity is
117
+ * server-authoritative."
118
+ *
119
+ * @example
120
+ * ```ts
121
+ * // Client (no identity helper needed):
122
+ * import { useServerAuthoritative } from 'scorezilla/identity';
123
+ * useServerAuthoritative();
124
+ *
125
+ * // Server (where the real work happens):
126
+ * import { Scorezilla } from 'scorezilla/server';
127
+ * const sz = new Scorezilla({ secretKey: process.env.SCOREZILLA_SECRET_KEY! });
128
+ * await sz.submitScore({ boardId, playerId: serverDerivedId, score });
129
+ * ```
130
+ *
131
+ * @since 0.3.0
132
+ * @stability stable
133
+ */
134
+ declare function useServerAuthoritative(): ServerAuthoritativeMarker;
135
+ /**
136
+ * OAuth-backed player identity. **Preview stub in 0.3.0-next.x** — throws
137
+ * on call. Full implementation (Google + GitHub for v1, Apple + Discord
138
+ * deferred) lands in a follow-up release on the `next` dist-tag, before
139
+ * the `latest` 0.3.0 ships.
140
+ *
141
+ * Until then: drive your own OAuth flow and pass the resulting user
142
+ * identifier to `submitScore` directly.
143
+ *
144
+ * @since 0.3.0
145
+ * @stability preview
146
+ */
147
+ declare function useAuthProvider(_options: {
148
+ readonly provider: 'google' | 'github';
149
+ }): never;
150
+
151
+ export { type AnonymousPlayerOptions, type PlayerHandle, type PromptedPlayerOptions, type ServerAuthoritativeMarker, useAnonymousPlayer, useAuthProvider, usePromptedPlayer, useServerAuthoritative };
@@ -0,0 +1,82 @@
1
+ // src/identity.ts
2
+ var isBrowser = () => typeof window !== "undefined";
3
+ function readPersisted(key) {
4
+ if (!isBrowser()) return null;
5
+ try {
6
+ return window.localStorage.getItem(key);
7
+ } catch {
8
+ return null;
9
+ }
10
+ }
11
+ function writePersisted(key, value) {
12
+ if (!isBrowser()) return;
13
+ try {
14
+ window.localStorage.setItem(key, value);
15
+ } catch {
16
+ }
17
+ }
18
+ function removePersisted(key) {
19
+ if (!isBrowser()) return;
20
+ try {
21
+ window.localStorage.removeItem(key);
22
+ } catch {
23
+ }
24
+ }
25
+ function mintUuid() {
26
+ if (isBrowser() && typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
27
+ return crypto.randomUUID();
28
+ }
29
+ return `anon-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;
30
+ }
31
+ function requireStorageKey(fnName, options) {
32
+ if (!options || typeof options.storageKey !== "string" || options.storageKey.length === 0) {
33
+ throw new TypeError(`${fnName}: options.storageKey is required (non-empty string)`);
34
+ }
35
+ return options.storageKey;
36
+ }
37
+ function useAnonymousPlayer(options) {
38
+ const storageKey = requireStorageKey("useAnonymousPlayer", options);
39
+ let id = readPersisted(storageKey);
40
+ if (id === null || id.length === 0) {
41
+ id = mintUuid();
42
+ writePersisted(storageKey, id);
43
+ }
44
+ return {
45
+ id,
46
+ forget: () => removePersisted(storageKey)
47
+ };
48
+ }
49
+ function usePromptedPlayer(options) {
50
+ const storageKey = requireStorageKey("usePromptedPlayer", options);
51
+ if (typeof options.prompt !== "string" || options.prompt.length === 0) {
52
+ throw new TypeError("usePromptedPlayer: options.prompt is required (non-empty string)");
53
+ }
54
+ let id = readPersisted(storageKey);
55
+ if (id === null || id.length === 0) {
56
+ if (!isBrowser() || typeof window.prompt !== "function") {
57
+ return null;
58
+ }
59
+ const entered = window.prompt(options.prompt);
60
+ if (entered === null || entered.length === 0) {
61
+ return null;
62
+ }
63
+ id = entered;
64
+ writePersisted(storageKey, id);
65
+ }
66
+ return {
67
+ id,
68
+ forget: () => removePersisted(storageKey)
69
+ };
70
+ }
71
+ function useServerAuthoritative() {
72
+ return { source: "server-authoritative" };
73
+ }
74
+ function useAuthProvider(_options) {
75
+ throw new Error(
76
+ "useAuthProvider is not yet implemented in this 0.3.0-next preview. OAuth provider helpers (Google + GitHub for v1) ship in a follow-up release on the `next` dist-tag. Until then, drive your own OAuth flow and pass the resulting user identifier to submitScore directly."
77
+ );
78
+ }
79
+
80
+ export { useAnonymousPlayer, useAuthProvider, usePromptedPlayer, useServerAuthoritative };
81
+ //# sourceMappingURL=identity.js.map
82
+ //# sourceMappingURL=identity.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/identity.ts"],"names":[],"mappings":"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","file":"identity.js","sourcesContent":["/**\n * Identity preset helpers — opinionated, selectable ways for your game to\n * generate or fetch a `playerId` for score submission.\n *\n * Background: every Scorezilla score carries an opaque `playerId`. The SDK\n * doesn't care whether it's a UUID, a nickname, an email, or a server\n * session token. But _how_ your game decides on that value is a UX +\n * privacy decision the team should make explicitly. These presets are the\n * blessed patterns; pick one per integration.\n *\n * See ADR 0003 (MCP identity axis) for the design rationale:\n * https://github.com/isco-tec/scorezilla/blob/main/docs/adr/0003-mcp-identity-axis.md\n *\n * @module scorezilla/identity\n * @since 0.3.0\n */\n\nexport interface AnonymousPlayerOptions {\n /** localStorage key under which the generated UUID is persisted. */\n readonly storageKey: string;\n}\n\nexport interface PromptedPlayerOptions {\n /** localStorage key under which the user-entered name is persisted. */\n readonly storageKey: string;\n /** Message shown in `window.prompt()` on first run. */\n readonly prompt: string;\n}\n\n/**\n * Identity handle returned by the storage-backed presets.\n *\n * `forget()` clears the persisted value from browser storage. It does\n * **not** delete server-side score history for this player — to fully\n * erase a player's data, call the admin \"delete player\" endpoint.\n */\nexport interface PlayerHandle {\n readonly id: string;\n readonly forget: () => void;\n}\n\n/**\n * Marker returned by `useServerAuthoritative()` to signal that the\n * game's backend (not the browser) owns the `playerId` via the\n * HMAC-signed secure path (`scorezilla/server`).\n */\nexport interface ServerAuthoritativeMarker {\n readonly source: 'server-authoritative';\n}\n\nconst isBrowser = (): boolean => typeof window !== 'undefined';\n\nfunction readPersisted(key: string): string | null {\n if (!isBrowser()) return null;\n try {\n return window.localStorage.getItem(key);\n } catch {\n // Storage may throw in sandboxed iframes, privacy mode, or when the\n // user has disabled site data. Treat as \"missing\"; the caller will\n // mint or re-prompt.\n return null;\n }\n}\n\nfunction writePersisted(key: string, value: string): void {\n if (!isBrowser()) return;\n try {\n window.localStorage.setItem(key, value);\n } catch {\n // ignore; next call will re-mint or re-prompt\n }\n}\n\nfunction removePersisted(key: string): void {\n if (!isBrowser()) return;\n try {\n window.localStorage.removeItem(key);\n } catch {\n // ignore\n }\n}\n\nfunction mintUuid(): string {\n if (isBrowser() && typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {\n return crypto.randomUUID();\n }\n // Best-effort fallback: timestamp + random suffix. Not cryptographically\n // strong, but opaque enough for the identifier-only use case. The\n // browsers we target (Chrome 92+, Firefox 95+, Safari 15.4+) all have\n // crypto.randomUUID — this branch is reached only in non-browser\n // environments where useAnonymousPlayer shouldn't be called anyway.\n return `anon-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;\n}\n\nfunction requireStorageKey(fnName: string, options: { storageKey?: unknown } | undefined): string {\n if (!options || typeof options.storageKey !== 'string' || options.storageKey.length === 0) {\n throw new TypeError(`${fnName}: options.storageKey is required (non-empty string)`);\n }\n return options.storageKey;\n}\n\n/**\n * Anonymous player identity. Generates an opaque UUID on first run and\n * persists it in `localStorage` so the same browser keeps the same ID\n * across page reloads.\n *\n * **Privacy.** Stores a randomly-generated UUID in browser localStorage;\n * the value is sent to the API on every score submission and persisted\n * indefinitely in the player's score-history rows. No PII is collected.\n * `forget()` removes the localStorage entry; for full server-side erasure\n * call the admin \"delete player\" endpoint.\n *\n * @example\n * ```ts\n * import { Scorezilla } from 'scorezilla';\n * import { useAnonymousPlayer } from 'scorezilla/identity';\n *\n * const player = useAnonymousPlayer({ storageKey: 'mygame:player' });\n * const sz = new Scorezilla({ publicKey: 'pk_…' });\n * await sz.submitScore({ boardId, playerId: player.id, score: 42 });\n * ```\n *\n * @since 0.3.0\n * @stability stable\n */\nexport function useAnonymousPlayer(options: AnonymousPlayerOptions): PlayerHandle {\n const storageKey = requireStorageKey('useAnonymousPlayer', options);\n let id = readPersisted(storageKey);\n if (id === null || id.length === 0) {\n id = mintUuid();\n writePersisted(storageKey, id);\n }\n return {\n id,\n forget: () => removePersisted(storageKey),\n };\n}\n\n/**\n * Prompted player identity. On first run shows a `window.prompt()` asking\n * the user for a name, then persists it in `localStorage` for subsequent\n * visits. Returns `null` if there is no browser (SSR), no `window.prompt`,\n * or if the user cancelled / entered an empty value.\n *\n * **Privacy.** The user-entered string is stored in browser localStorage,\n * transmitted to the API on every score submission, and persisted\n * indefinitely on the leaderboard. The persisted value is whatever the\n * user typed — sanitize at the UI layer if you care. `forget()` clears\n * local state but does NOT delete server-side history.\n *\n * **UX caveat.** `window.prompt()` blocks the main thread and looks\n * dated in modern apps. For a polished flow, build your own inline form\n * and pass the result to `submitScore` directly — the preset is here to\n * cover quick prototypes and jam-style integrations.\n *\n * @example\n * ```ts\n * import { Scorezilla } from 'scorezilla';\n * import { usePromptedPlayer } from 'scorezilla/identity';\n *\n * const player = usePromptedPlayer({\n * storageKey: 'mygame:player',\n * prompt: 'Enter a name for the leaderboard:',\n * });\n *\n * if (player) {\n * const sz = new Scorezilla({ publicKey: 'pk_…' });\n * await sz.submitScore({ boardId, playerId: player.id, score: 42 });\n * }\n * ```\n *\n * @since 0.3.0\n * @stability stable\n */\nexport function usePromptedPlayer(options: PromptedPlayerOptions): PlayerHandle | null {\n const storageKey = requireStorageKey('usePromptedPlayer', options);\n if (typeof options.prompt !== 'string' || options.prompt.length === 0) {\n throw new TypeError('usePromptedPlayer: options.prompt is required (non-empty string)');\n }\n\n let id = readPersisted(storageKey);\n if (id === null || id.length === 0) {\n if (!isBrowser() || typeof window.prompt !== 'function') {\n return null;\n }\n const entered = window.prompt(options.prompt);\n if (entered === null || entered.length === 0) {\n return null;\n }\n id = entered;\n writePersisted(storageKey, id);\n }\n return {\n id,\n forget: () => removePersisted(storageKey),\n };\n}\n\n/**\n * Server-authoritative identity marker. Signals that the game's backend\n * is responsible for the `playerId` via the HMAC-signed secure path\n * (`scorezilla/server`). The browser SDK does no identity work — the\n * server picks the value, signs the submission, and posts.\n *\n * The return value is a no-op marker; you don't pass it anywhere. It\n * exists so MCP-returned snippets can emit a single line that\n * unambiguously says \"this game uses the secure path; identity is\n * server-authoritative.\"\n *\n * @example\n * ```ts\n * // Client (no identity helper needed):\n * import { useServerAuthoritative } from 'scorezilla/identity';\n * useServerAuthoritative();\n *\n * // Server (where the real work happens):\n * import { Scorezilla } from 'scorezilla/server';\n * const sz = new Scorezilla({ secretKey: process.env.SCOREZILLA_SECRET_KEY! });\n * await sz.submitScore({ boardId, playerId: serverDerivedId, score });\n * ```\n *\n * @since 0.3.0\n * @stability stable\n */\nexport function useServerAuthoritative(): ServerAuthoritativeMarker {\n return { source: 'server-authoritative' };\n}\n\n/**\n * OAuth-backed player identity. **Preview stub in 0.3.0-next.x** — throws\n * on call. Full implementation (Google + GitHub for v1, Apple + Discord\n * deferred) lands in a follow-up release on the `next` dist-tag, before\n * the `latest` 0.3.0 ships.\n *\n * Until then: drive your own OAuth flow and pass the resulting user\n * identifier to `submitScore` directly.\n *\n * @since 0.3.0\n * @stability preview\n */\nexport function useAuthProvider(_options: { readonly provider: 'google' | 'github' }): never {\n throw new Error(\n 'useAuthProvider is not yet implemented in this 0.3.0-next preview. ' +\n 'OAuth provider helpers (Google + GitHub for v1) ship in a follow-up ' +\n 'release on the `next` dist-tag. Until then, drive your own OAuth flow ' +\n 'and pass the resulting user identifier to submitScore directly.',\n );\n}\n"]}
package/dist/index.cjs CHANGED
@@ -633,7 +633,7 @@ function validateMetadata(metadata) {
633
633
  }
634
634
  var Scorezilla = class _Scorezilla {
635
635
  /** The package version, injected at build time from `package.json`. */
636
- static version = "0.2.0";
636
+ static version = "0.3.0-next.0";
637
637
  #config;
638
638
  #userAgent;
639
639
  #authHeader;
@@ -818,7 +818,7 @@ function createClient(config) {
818
818
  }
819
819
 
820
820
  // src/index.ts
821
- var SDK_VERSION = "0.2.0";
821
+ var SDK_VERSION = "0.3.0-next.0";
822
822
 
823
823
  exports.SDK_VERSION = SDK_VERSION;
824
824
  exports.Scorezilla = Scorezilla;