@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.
- package/lib/action-authorization.d.ts +1 -27
- package/lib/action-authorization.d.ts.map +1 -1
- package/lib/broadcast-scheduler.d.ts +1 -29
- package/lib/broadcast-scheduler.d.ts.map +1 -1
- package/lib/message-validation.d.ts +1 -19
- package/lib/message-validation.d.ts.map +1 -1
- package/lib/provider.d.ts +12 -36
- package/lib/provider.d.ts.map +1 -1
- package/lib/rate-limiter.d.ts +1 -31
- package/lib/rate-limiter.d.ts.map +1 -1
- package/lib/session-manager.d.ts +1 -61
- package/lib/session-manager.d.ts.map +1 -1
- package/lib/websocket.d.ts +8 -0
- package/lib/websocket.d.ts.map +1 -1
- package/package.json +11 -6
- package/src/action-authorization.ts +5 -55
- package/src/broadcast-scheduler.ts +8 -84
- package/src/message-validation.ts +6 -58
- package/src/provider.tsx +100 -370
- package/src/rate-limiter.ts +8 -66
- package/src/session-manager.ts +8 -192
- package/src/websocket.ts +28 -4
|
@@ -1,28 +1,2 @@
|
|
|
1
|
-
|
|
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":"
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
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,
|
|
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
|
|
3
|
-
|
|
4
|
-
|
|
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
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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
|
|
23
|
+
* React Native adapter for the transport-neutral authoritative game runtime.
|
|
31
24
|
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
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
|
-
*
|
|
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 {};
|
package/lib/provider.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"provider.d.ts","sourceRoot":"","sources":["../src/provider.tsx"],"names":[],"mappings":"AAAA,OAAO,KAQN,MAAM,OAAO,CAAC;
|
|
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"}
|
package/lib/rate-limiter.d.ts
CHANGED
|
@@ -1,32 +1,2 @@
|
|
|
1
|
-
|
|
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,
|
|
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"}
|
package/lib/session-manager.d.ts
CHANGED
|
@@ -1,62 +1,2 @@
|
|
|
1
|
-
|
|
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,
|
|
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"}
|
package/lib/websocket.d.ts
CHANGED
|
@@ -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>;
|
package/lib/websocket.d.ts.map
CHANGED
|
@@ -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;
|
|
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.
|
|
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.
|
|
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
|
|
69
|
-
"react
|
|
70
|
-
"
|
|
71
|
-
"
|
|
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
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
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
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
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";
|