@couch-kit/client 0.5.1 → 0.6.1

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,42 @@
1
1
  # @couch-kit/client
2
2
 
3
+ ## 0.6.1
4
+
5
+ ### Patch Changes
6
+
7
+ - [#9](https://github.com/faluciano/react-native-couch-kit/pull/9) [`db8bbc9`](https://github.com/faluciano/react-native-couch-kit/commit/db8bbc95c3a3334c97da3e000b2c28b9c2959fb1) Thanks [@faluciano](https://github.com/faluciano)! - Add publishConfig with public access and provenance attestation to all packages. Add missing prepublishOnly build script to CLI package.
8
+
9
+ - Updated dependencies [[`db8bbc9`](https://github.com/faluciano/react-native-couch-kit/commit/db8bbc95c3a3334c97da3e000b2c28b9c2959fb1)]:
10
+ - @couch-kit/core@0.5.1
11
+
12
+ ## 0.6.0
13
+
14
+ ### Minor Changes
15
+
16
+ - Fix player state not restored on page refresh
17
+
18
+ Players now maintain their identity across page refreshes and network reconnections. The library uses a stable player ID derived from a persistent session secret (stored in localStorage) instead of the ephemeral WebSocket socket ID.
19
+
20
+ **New features:**
21
+ - `__PLAYER_RECONNECTED__` internal action: dispatched when a returning player reconnects, preserving all game data (hand, score, turn, etc.)
22
+ - `__PLAYER_REMOVED__` internal action: dispatched when a disconnected player times out (default: 5 minutes), permanently removing them from state
23
+ - Race-safe disconnect handling: prevents marking a player as disconnected if they've already reconnected on a new socket
24
+ - Secret validation: malformed session secrets are rejected at the JOIN boundary
25
+
26
+ **Breaking changes:**
27
+ - `state.players` keys are now stable derived player IDs instead of ephemeral socket IDs
28
+ - `playerId` returned by `useGameClient()` and sent in `WELCOME` messages is now a stable identifier that persists across reconnections
29
+ - `secret` field in `JOIN` payload is now required (was optional)
30
+
31
+ **Security:**
32
+ - Raw session secret is never stored on `IPlayer` or broadcast to clients
33
+ - Only the derived public player ID is shared in game state
34
+
35
+ ### Patch Changes
36
+
37
+ - Updated dependencies
38
+ - @couch-kit/core@0.5.0
39
+
3
40
  ## 0.5.1
4
41
 
5
42
  ### Patch Changes
package/README.md CHANGED
@@ -28,6 +28,7 @@ Config:
28
28
  - `reducer`: `(state, action) => state` (your shared reducer)
29
29
  - `initialState`: initial state used until the host hydrates
30
30
  - `url?`: explicit WebSocket URL. If omitted, the hook uses `ws(s)://{window.location.hostname}:8082`.
31
+ - `wsPort?`: WebSocket port override (default: auto-detected from page URL using HTTP port + 2)
31
32
  - `name?`: player display name (default: `"Player"`)
32
33
  - `avatar?`: player avatar emoji (default: `"\u{1F600}"`)
33
34
  - `maxRetries?`: maximum reconnection attempts before giving up (default: `5`)
@@ -40,7 +41,7 @@ Returns:
40
41
 
41
42
  - `status`: `'connecting' | 'connected' | 'disconnected' | 'error'`
42
43
  - `state`: current controller state (optimistic + hydrated)
43
- - `playerId`: server-assigned player id (after `WELCOME`)
44
+ - `playerId`: stable public identifier derived from the session secret. Persists across page refreshes and reconnections (the same player always gets the same `playerId`).
44
45
  - `sendAction(action)`: optimistic dispatch + send to host
45
46
  - `getServerTime()`: NTP-ish server time based on periodic ping/pong
46
47
  - `rtt`: round-trip time (ms) to the server, updated periodically via PING/PONG
@@ -94,7 +95,7 @@ export default function Controller() {
94
95
  const {
95
96
  status, // 'connecting' | 'connected' | 'disconnected' | 'error'
96
97
  state, // The current game state (synced with Host)
97
- playerId, // Your unique session ID
98
+ playerId, // Stable identifier, persists across reconnections
98
99
  sendAction, // Function to send actions to Host
99
100
  } = useGameClient({
100
101
  reducer: gameReducer,
package/dist/index.js CHANGED
@@ -156,6 +156,13 @@ function useGameClient(config) {
156
156
  case MessageTypes2.PONG:
157
157
  handlePongRef.current(msg.payload);
158
158
  break;
159
+ case MessageTypes2.RECONNECTED:
160
+ setPlayerId(msg.payload.playerId);
161
+ dispatchLocal({
162
+ type: InternalActionTypes.HYDRATE,
163
+ payload: msg.payload.state
164
+ });
165
+ break;
159
166
  }
160
167
  } catch (e) {
161
168
  console.error("Failed to parse message", e);
@@ -1 +1 @@
1
- {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AACA,OAAO,EAUL,KAAK,UAAU,EACf,KAAK,OAAO,EAEb,MAAM,iBAAiB,CAAC;AAGzB,MAAM,WAAW,YAAY,CAAC,CAAC,SAAS,UAAU,EAAE,CAAC,SAAS,OAAO;IACnE,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,CAAC,KAAK,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,KAAK,CAAC,CAAC;IACpC,YAAY,EAAE,CAAC,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,mEAAmE;IACnE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,4EAA4E;IAC5E,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,wEAAwE;IACxE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,IAAI,CAAC;IACvB,YAAY,CAAC,EAAE,MAAM,IAAI,CAAC;IAC1B,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,aAAa,CAAC,CAAC,SAAS,UAAU,EAAE,CAAC,SAAS,OAAO,EACnE,MAAM,EAAE,YAAY,CAAC,CAAC,EAAE,CAAC,CAAC;;;;yBA8Mc,CAAC;;IAqBvC,8EAA8E;;IAE9E,0EAA0E;;IAE1E,+EAA+E;;EAGlF"}
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AACA,OAAO,EAUL,KAAK,UAAU,EACf,KAAK,OAAO,EAEb,MAAM,iBAAiB,CAAC;AAGzB,MAAM,WAAW,YAAY,CAAC,CAAC,SAAS,UAAU,EAAE,CAAC,SAAS,OAAO;IACnE,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,CAAC,KAAK,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,KAAK,CAAC,CAAC;IACpC,YAAY,EAAE,CAAC,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,mEAAmE;IACnE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,4EAA4E;IAC5E,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,wEAAwE;IACxE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,IAAI,CAAC;IACvB,YAAY,CAAC,EAAE,MAAM,IAAI,CAAC;IAC1B,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,aAAa,CAAC,CAAC,SAAS,UAAU,EAAE,CAAC,SAAS,OAAO,EACnE,MAAM,EAAE,YAAY,CAAC,CAAC,EAAE,CAAC,CAAC;;;;yBAsNc,CAAC;;IAqBvC,8EAA8E;;IAE9E,0EAA0E;;IAE1E,+EAA+E;;EAGlF"}
package/package.json CHANGED
@@ -1,6 +1,10 @@
1
1
  {
2
2
  "name": "@couch-kit/client",
3
- "version": "0.5.1",
3
+ "version": "0.6.1",
4
+ "publishConfig": {
5
+ "access": "public",
6
+ "provenance": true
7
+ },
4
8
  "description": "React hooks for phone controllers in Couch Kit party games — WebSocket client and action dispatch",
5
9
  "license": "MIT",
6
10
  "repository": {
@@ -43,7 +47,7 @@
43
47
  "clean": "rm -rf dist lib"
44
48
  },
45
49
  "dependencies": {
46
- "@couch-kit/core": "0.4.0"
50
+ "@couch-kit/core": "0.5.1"
47
51
  },
48
52
  "devDependencies": {
49
53
  "react": "^18.2.0",
package/src/client.ts CHANGED
@@ -179,6 +179,14 @@ export function useGameClient<S extends IGameState, A extends IAction>(
179
179
  case MessageTypes.PONG:
180
180
  handlePongRef.current(msg.payload);
181
181
  break;
182
+
183
+ case MessageTypes.RECONNECTED:
184
+ setPlayerId(msg.payload.playerId);
185
+ dispatchLocal({
186
+ type: InternalActionTypes.HYDRATE,
187
+ payload: msg.payload.state as S,
188
+ } as InternalAction<S>);
189
+ break;
182
190
  }
183
191
  } catch (e) {
184
192
  console.error("Failed to parse message", e);