@couch-kit/host 1.7.12 → 1.7.14

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,28 +1,2 @@
1
- /** Error codes surfaced when a client ACTION is rejected before dispatch. */
2
- export type ActionRejectionCode = "FORBIDDEN_ACTION" | "NOT_JOINED";
3
- /** Outcome of authorizing an inbound client ACTION message. */
4
- export type ActionAuthorization = {
5
- kind: "allow";
6
- playerId: string;
7
- } | {
8
- kind: "reject";
9
- code: ActionRejectionCode;
10
- message: string;
11
- };
12
- /**
13
- * Decide whether an inbound client ACTION may be dispatched.
14
- *
15
- * Rejects, in order:
16
- * - **Internal action types** — clients must not be able to inject framework
17
- * actions (`__HYDRATE__`, `__PLAYER_JOINED__`, etc.) to forge state.
18
- * - **Un-joined sockets** — a socket with no resolved player ID never completed
19
- * a JOIN, so its actions have no owner and must not mutate state.
20
- *
21
- * The decision is pure so it can be unit-tested without a WebSocket or React.
22
- *
23
- * @param actionType - The `type` field of the client's action payload.
24
- * @param resolvedPlayerId - The player ID cached for the socket at JOIN time, or
25
- * `undefined` if the socket has not joined.
26
- */
27
- export declare function authorizeClientAction(actionType: string, resolvedPlayerId: string | undefined): ActionAuthorization;
1
+ export { authorizeClientAction, type ActionAuthorization, type ActionRejectionCode, } from "@couch-kit/runtime";
28
2
  //# sourceMappingURL=action-authorization.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"action-authorization.d.ts","sourceRoot":"","sources":["../src/action-authorization.ts"],"names":[],"mappings":"AAEA,6EAA6E;AAC7E,MAAM,MAAM,mBAAmB,GAAG,kBAAkB,GAAG,YAAY,CAAC;AAEpE,+DAA+D;AAC/D,MAAM,MAAM,mBAAmB,GAC3B;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GACnC;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,IAAI,EAAE,mBAAmB,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;AAUnE;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,qBAAqB,CACnC,UAAU,EAAE,MAAM,EAClB,gBAAgB,EAAE,MAAM,GAAG,SAAS,GACnC,mBAAmB,CAkBrB"}
1
+ {"version":3,"file":"action-authorization.d.ts","sourceRoot":"","sources":["../src/action-authorization.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,qBAAqB,EACrB,KAAK,mBAAmB,EACxB,KAAK,mBAAmB,GACzB,MAAM,oBAAoB,CAAC"}
@@ -1,30 +1,2 @@
1
- import { MessageTypes, type HostMessage } from "@couch-kit/core";
2
- /** Default state broadcast throttle (~30fps). */
3
- export declare const DEFAULT_STATE_THROTTLE_MS = 33;
4
- export type StateUpdateMessage = Extract<HostMessage, {
5
- type: typeof MessageTypes.STATE_UPDATE;
6
- }>;
7
- export interface TimerScheduler<TTimer> {
8
- setTimeout(callback: () => void, delayMs: number): TTimer;
9
- clearTimeout(timer: TTimer): void;
10
- }
11
- export interface BroadcastSchedulerOptions<TTimer> {
12
- stateThrottleMs?: number;
13
- scheduler?: TimerScheduler<TTimer>;
14
- }
15
- /**
16
- * Debounced state-broadcast scheduler used by the host provider.
17
- * Rapid state changes are coalesced into one broadcast after the latest change.
18
- */
19
- export declare class BroadcastScheduler<TTimer = ReturnType<typeof setTimeout>> {
20
- private stateThrottleMs;
21
- private readonly scheduler;
22
- private timer;
23
- constructor(options?: BroadcastSchedulerOptions<TTimer>);
24
- schedule(callback: () => void): void;
25
- cancel(): void;
26
- setStateThrottleMs(stateThrottleMs: number): void;
27
- hasPendingBroadcast(): boolean;
28
- }
29
- export declare function createStateUpdateMessage(newState: unknown, actions: readonly unknown[], timestamp?: number): StateUpdateMessage;
1
+ export { BroadcastScheduler, DEFAULT_STATE_THROTTLE_MS, createStateUpdateMessage, type BroadcastSchedulerOptions, type StateUpdateMessage, type TimerScheduler, } from "@couch-kit/runtime";
30
2
  //# sourceMappingURL=broadcast-scheduler.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"broadcast-scheduler.d.ts","sourceRoot":"","sources":["../src/broadcast-scheduler.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,KAAK,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAEjE,iDAAiD;AACjD,eAAO,MAAM,yBAAyB,KAAK,CAAC;AAE5C,MAAM,MAAM,kBAAkB,GAAG,OAAO,CACtC,WAAW,EACX;IAAE,IAAI,EAAE,OAAO,YAAY,CAAC,YAAY,CAAA;CAAE,CAC3C,CAAC;AAEF,MAAM,WAAW,cAAc,CAAC,MAAM;IACpC,UAAU,CAAC,QAAQ,EAAE,MAAM,IAAI,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAAC;IAC1D,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACnC;AAOD,MAAM,WAAW,yBAAyB,CAAC,MAAM;IAC/C,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,SAAS,CAAC,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;CACpC;AAED;;;GAGG;AACH,qBAAa,kBAAkB,CAAC,MAAM,GAAG,UAAU,CAAC,OAAO,UAAU,CAAC;IACpE,OAAO,CAAC,eAAe,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAyB;IACnD,OAAO,CAAC,KAAK,CAAuB;gBAExB,OAAO,GAAE,yBAAyB,CAAC,MAAM,CAAM;IAQ3D,QAAQ,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,IAAI;IAQpC,MAAM,IAAI,IAAI;IAOd,kBAAkB,CAAC,eAAe,EAAE,MAAM,GAAG,IAAI;IAIjD,mBAAmB,IAAI,OAAO;CAG/B;AAED,wBAAgB,wBAAwB,CACtC,QAAQ,EAAE,OAAO,EACjB,OAAO,EAAE,SAAS,OAAO,EAAE,EAC3B,SAAS,GAAE,MAAmB,GAC7B,kBAAkB,CAapB"}
1
+ {"version":3,"file":"broadcast-scheduler.d.ts","sourceRoot":"","sources":["../src/broadcast-scheduler.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,kBAAkB,EAClB,yBAAyB,EACzB,wBAAwB,EACxB,KAAK,yBAAyB,EAC9B,KAAK,kBAAkB,EACvB,KAAK,cAAc,GACpB,MAAM,oBAAoB,CAAC"}
@@ -1,20 +1,2 @@
1
- import { MessageTypes, type ClientMessage } from "@couch-kit/core";
2
- type ClientMessageOf<TType extends ClientMessage["type"]> = Extract<ClientMessage, {
3
- type: TType;
4
- }>;
5
- export type ValidatedClientMessage = {
6
- type: typeof MessageTypes.JOIN;
7
- payload: {
8
- name: string;
9
- secret?: unknown;
10
- avatar?: unknown;
11
- [key: string]: unknown;
12
- };
13
- } | ClientMessageOf<"ACTION"> | ClientMessageOf<"PING"> | ClientMessageOf<"ASSETS_LOADED">;
14
- /**
15
- * Validates that an incoming message has the expected shape.
16
- * Returns true if the message has a processable client-message shape.
17
- */
18
- export declare function isValidClientMessage(msg: unknown): msg is ValidatedClientMessage;
19
- export {};
1
+ export { DEFAULT_MAX_MESSAGE_BYTES, frameByteLength, isValidClientMessage, type ValidatedClientMessage, } from "@couch-kit/runtime";
20
2
  //# sourceMappingURL=message-validation.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"message-validation.d.ts","sourceRoot":"","sources":["../src/message-validation.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,KAAK,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAEnE,KAAK,eAAe,CAAC,KAAK,SAAS,aAAa,CAAC,MAAM,CAAC,IAAI,OAAO,CACjE,aAAa,EACb;IAAE,IAAI,EAAE,KAAK,CAAA;CAAE,CAChB,CAAC;AAEF,MAAM,MAAM,sBAAsB,GAC9B;IACE,IAAI,EAAE,OAAO,YAAY,CAAC,IAAI,CAAC;IAC/B,OAAO,EAAE;QACP,IAAI,EAAE,MAAM,CAAC;QACb,MAAM,CAAC,EAAE,OAAO,CAAC;QACjB,MAAM,CAAC,EAAE,OAAO,CAAC;QACjB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;KACxB,CAAC;CACH,GACD,eAAe,CAAC,QAAQ,CAAC,GACzB,eAAe,CAAC,MAAM,CAAC,GACvB,eAAe,CAAC,eAAe,CAAC,CAAC;AAErC;;;GAGG;AACH,wBAAgB,oBAAoB,CAClC,GAAG,EAAE,OAAO,GACX,GAAG,IAAI,sBAAsB,CA8B/B"}
1
+ {"version":3,"file":"message-validation.d.ts","sourceRoot":"","sources":["../src/message-validation.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,yBAAyB,EACzB,eAAe,EACf,oBAAoB,EACpB,KAAK,sBAAsB,GAC5B,MAAM,oBAAoB,CAAC"}
package/lib/provider.d.ts CHANGED
@@ -1,24 +1,17 @@
1
1
  import React from "react";
2
- import { type IGameState, type IAction } from "@couch-kit/core";
3
- export interface GameHostConfig<S extends IGameState, A extends IAction> {
4
- initialState: S;
5
- reducer: (state: S, action: A) => S;
2
+ import { type IAction, type IGameState } from "@couch-kit/core";
3
+ import { type GameHostRuntimeConfig } from "@couch-kit/runtime";
4
+ export interface GameHostConfig<S extends IGameState, A extends IAction> extends GameHostRuntimeConfig<S, A> {
6
5
  port?: number;
7
6
  wsPort?: number;
8
7
  devMode?: boolean;
9
8
  devServerUrl?: string;
10
9
  staticDir?: string;
11
- debug?: boolean;
12
- /** Timeout (ms) before a disconnected player is permanently removed (default: 5 minutes). */
13
- disconnectTimeout?: number;
14
- /** State broadcast throttle interval in milliseconds (default: 33ms, ~30fps). */
15
- stateThrottleMs?: number;
16
- /** Called when a player successfully joins. */
17
- onPlayerJoined?: (playerId: string, name: string) => void;
18
- /** Called when a player disconnects. */
19
- onPlayerLeft?: (playerId: string) => void;
20
- /** Called when a server error occurs. */
21
- onError?: (error: Error) => void;
10
+ /**
11
+ * Maximum size of an inbound client message before parsing.
12
+ * Defaults to 256 KiB.
13
+ */
14
+ maxMessageBytes?: number;
22
15
  }
23
16
  interface GameHostContextValue<S extends IGameState, A extends IAction> {
24
17
  state: S;
@@ -27,34 +20,17 @@ interface GameHostContextValue<S extends IGameState, A extends IAction> {
27
20
  serverError: Error | null;
28
21
  }
29
22
  /**
30
- * React context provider that turns a React Native TV app into a local game server.
23
+ * React Native adapter for the transport-neutral authoritative game runtime.
31
24
  *
32
- * Starts a static file server (for the web controller) and a WebSocket game server
33
- * (for real-time state sync). Manages the canonical game state using the provided
34
- * reducer and broadcasts state updates to all connected clients.
35
- *
36
- * @param config - Host configuration including reducer, initial state, ports, and callbacks.
37
- *
38
- * @example
39
- * ```tsx
40
- * <GameHostProvider config={{ reducer: gameReducer, initialState }}>
41
- * <GameScreen />
42
- * </GameHostProvider>
43
- * ```
25
+ * The runtime owns canonical state and protocol behavior. This provider starts
26
+ * the native static/WebSocket servers and exposes runtime state through React.
44
27
  */
45
28
  export declare function GameHostProvider<S extends IGameState, A extends IAction>({ children, config, }: {
46
29
  children: React.ReactNode;
47
30
  config: GameHostConfig<S, A>;
48
31
  }): React.JSX.Element;
49
32
  /**
50
- * React hook to access the game host context.
51
- *
52
- * Must be used within a `<GameHostProvider>`. Returns the canonical game state,
53
- * a dispatch function for actions, the server URL (for QR codes), and any
54
- * server startup errors.
55
- *
56
- * @returns An object with `state`, `dispatch`, `serverUrl`, and `serverError`.
57
- * @throws If used outside of a `<GameHostProvider>`.
33
+ * Accesses canonical host state, trusted dispatch, and controller server URL.
58
34
  */
59
35
  export declare function useGameHost<S extends IGameState, A extends IAction>(): GameHostContextValue<S, A>;
60
36
  export {};
@@ -1 +1 @@
1
- {"version":3,"file":"provider.d.ts","sourceRoot":"","sources":["../src/provider.tsx"],"names":[],"mappings":"AAAA,OAAO,KAQN,MAAM,OAAO,CAAC;AAGf,OAAO,EAQL,KAAK,UAAU,EACf,KAAK,OAAO,EAEb,MAAM,iBAAiB,CAAC;AAczB,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,6FAA6F;IAC7F,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,iFAAiF;IACjF,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,+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;AAQD;;;;;;;;;;;;;;;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,qBAqWA;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;AACf,OAAO,EAGL,KAAK,OAAO,EACZ,KAAK,UAAU,EAChB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAEL,KAAK,qBAAqB,EAC3B,MAAM,oBAAoB,CAAC;AAI5B,MAAM,WAAW,cAAc,CAC7B,CAAC,SAAS,UAAU,EACpB,CAAC,SAAS,OAAO,CACjB,SAAQ,qBAAqB,CAAC,CAAC,EAAE,CAAC,CAAC;IACnC,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;;;OAGG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;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;AAOD;;;;;GAKG;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,qBA4HA;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,CAAC,SAAS,UAAU,EAAE,CAAC,SAAS,OAAO,KAK/C,oBAAoB,CAAC,CAAC,EAAE,CAAC,CAAC,CAC7C"}
@@ -1,32 +1,2 @@
1
- /** Maximum actions per rate-limit window. */
2
- export declare const RATE_LIMIT_MAX = 60;
3
- /** Rate-limit window duration (ms). */
4
- export declare const RATE_LIMIT_WINDOW = 1000;
5
- export interface RateLimitInfo {
6
- count: number;
7
- windowStart: number;
8
- }
9
- export interface RateLimitResult extends RateLimitInfo {
10
- allowed: boolean;
11
- }
12
- export interface ActionRateLimiterOptions {
13
- maxActions?: number;
14
- windowMs?: number;
15
- now?: () => number;
16
- }
17
- /**
18
- * Per-socket action limiter that preserves the host provider's original
19
- * fixed-window algorithm.
20
- */
21
- export declare class ActionRateLimiter {
22
- private readonly maxActions;
23
- private readonly windowMs;
24
- private readonly now;
25
- private readonly limits;
26
- constructor(options?: ActionRateLimiterOptions);
27
- record(socketId: string): RateLimitResult;
28
- reset(socketId: string): void;
29
- clear(): void;
30
- get(socketId: string): RateLimitInfo | undefined;
31
- }
1
+ export { ActionRateLimiter, RATE_LIMIT_MAX, RATE_LIMIT_WINDOW, type ActionRateLimiterOptions, type RateLimitInfo, type RateLimitResult, } from "@couch-kit/runtime";
32
2
  //# sourceMappingURL=rate-limiter.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"rate-limiter.d.ts","sourceRoot":"","sources":["../src/rate-limiter.ts"],"names":[],"mappings":"AAAA,6CAA6C;AAC7C,eAAO,MAAM,cAAc,KAAK,CAAC;AAEjC,uCAAuC;AACvC,eAAO,MAAM,iBAAiB,OAAO,CAAC;AAEtC,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,eAAgB,SAAQ,aAAa;IACpD,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,wBAAwB;IACvC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CACpB;AAED;;;GAGG;AACH,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAe;IACnC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAoC;gBAE/C,OAAO,GAAE,wBAA6B;IAMlD,MAAM,CAAC,QAAQ,EAAE,MAAM,GAAG,eAAe;IAiBzC,KAAK,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI;IAI7B,KAAK,IAAI,IAAI;IAIb,GAAG,CAAC,QAAQ,EAAE,MAAM,GAAG,aAAa,GAAG,SAAS;CAGjD"}
1
+ {"version":3,"file":"rate-limiter.d.ts","sourceRoot":"","sources":["../src/rate-limiter.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,iBAAiB,EACjB,cAAc,EACd,iBAAiB,EACjB,KAAK,wBAAwB,EAC7B,KAAK,aAAa,EAClB,KAAK,eAAe,GACrB,MAAM,oBAAoB,CAAC"}
@@ -1,62 +1,2 @@
1
- import { type IGameState, type InternalAction } from "@couch-kit/core";
2
- export interface SessionTimerScheduler<TTimer> {
3
- setTimeout(callback: () => void, delayMs: number): TTimer;
4
- clearTimeout(timer: TTimer): void;
5
- }
6
- export interface JoinSessionPayload {
7
- name: string;
8
- avatar?: string;
9
- secret: string;
10
- }
11
- export interface HostSessionManagerOptions<TTimer> {
12
- disconnectTimeout?: number;
13
- getDisconnectTimeout?: () => number;
14
- scheduler?: SessionTimerScheduler<TTimer>;
15
- derivePlayerId?: (secret: string) => Promise<string>;
16
- derivePlayerIdLegacy?: (secret: string) => string;
17
- }
18
- export type JoinSessionResult<S extends IGameState> = {
19
- playerId: string;
20
- socketId: string;
21
- secret: string;
22
- isReconnect: boolean;
23
- action: InternalAction<S>;
24
- };
25
- export type DisconnectSessionResult<S extends IGameState> = {
26
- kind: "unknown";
27
- } | {
28
- kind: "stale";
29
- playerId: string;
30
- secret: string;
31
- } | {
32
- kind: "left";
33
- playerId: string;
34
- secret: string;
35
- action: InternalAction<S>;
36
- };
37
- type PlayersSource<S extends IGameState> = S["players"] | (() => S["players"]);
38
- /**
39
- * Tracks host player sessions by secret and socket ID, including reconnects and
40
- * delayed cleanup of disconnected players.
41
- */
42
- export declare class HostSessionManager<TTimer = ReturnType<typeof setTimeout>> {
43
- private readonly sessions;
44
- private readonly reverseMap;
45
- private readonly cleanupTimers;
46
- private readonly socketIdToPlayerId;
47
- private readonly scheduler;
48
- private readonly getDisconnectTimeout;
49
- private readonly derivePlayerIdFn;
50
- private readonly derivePlayerIdLegacyFn;
51
- constructor(options?: HostSessionManagerOptions<TTimer>);
52
- handleJoin<S extends IGameState>(socketId: string, payload: JoinSessionPayload, playersSource: PlayersSource<S>): Promise<JoinSessionResult<S>>;
53
- handleDisconnect<S extends IGameState>(socketId: string): DisconnectSessionResult<S>;
54
- scheduleRemoval(playerId: string, secret: string, onRemove: (playerId: string) => void): void;
55
- cancelRemoval(playerId: string): void;
56
- clearRemovalTimers(): void;
57
- getPlayerIdForSocket(socketId: string): string | undefined;
58
- getSocketIdForSecret(secret: string): string | undefined;
59
- hasPendingRemoval(playerId: string): boolean;
60
- }
61
- export {};
1
+ export { HostSessionManager, type DisconnectSessionResult, type HostSessionManagerOptions, type JoinSessionPayload, type JoinSessionResult, type SessionTimerScheduler, } from "@couch-kit/runtime";
62
2
  //# sourceMappingURL=session-manager.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"session-manager.d.ts","sourceRoot":"","sources":["../src/session-manager.ts"],"names":[],"mappings":"AAAA,OAAO,EAKL,KAAK,UAAU,EACf,KAAK,cAAc,EACpB,MAAM,iBAAiB,CAAC;AAEzB,MAAM,WAAW,qBAAqB,CAAC,MAAM;IAC3C,UAAU,CAAC,QAAQ,EAAE,MAAM,IAAI,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAAC;IAC1D,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACnC;AASD,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,yBAAyB,CAAC,MAAM;IAC/C,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,oBAAoB,CAAC,EAAE,MAAM,MAAM,CAAC;IACpC,SAAS,CAAC,EAAE,qBAAqB,CAAC,MAAM,CAAC,CAAC;IAC1C,cAAc,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IACrD,oBAAoB,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,MAAM,CAAC;CACnD;AAED,MAAM,MAAM,iBAAiB,CAAC,CAAC,SAAS,UAAU,IAAI;IACpD,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,OAAO,CAAC;IACrB,MAAM,EAAE,cAAc,CAAC,CAAC,CAAC,CAAC;CAC3B,CAAC;AAEF,MAAM,MAAM,uBAAuB,CAAC,CAAC,SAAS,UAAU,IACpD;IAAE,IAAI,EAAE,SAAS,CAAA;CAAE,GACnB;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GACnD;IACE,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,cAAc,CAAC,CAAC,CAAC,CAAC;CAC3B,CAAC;AAEN,KAAK,aAAa,CAAC,CAAC,SAAS,UAAU,IACnC,CAAC,CAAC,SAAS,CAAC,GACZ,CAAC,MAAM,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC;AAEzB;;;GAGG;AACH,qBAAa,kBAAkB,CAC7B,MAAM,GAAG,UAAU,CAAC,OAAO,UAAU,CAAC;IAEtC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAA6B;IACtD,OAAO,CAAC,QAAQ,CAAC,UAAU,CAA6B;IACxD,OAAO,CAAC,QAAQ,CAAC,aAAa,CAA6B;IAC3D,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAA6B;IAChE,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAgC;IAC1D,OAAO,CAAC,QAAQ,CAAC,oBAAoB,CAAe;IACpD,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAsC;IACvE,OAAO,CAAC,QAAQ,CAAC,sBAAsB,CAA6B;gBAExD,OAAO,GAAE,yBAAyB,CAAC,MAAM,CAAM;IAYrD,UAAU,CAAC,CAAC,SAAS,UAAU,EACnC,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,kBAAkB,EAC3B,aAAa,EAAE,aAAa,CAAC,CAAC,CAAC,GAC9B,OAAO,CAAC,iBAAiB,CAAC,CAAC,CAAC,CAAC;IAoChC,gBAAgB,CAAC,CAAC,SAAS,UAAU,EACnC,QAAQ,EAAE,MAAM,GACf,uBAAuB,CAAC,CAAC,CAAC;IAwB7B,eAAe,CACb,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,GACnC,IAAI;IAUP,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI;IAQrC,kBAAkB,IAAI,IAAI;IAO1B,oBAAoB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAI1D,oBAAoB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAIxD,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO;CAG7C"}
1
+ {"version":3,"file":"session-manager.d.ts","sourceRoot":"","sources":["../src/session-manager.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,kBAAkB,EAClB,KAAK,uBAAuB,EAC5B,KAAK,yBAAyB,EAC9B,KAAK,kBAAkB,EACvB,KAAK,iBAAiB,EACtB,KAAK,qBAAqB,GAC3B,MAAM,oBAAoB,CAAC"}
@@ -8,6 +8,13 @@ import { EventEmitter } from "./event-emitter";
8
8
  export interface WebSocketConfig {
9
9
  port: number;
10
10
  debug?: boolean;
11
+ /**
12
+ * Maximum size (in bytes) of an inbound client message. Frames larger than
13
+ * this are discarded before parsing, bounding the memory a single client can
14
+ * force the host to allocate. Defaults to {@link DEFAULT_MAX_MESSAGE_BYTES}
15
+ * (256 KiB).
16
+ */
17
+ maxMessageBytes?: number;
11
18
  /**
12
19
  * @deprecated No effect. The underlying nitro-http WebSocket transport
13
20
  * manages frame sizing internally and this value is never read. This field
@@ -41,6 +48,7 @@ export declare class GameWebSocketServer extends EventEmitter<WebSocketServerEve
41
48
  private clients;
42
49
  private port;
43
50
  private debug;
51
+ private maxMessageBytes;
44
52
  constructor(config: WebSocketConfig);
45
53
  private log;
46
54
  start(): Promise<void>;
@@ -1 +1 @@
1
- {"version":3,"file":"websocket.d.ts","sourceRoot":"","sources":["../src/websocket.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAOH,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAG/C,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB;;;;OAIG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;;;OAKG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,8CAA8C;AAC9C,MAAM,MAAM,qBAAqB,GAAG;IAClC,UAAU,EAAE,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;IAC/B,OAAO,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;IAC9C,UAAU,EAAE,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;IAC/B,SAAS,EAAE,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IAC1B,KAAK,EAAE,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;CACvB,CAAC;AAOF,qBAAa,mBAAoB,SAAQ,YAAY,CAAC,qBAAqB,CAAC;IAC1E,OAAO,CAAC,MAAM,CAA6B;IAC3C,OAAO,CAAC,OAAO,CAA2C;IAC1D,OAAO,CAAC,IAAI,CAAS;IACrB,OAAO,CAAC,KAAK,CAAU;gBAEX,MAAM,EAAE,eAAe;IAUnC,OAAO,CAAC,GAAG;IAME,KAAK;IA6FL,IAAI;IAoBjB;;;OAGG;IACI,IAAI,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO;IAsB3C;;;OAGG;IACI,SAAS,CAAC,IAAI,EAAE,OAAO,EAAE,SAAS,CAAC,EAAE,MAAM;IAoBlD,yDAAyD;IACzD,IAAW,WAAW,IAAI,MAAM,CAE/B;CACF"}
1
+ {"version":3,"file":"websocket.d.ts","sourceRoot":"","sources":["../src/websocket.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAOH,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAO/C,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB;;;;;OAKG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB;;;;OAIG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;;;OAKG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,8CAA8C;AAC9C,MAAM,MAAM,qBAAqB,GAAG;IAClC,UAAU,EAAE,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;IAC/B,OAAO,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;IAC9C,UAAU,EAAE,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;IAC/B,SAAS,EAAE,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IAC1B,KAAK,EAAE,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;CACvB,CAAC;AAOF,qBAAa,mBAAoB,SAAQ,YAAY,CAAC,qBAAqB,CAAC;IAC1E,OAAO,CAAC,MAAM,CAA6B;IAC3C,OAAO,CAAC,OAAO,CAA2C;IAC1D,OAAO,CAAC,IAAI,CAAS;IACrB,OAAO,CAAC,KAAK,CAAU;IACvB,OAAO,CAAC,eAAe,CAAS;gBAEpB,MAAM,EAAE,eAAe;IAYnC,OAAO,CAAC,GAAG;IAME,KAAK;IAuGL,IAAI;IAoBjB;;;OAGG;IACI,IAAI,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO;IAsB3C;;;OAGG;IACI,SAAS,CAAC,IAAI,EAAE,OAAO,EAAE,SAAS,CAAC,EAAE,MAAM;IAoBlD,yDAAyD;IACzD,IAAW,WAAW,IAAI,MAAM,CAE/B;CACF"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@couch-kit/host",
3
- "version": "1.7.12",
3
+ "version": "1.7.14",
4
4
  "publishConfig": {
5
5
  "access": "public",
6
6
  "provenance": true
@@ -58,17 +58,22 @@
58
58
  "prepublishOnly": "bun run build"
59
59
  },
60
60
  "dependencies": {
61
- "@couch-kit/core": "0.9.2",
61
+ "@couch-kit/core": "0.9.3",
62
+ "@couch-kit/runtime": "0.1.0",
62
63
  "buffer": "^6.0.3",
63
64
  "js-sha1": "^0.7.0",
64
65
  "react-native-nitro-http-server": "^1.6.1"
65
66
  },
66
67
  "devDependencies": {
68
+ "@happy-dom/global-registrator": "^20.10.6",
69
+ "@testing-library/react": "^16.3.2",
67
70
  "@types/react": "^19.2.17",
68
- "react": "19.0.4",
69
- "react-native": "0.83.2",
70
- "del-cli": "^5.1.0",
71
- "jest": "^29.7.0"
71
+ "@types/react-dom": "^19",
72
+ "react": "19.2.7",
73
+ "react-dom": "19.2.7",
74
+ "react-native": "0.86.0",
75
+ "del-cli": "^7.0.0",
76
+ "jest": "^30.0.0"
72
77
  },
73
78
  "peerDependencies": {
74
79
  "react": ">=18.2.0",
@@ -1,55 +1,5 @@
1
- import { InternalActionTypes } from "@couch-kit/core";
2
-
3
- /** Error codes surfaced when a client ACTION is rejected before dispatch. */
4
- export type ActionRejectionCode = "FORBIDDEN_ACTION" | "NOT_JOINED";
5
-
6
- /** Outcome of authorizing an inbound client ACTION message. */
7
- export type ActionAuthorization =
8
- | { kind: "allow"; playerId: string }
9
- | { kind: "reject"; code: ActionRejectionCode; message: string };
10
-
11
- const INTERNAL_ACTION_TYPES = new Set<string>([
12
- InternalActionTypes.HYDRATE,
13
- InternalActionTypes.PLAYER_JOINED,
14
- InternalActionTypes.PLAYER_LEFT,
15
- InternalActionTypes.PLAYER_RECONNECTED,
16
- InternalActionTypes.PLAYER_REMOVED,
17
- ]);
18
-
19
- /**
20
- * Decide whether an inbound client ACTION may be dispatched.
21
- *
22
- * Rejects, in order:
23
- * - **Internal action types** — clients must not be able to inject framework
24
- * actions (`__HYDRATE__`, `__PLAYER_JOINED__`, etc.) to forge state.
25
- * - **Un-joined sockets** — a socket with no resolved player ID never completed
26
- * a JOIN, so its actions have no owner and must not mutate state.
27
- *
28
- * The decision is pure so it can be unit-tested without a WebSocket or React.
29
- *
30
- * @param actionType - The `type` field of the client's action payload.
31
- * @param resolvedPlayerId - The player ID cached for the socket at JOIN time, or
32
- * `undefined` if the socket has not joined.
33
- */
34
- export function authorizeClientAction(
35
- actionType: string,
36
- resolvedPlayerId: string | undefined,
37
- ): ActionAuthorization {
38
- if (INTERNAL_ACTION_TYPES.has(actionType)) {
39
- return {
40
- kind: "reject",
41
- code: "FORBIDDEN_ACTION",
42
- message: "Internal action types cannot be dispatched by clients",
43
- };
44
- }
45
-
46
- if (!resolvedPlayerId) {
47
- return {
48
- kind: "reject",
49
- code: "NOT_JOINED",
50
- message: "You must JOIN before dispatching actions",
51
- };
52
- }
53
-
54
- return { kind: "allow", playerId: resolvedPlayerId };
55
- }
1
+ export {
2
+ authorizeClientAction,
3
+ type ActionAuthorization,
4
+ type ActionRejectionCode,
5
+ } from "@couch-kit/runtime";
@@ -1,84 +1,8 @@
1
- import { MessageTypes, type HostMessage } from "@couch-kit/core";
2
-
3
- /** Default state broadcast throttle (~30fps). */
4
- export const DEFAULT_STATE_THROTTLE_MS = 33;
5
-
6
- export type StateUpdateMessage = Extract<
7
- HostMessage,
8
- { type: typeof MessageTypes.STATE_UPDATE }
9
- >;
10
-
11
- export interface TimerScheduler<TTimer> {
12
- setTimeout(callback: () => void, delayMs: number): TTimer;
13
- clearTimeout(timer: TTimer): void;
14
- }
15
-
16
- const defaultTimerScheduler: TimerScheduler<ReturnType<typeof setTimeout>> = {
17
- setTimeout: (callback, delayMs) => setTimeout(callback, delayMs),
18
- clearTimeout: (timer) => clearTimeout(timer),
19
- };
20
-
21
- export interface BroadcastSchedulerOptions<TTimer> {
22
- stateThrottleMs?: number;
23
- scheduler?: TimerScheduler<TTimer>;
24
- }
25
-
26
- /**
27
- * Debounced state-broadcast scheduler used by the host provider.
28
- * Rapid state changes are coalesced into one broadcast after the latest change.
29
- */
30
- export class BroadcastScheduler<TTimer = ReturnType<typeof setTimeout>> {
31
- private stateThrottleMs: number;
32
- private readonly scheduler: TimerScheduler<TTimer>;
33
- private timer: TTimer | null = null;
34
-
35
- constructor(options: BroadcastSchedulerOptions<TTimer> = {}) {
36
- this.stateThrottleMs =
37
- options.stateThrottleMs ?? DEFAULT_STATE_THROTTLE_MS;
38
- this.scheduler =
39
- options.scheduler ??
40
- (defaultTimerScheduler as unknown as TimerScheduler<TTimer>);
41
- }
42
-
43
- schedule(callback: () => void): void {
44
- this.cancel();
45
- this.timer = this.scheduler.setTimeout(() => {
46
- this.timer = null;
47
- callback();
48
- }, this.stateThrottleMs);
49
- }
50
-
51
- cancel(): void {
52
- if (this.timer) {
53
- this.scheduler.clearTimeout(this.timer);
54
- this.timer = null;
55
- }
56
- }
57
-
58
- setStateThrottleMs(stateThrottleMs: number): void {
59
- this.stateThrottleMs = stateThrottleMs;
60
- }
61
-
62
- hasPendingBroadcast(): boolean {
63
- return this.timer !== null;
64
- }
65
- }
66
-
67
- export function createStateUpdateMessage(
68
- newState: unknown,
69
- actions: readonly unknown[],
70
- timestamp: number = Date.now(),
71
- ): StateUpdateMessage {
72
- return {
73
- type: MessageTypes.STATE_UPDATE,
74
- payload: {
75
- newState,
76
- timestamp,
77
- ...(actions.length === 1
78
- ? { action: actions[0] }
79
- : actions.length > 1
80
- ? { action: actions }
81
- : {}),
82
- },
83
- };
84
- }
1
+ export {
2
+ BroadcastScheduler,
3
+ DEFAULT_STATE_THROTTLE_MS,
4
+ createStateUpdateMessage,
5
+ type BroadcastSchedulerOptions,
6
+ type StateUpdateMessage,
7
+ type TimerScheduler,
8
+ } from "@couch-kit/runtime";
@@ -1,58 +1,6 @@
1
- import { MessageTypes, type ClientMessage } from "@couch-kit/core";
2
-
3
- type ClientMessageOf<TType extends ClientMessage["type"]> = Extract<
4
- ClientMessage,
5
- { type: TType }
6
- >;
7
-
8
- export type ValidatedClientMessage =
9
- | {
10
- type: typeof MessageTypes.JOIN;
11
- payload: {
12
- name: string;
13
- secret?: unknown;
14
- avatar?: unknown;
15
- [key: string]: unknown;
16
- };
17
- }
18
- | ClientMessageOf<"ACTION">
19
- | ClientMessageOf<"PING">
20
- | ClientMessageOf<"ASSETS_LOADED">;
21
-
22
- /**
23
- * Validates that an incoming message has the expected shape.
24
- * Returns true if the message has a processable client-message shape.
25
- */
26
- export function isValidClientMessage(
27
- msg: unknown,
28
- ): msg is ValidatedClientMessage {
29
- if (typeof msg !== "object" || msg === null) return false;
30
- const m = msg as Record<string, unknown>;
31
- if (typeof m.type !== "string") return false;
32
-
33
- switch (m.type) {
34
- case MessageTypes.JOIN:
35
- return (
36
- typeof m.payload === "object" &&
37
- m.payload !== null &&
38
- typeof (m.payload as Record<string, unknown>).name === "string"
39
- );
40
- case MessageTypes.ACTION:
41
- return (
42
- typeof m.payload === "object" &&
43
- m.payload !== null &&
44
- typeof (m.payload as Record<string, unknown>).type === "string"
45
- );
46
- case MessageTypes.PING:
47
- return (
48
- typeof m.payload === "object" &&
49
- m.payload !== null &&
50
- typeof (m.payload as Record<string, unknown>).id === "string" &&
51
- typeof (m.payload as Record<string, unknown>).timestamp === "number"
52
- );
53
- case MessageTypes.ASSETS_LOADED:
54
- return m.payload === true;
55
- default:
56
- return false;
57
- }
58
- }
1
+ export {
2
+ DEFAULT_MAX_MESSAGE_BYTES,
3
+ frameByteLength,
4
+ isValidClientMessage,
5
+ type ValidatedClientMessage,
6
+ } from "@couch-kit/runtime";