@couch-kit/client 0.11.0 → 0.13.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,56 @@
1
1
  # @couch-kit/client
2
2
 
3
+ ## 0.13.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#158](https://github.com/faluciano/react-native-couch-kit/pull/158) [`509ea7c`](https://github.com/faluciano/react-native-couch-kit/commit/509ea7c02aa6f2e56ebf01e781d6de74f0ded021) Thanks [@faluciano](https://github.com/faluciano)! - Let the relay assign room codes
8
+
9
+ `RelayDisplayHost`'s `roomId` is now optional. Omit it and the relay mints an
10
+ unused six-character code, reported through the new `onRoomCode` callback and
11
+ the `roomCode` getter.
12
+
13
+ A display could never check its own code for collisions — only the relay knows
14
+ which codes are live — so a self-chosen code could land on a game already in
15
+ progress, and did so only after it was on screen. Minted codes are drawn from
16
+ the CSPRNG over a 32-character alphabet without `O`/`0` or `I`/`1`, giving about
17
+ 1.07 billion codes.
18
+
19
+ Existing callers that pass `roomId` keep their current behaviour, including
20
+ `ROOM_EXISTS` when the code is taken. New callers should expect the code to
21
+ arrive one round trip after connecting rather than being known up front:
22
+
23
+ ```ts
24
+ const [roomCode, setRoomCode] = useState<string | null>(null);
25
+ new RelayDisplayHost({ url, onRoomCode: setRoomCode, reducer, initialState });
26
+ ```
27
+
28
+ Relays need a matching update to mint: both bundled implementations
29
+ (`services/relay`, `services/relay-worker`) support it. A display that omits
30
+ `roomId` against an older relay gets `MALFORMED`.
31
+
32
+ ## 0.12.0
33
+
34
+ ### Minor Changes
35
+
36
+ - [#155](https://github.com/faluciano/react-native-couch-kit/pull/155) [`4f297a1`](https://github.com/faluciano/react-native-couch-kit/commit/4f297a19c442541703c3bee7bee26354ae3476a4) Thanks [@faluciano](https://github.com/faluciano)! - Hidden information can now be hidden for real: `GameHostRuntimeConfig` takes an
37
+ optional `project(state, playerId)` that narrows the authoritative state to what
38
+ one player may see.
39
+
40
+ Without it the runtime broadcasts the same state to everyone, so hiding a hand
41
+ depends on the client choosing not to look — any player could read opponents'
42
+ cards from devtools. With it, the data never reaches their device: the runtime
43
+ sends each connection its own projection, in `WELCOME`, `RECONNECTED`, and every
44
+ state update.
45
+
46
+ Games with no hidden information omit `project` and are unchanged — state is
47
+ still broadcast in one frame.
48
+
49
+ Because a projected client holds a _view_ rather than the whole state, it cannot
50
+ run the game reducer, so `ClientConfig.reducer` is now optional. Omit it and the
51
+ client renders what the host sends (no optimistic updates); a round trip is
52
+ imperceptible for turn-based games, and it is what makes the guarantee real.
53
+
3
54
  ## 0.11.0
4
55
 
5
56
  ### Minor Changes
package/dist/index.js CHANGED
@@ -168,7 +168,7 @@ function useGameClient(config) {
168
168
  const [status, setStatus] = useState2("disconnected");
169
169
  const [playerId, setPlayerId] = useState2(null);
170
170
  const [disconnectReason, setDisconnectReason] = useState2(null);
171
- const [state, dispatchLocal] = useReducer(createGameReducer(config.reducer), config.initialState);
171
+ const [state, dispatchLocal] = useReducer(createGameReducer(config.reducer ?? ((current) => current)), config.initialState);
172
172
  const socketRef = useRef2(null);
173
173
  const reconnectAttempts = useRef2(0);
174
174
  const reconnectTimer = useRef2(null);
@@ -345,9 +345,10 @@ var RelayErrorCodes = {
345
345
  function relayRoomUrl(url, roomId) {
346
346
  const trimmed = url.replace(/\/+$/, "");
347
347
  const [base, query] = trimmed.split("?", 2);
348
- const path = `${base}/r/${encodeURIComponent(roomId)}`;
348
+ const path = roomId === undefined ? `${base}${RELAY_MINT_PATH}` : `${base}/r/${encodeURIComponent(roomId)}`;
349
349
  return query ? `${path}?${query}` : path;
350
350
  }
351
+ var RELAY_MINT_PATH = "/new";
351
352
  // src/relay-transport.ts
352
353
  var POLICY_CLOSE_CODE = 1008;
353
354
 
@@ -647,5 +648,6 @@ export {
647
648
  SESSION_SECRET_KEY,
648
649
  RelayMessageTypes,
649
650
  RelayErrorCodes,
650
- RelayClientTransport
651
+ RelayClientTransport,
652
+ RELAY_MINT_PATH
651
653
  };
package/lib/client.d.ts CHANGED
@@ -3,7 +3,17 @@ import { type CreateClientTransport } from "./transport";
3
3
  export interface ClientConfig<S extends IGameState, A extends IAction> {
4
4
  url?: string;
5
5
  wsPort?: number;
6
- reducer: (state: S, action: A) => S;
6
+ /**
7
+ * Applies actions locally for an optimistic update before the host confirms.
8
+ *
9
+ * **Omit it when the host projects state per player** (`project` in
10
+ * `GameHostRuntimeConfig`): the client then holds a *view* rather than the
11
+ * whole state, and the game reducer cannot run over a partial view. Without a
12
+ * reducer the client simply renders what the host sends — a round trip that
13
+ * is imperceptible for turn-based games, and the price of hidden information
14
+ * never reaching the device.
15
+ */
16
+ reducer?: (state: S, action: A) => S;
7
17
  initialState: S;
8
18
  name?: string;
9
19
  avatar?: string;
@@ -1 +1 @@
1
- {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AACA,OAAO,EAQL,KAAK,UAAU,EACf,KAAK,OAAO,EAEb,MAAM,iBAAiB,CAAC;AASzB,OAAO,EAIL,KAAK,qBAAqB,EAC3B,MAAM,aAAa,CAAC;AAErB,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;IAChB;;;;;OAKG;IACH,eAAe,CAAC,EAAE,qBAAqB,CAAC;CACzC;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,aAAa,CAAC,CAAC,SAAS,UAAU,EAAE,CAAC,SAAS,OAAO,EACnE,MAAM,EAAE,YAAY,CAAC,CAAC,EAAE,CAAC,CAAC;;;;IAyOxB;;;;OAIG;;yBAvBmC,CAAC;;IA2BvC,8EAA8E;;IAE9E,0EAA0E;;IAE1E,+EAA+E;;EAGlF"}
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AACA,OAAO,EAQL,KAAK,UAAU,EACf,KAAK,OAAO,EAEb,MAAM,iBAAiB,CAAC;AASzB,OAAO,EAIL,KAAK,qBAAqB,EAC3B,MAAM,aAAa,CAAC;AAErB,MAAM,WAAW,YAAY,CAAC,CAAC,SAAS,UAAU,EAAE,CAAC,SAAS,OAAO;IACnE,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;;;;;;OASG;IACH,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,KAAK,CAAC,CAAC;IACrC,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;IAChB;;;;;OAKG;IACH,eAAe,CAAC,EAAE,qBAAqB,CAAC;CACzC;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,aAAa,CAAC,CAAC,SAAS,UAAU,EAAE,CAAC,SAAS,OAAO,EACnE,MAAM,EAAE,YAAY,CAAC,CAAC,EAAE,CAAC,CAAC;;;;IA2OxB;;;;OAIG;;yBAvBmC,CAAC;;IA2BvC,8EAA8E;;IAE9E,0EAA0E;;IAE1E,+EAA+E;;EAGlF"}
@@ -119,8 +119,19 @@ export type RelayMessage = RelayClientMessage | RelayServerMessage;
119
119
  * that keep every room in one process (the Bun reference server) ignore the
120
120
  * path, so this is safe to send to either.
121
121
  *
122
+ * Passing no room code addresses {@link RELAY_MINT_PATH} instead, asking the
123
+ * relay to allocate one; the code comes back in `ROOM_CREATED`.
124
+ *
122
125
  * @param url - Base relay URL, e.g. `wss://relay.example.com`.
123
- * @param roomId - Room code to address.
126
+ * @param roomId - Room code to address, or omitted to have one minted.
127
+ */
128
+ export declare function relayRoomUrl(url: string, roomId?: string): string;
129
+ /**
130
+ * Path that asks the relay to allocate a room code.
131
+ *
132
+ * Reserved, so it can never be mistaken for a room code. Single-process relays
133
+ * ignore the path and mint from the `CREATE_ROOM` message alone; sharded relays
134
+ * need it, because they must choose the shard before any frame arrives.
124
135
  */
125
- export declare function relayRoomUrl(url: string, roomId: string): string;
136
+ export declare const RELAY_MINT_PATH = "/new";
126
137
  //# sourceMappingURL=relay-protocol.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"relay-protocol.d.ts","sourceRoot":"","sources":["../src/relay-protocol.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"relay-protocol.d.ts","sourceRoot":"","sources":["../src/relay-protocol.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@couch-kit/client",
3
- "version": "0.11.0",
3
+ "version": "0.13.0",
4
4
  "publishConfig": {
5
5
  "access": "public",
6
6
  "provenance": true
package/src/client.ts CHANGED
@@ -29,7 +29,17 @@ import {
29
29
  export interface ClientConfig<S extends IGameState, A extends IAction> {
30
30
  url?: string; // Full WebSocket URL (overrides auto-detection)
31
31
  wsPort?: number; // WebSocket port (default: auto-detected as HTTP port + 2)
32
- reducer: (state: S, action: A) => S;
32
+ /**
33
+ * Applies actions locally for an optimistic update before the host confirms.
34
+ *
35
+ * **Omit it when the host projects state per player** (`project` in
36
+ * `GameHostRuntimeConfig`): the client then holds a *view* rather than the
37
+ * whole state, and the game reducer cannot run over a partial view. Without a
38
+ * reducer the client simply renders what the host sends — a round trip that
39
+ * is imperceptible for turn-based games, and the price of hidden information
40
+ * never reaching the device.
41
+ */
42
+ reducer?: (state: S, action: A) => S;
33
43
  initialState: S;
34
44
  name?: string; // Player display name (default: "Player")
35
45
  avatar?: string; // Player avatar emoji (default: "\u{1F600}")
@@ -83,9 +93,11 @@ export function useGameClient<S extends IGameState, A extends IAction>(
83
93
  const [disconnectReason, setDisconnectReason] = useState<string | null>(null);
84
94
 
85
95
  // Local Optimistic State
86
- // Wrap the user's reducer with createGameReducer to handle HYDRATE automatically
96
+ // Wrap the user's reducer with createGameReducer to handle HYDRATE automatically.
97
+ // With no reducer (server-projected views) the identity function keeps HYDRATE
98
+ // working while local application becomes a no-op.
87
99
  const [state, dispatchLocal] = useReducer(
88
- createGameReducer(config.reducer),
100
+ createGameReducer(config.reducer ?? ((current: S) => current)),
89
101
  config.initialState,
90
102
  );
91
103
 
@@ -144,12 +144,27 @@ export type RelayMessage = RelayClientMessage | RelayServerMessage;
144
144
  * that keep every room in one process (the Bun reference server) ignore the
145
145
  * path, so this is safe to send to either.
146
146
  *
147
+ * Passing no room code addresses {@link RELAY_MINT_PATH} instead, asking the
148
+ * relay to allocate one; the code comes back in `ROOM_CREATED`.
149
+ *
147
150
  * @param url - Base relay URL, e.g. `wss://relay.example.com`.
148
- * @param roomId - Room code to address.
151
+ * @param roomId - Room code to address, or omitted to have one minted.
149
152
  */
150
- export function relayRoomUrl(url: string, roomId: string): string {
153
+ export function relayRoomUrl(url: string, roomId?: string): string {
151
154
  const trimmed = url.replace(/\/+$/, "");
152
155
  const [base, query] = trimmed.split("?", 2);
153
- const path = `${base}/r/${encodeURIComponent(roomId)}`;
156
+ const path =
157
+ roomId === undefined
158
+ ? `${base}${RELAY_MINT_PATH}`
159
+ : `${base}/r/${encodeURIComponent(roomId)}`;
154
160
  return query ? `${path}?${query}` : path;
155
161
  }
162
+
163
+ /**
164
+ * Path that asks the relay to allocate a room code.
165
+ *
166
+ * Reserved, so it can never be mistaken for a room code. Single-process relays
167
+ * ignore the path and mint from the `CREATE_ROOM` message alone; sharded relays
168
+ * need it, because they must choose the shard before any frame arrives.
169
+ */
170
+ export const RELAY_MINT_PATH = "/new";