@couch-kit/host 1.2.6 → 1.2.7

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.
@@ -1 +1 @@
1
- {"version":3,"file":"provider.d.ts","sourceRoot":"","sources":["../src/provider.tsx"],"names":[],"mappings":"AAAA,OAAO,KAQN,MAAM,OAAO,CAAC;AAGf,OAAO,EAML,KAAK,UAAU,EACf,KAAK,OAAO,EAGb,MAAM,iBAAiB,CAAC;AAEzB,MAAM,WAAW,cAAc,CAAC,CAAC,SAAS,UAAU,EAAE,CAAC,SAAS,OAAO;IACrE,YAAY,EAAE,CAAC,CAAC;IAChB,OAAO,EAAE,CAAC,KAAK,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,KAAK,CAAC,CAAC;IACpC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,+CAA+C;IAC/C,cAAc,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC1D,wCAAwC;IACxC,YAAY,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IAC1C,yCAAyC;IACzC,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;CAClC;AAED,UAAU,oBAAoB,CAAC,CAAC,SAAS,UAAU,EAAE,CAAC,SAAS,OAAO;IACpE,KAAK,EAAE,CAAC,CAAC;IACT,QAAQ,EAAE,CAAC,MAAM,EAAE,CAAC,KAAK,IAAI,CAAC;IAC9B,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,WAAW,EAAE,KAAK,GAAG,IAAI,CAAC;CAC3B;AA4CD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,SAAS,UAAU,EAAE,CAAC,SAAS,OAAO,EAAE,EACxE,QAAQ,EACR,MAAM,GACP,EAAE;IACD,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,MAAM,EAAE,cAAc,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;CAC9B,qBA+NA;AAED;;;;;;;;;GASG;AACH,wBAAgB,WAAW,CAAC,CAAC,SAAS,UAAU,EAAE,CAAC,SAAS,OAAO,KAK/C,oBAAoB,CAAC,CAAC,EAAE,CAAC,CAAC,CAC7C"}
1
+ {"version":3,"file":"provider.d.ts","sourceRoot":"","sources":["../src/provider.tsx"],"names":[],"mappings":"AAAA,OAAO,KAQN,MAAM,OAAO,CAAC;AAGf,OAAO,EAML,KAAK,UAAU,EACf,KAAK,OAAO,EAGb,MAAM,iBAAiB,CAAC;AAEzB,MAAM,WAAW,cAAc,CAAC,CAAC,SAAS,UAAU,EAAE,CAAC,SAAS,OAAO;IACrE,YAAY,EAAE,CAAC,CAAC;IAChB,OAAO,EAAE,CAAC,KAAK,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,KAAK,CAAC,CAAC;IACpC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,+CAA+C;IAC/C,cAAc,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC1D,wCAAwC;IACxC,YAAY,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IAC1C,yCAAyC;IACzC,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;CAClC;AAED,UAAU,oBAAoB,CAAC,CAAC,SAAS,UAAU,EAAE,CAAC,SAAS,OAAO;IACpE,KAAK,EAAE,CAAC,CAAC;IACT,QAAQ,EAAE,CAAC,MAAM,EAAE,CAAC,KAAK,IAAI,CAAC;IAC9B,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,WAAW,EAAE,KAAK,GAAG,IAAI,CAAC;CAC3B;AA4CD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,SAAS,UAAU,EAAE,CAAC,SAAS,OAAO,EAAE,EACxE,QAAQ,EACR,MAAM,GACP,EAAE;IACD,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,MAAM,EAAE,cAAc,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;CAC9B,qBA4OA;AAED;;;;;;;;;GASG;AACH,wBAAgB,WAAW,CAAC,CAAC,SAAS,UAAU,EAAE,CAAC,SAAS,OAAO,KAK/C,oBAAoB,CAAC,CAAC,EAAE,CAAC,CAAC,CAC7C"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@couch-kit/host",
3
- "version": "1.2.6",
3
+ "version": "1.2.7",
4
4
  "description": "React Native host for local multiplayer party games on Android TV — WebSocket server, state management, and static file serving",
5
5
  "license": "MIT",
6
6
  "repository": {
package/src/provider.tsx CHANGED
@@ -123,6 +123,27 @@ export function GameHostProvider<S extends IGameState, A extends IAction>({
123
123
  stateRef.current = state;
124
124
  }, [state]);
125
125
 
126
+ // Send WELCOME messages after state has settled (post-render).
127
+ // This guarantees the joining player is included in the state snapshot.
128
+ useEffect(() => {
129
+ if (pendingWelcome.current.size === 0) return;
130
+ if (!wsServer.current) return;
131
+
132
+ const server = wsServer.current;
133
+ for (const [socketId] of pendingWelcome.current) {
134
+ welcomedClients.current.add(socketId);
135
+ server.send(socketId, {
136
+ type: MessageTypes.WELCOME,
137
+ payload: {
138
+ playerId: socketId,
139
+ state,
140
+ serverTime: Date.now(),
141
+ },
142
+ });
143
+ }
144
+ pendingWelcome.current.clear();
145
+ }, [state]);
146
+
126
147
  // Keep refs for callback props to avoid stale closures
127
148
  const configRef = useRef(config);
128
149
  useEffect(() => {
@@ -147,6 +168,9 @@ export function GameHostProvider<S extends IGameState, A extends IAction>({
147
168
  // Track socket IDs that have received their WELCOME message
148
169
  const welcomedClients = useRef<Set<string>>(new Set());
149
170
 
171
+ // Track socket IDs that need a WELCOME message after state settles
172
+ const pendingWelcome = useRef<Map<string, string>>(new Map()); // socketId -> playerId
173
+
150
174
  useEffect(() => {
151
175
  const port = config.wsPort || httpPort + DEFAULT_WS_PORT_OFFSET;
152
176
  const server = new GameWebSocketServer({ port, debug: config.debug });
@@ -199,20 +223,9 @@ export function GameHostProvider<S extends IGameState, A extends IAction>({
199
223
  payload: { id: socketId, ...payload },
200
224
  } as InternalAction<S>);
201
225
 
202
- // Use queueMicrotask to send WELCOME after the reducer has processed
203
- // the PLAYER_JOINED action, so the client receives state that includes
204
- // themselves in the players list.
205
- queueMicrotask(() => {
206
- welcomedClients.current.add(socketId);
207
- server.send(socketId, {
208
- type: MessageTypes.WELCOME,
209
- payload: {
210
- playerId: socketId,
211
- state: stateRef.current,
212
- serverTime: Date.now(),
213
- },
214
- });
215
- });
226
+ // Queue WELCOME to be sent after React re-renders and stateRef is updated.
227
+ // A useEffect watches for pending welcomes and sends them with fresh state.
228
+ pendingWelcome.current.set(socketId, socketId);
216
229
 
217
230
  configRef.current.onPlayerJoined?.(socketId, payload.name);
218
231
  break;
@@ -290,7 +303,6 @@ export function GameHostProvider<S extends IGameState, A extends IAction>({
290
303
  // 3. Throttled State Broadcasts (~30fps)
291
304
  // Batches rapid state changes so at most one broadcast is sent per ~33ms frame,
292
305
  // reducing serialization overhead and network traffic for fast-updating games.
293
- const broadcastPending = useRef(false);
294
306
  const broadcastTimer = useRef<ReturnType<typeof setTimeout> | null>(null);
295
307
 
296
308
  const broadcastState = useCallback(() => {
@@ -303,14 +315,15 @@ export function GameHostProvider<S extends IGameState, A extends IAction>({
303
315
  },
304
316
  });
305
317
  }
306
- broadcastPending.current = false;
307
318
  }, []);
308
319
 
309
320
  useEffect(() => {
310
- if (!broadcastPending.current) {
311
- broadcastPending.current = true;
312
- broadcastTimer.current = setTimeout(broadcastState, 33); // ~30fps
321
+ // Cancel any pending broadcast and schedule a fresh one.
322
+ // This ensures the broadcast always uses the latest stateRef.
323
+ if (broadcastTimer.current) {
324
+ clearTimeout(broadcastTimer.current);
313
325
  }
326
+ broadcastTimer.current = setTimeout(broadcastState, 33); // ~30fps
314
327
 
315
328
  return () => {
316
329
  if (broadcastTimer.current) {