@game-vir/multiplayer
Version:
Client side definitions and types for @game-vir/multiplayer-server
343 lines (342 loc) • 14.5 kB
TypeScript
import { type JsonCompatibleValue, type MaybePromise, type Uuid } from '@augment-vir/common';
import { type mapServiceDevPort } from '@rest-vir/define-service';
import { type AnyDuration } from 'date-vir';
import { ListenTarget } from 'typed-event-target';
import { type RoomInput } from '../webrtc/webrtc-multiplayer-controller.js';
import { RoomRejectionError } from './errors.js';
import { LockStepGameStateController } from './lock-step-controller.js';
import { type MultiplayerApi } from './multiplayer-api.js';
/**
* Connection state for {@link MultiplayerController}.
*
* @category Internal
*/
export declare enum MultiplayerConnectionState {
Connecting = "connecting",
Connected = "connected",
/** The connection has not been started or has been gracefully terminated. */
Disconnected = "disconnected"
}
/**
* Service and room connection state for {@link MultiplayerController}.
*
* @category Internal
*/
export type ServiceAndRoomConnectionState = {
service: MultiplayerConnectionState | Error;
room: MultiplayerConnectionState | Error;
};
/**
* Empty or totally disconnected state for {@link ServiceAndRoomConnectionState}.
*
* @category Internal
*/
export declare const emptyServiceAndRoomConnectionState: Readonly<ServiceAndRoomConnectionState>;
/**
* Constructor parameters for {@link MultiplayerController}.
*
* @category Internal
*/
export type MultiplayerControllerParams<Action extends JsonCompatibleValue> = {
/**
* Unique string id that represents your game. Your backend will need to know this game id and
* match it to your frontend's origin.
*/
gameId: string;
/**
* This is fired when a WebRTC peer attempts to connect to the host client (this will only be
* fired if your client is the host). Return `true` to accept the connection. Return `false` to
* reject it.
*
* @default accept all connections
*/
acceptConnection?: ((connectingClientId: Uuid, controller: MultiplayerController<Action>) => MaybePromise<boolean>) | undefined;
/**
* The duration between each frame. This should probably always be smaller than your supported
* render frame duration (1/FPS). If this is set to `undefined`, you'll need to manually trigger
* frames with `MultiplayerController.runFrame`.
*/
frameDuration?: AnyDuration | undefined;
};
/**
* Multiplayer mode parameters for {@link MultiplayerController}.
*
* @category Internal
*/
export type MultiplayerParams = {
/**
* Set to `undefined` or `false` to disable port scanning. Set to `true` to enable port
* scanning. Set to an options object to configure port scanning.
*
* It is useful to enable this so that clients can find the port that your multiplayer server is
* running on in case it must change. Note that port scanning will not be active if your
* `serviceOrigin` does not contain a port.
*
* @default undefined
*/
portScanOptions?: undefined | Parameters<typeof mapServiceDevPort>[1] | boolean;
/**
* The origin of the server running the multiplayer connection service.
*
* @example 'http://localhost:3000'
*/
backendOrigin: string;
/**
* How long to wait before fetching the list of rooms again.
*
* @default {seconds: 10}
*/
roomUpdateInterval?: AnyDuration | undefined;
/**
* Optional stun server URLs to help with routing WebRTC connections. This is entirely optional,
* but might help with clients attempting to establish connections to each other.
*/
stunServerUrls?: ReadonlyArray<string> | undefined;
};
declare const ControllerFrameEvent_base: (new (eventInitDict: {
bubbles?: boolean;
cancelable?: boolean;
composed?: boolean;
detail: any;
}) => import("typed-event-target").TypedCustomEvent<any, "controller-frame">) & Pick<{
new (type: string, eventInitDict?: EventInit): Event;
prototype: Event;
readonly NONE: 0;
readonly CAPTURING_PHASE: 1;
readonly AT_TARGET: 2;
readonly BUBBLING_PHASE: 3;
}, "prototype" | "NONE" | "CAPTURING_PHASE" | "AT_TARGET" | "BUBBLING_PHASE"> & Pick<import("typed-event-target").TypedCustomEvent<any, "controller-frame">, "type">;
/**
* This is fired whenever a new frame is received from the host client.
*
* @category Events
*/
export declare class ControllerFrameEvent<Action extends JsonCompatibleValue> extends ControllerFrameEvent_base {
detail: ReadonlyArray<Action>;
}
declare const ControllerRoomListEvent_base: (new (eventInitDict: {
bubbles?: boolean;
cancelable?: boolean;
composed?: boolean;
detail: Readonly<Partial<Record<`${string}-${string}-${string}-${string}-${string}`, {
roomName: string;
roomId: `${string}-${string}-${string}-${string}-${string}`;
clientCount: number;
hasRoomPassword: boolean;
}>>>;
}) => import("typed-event-target").TypedCustomEvent<Readonly<Partial<Record<`${string}-${string}-${string}-${string}-${string}`, {
roomName: string;
roomId: `${string}-${string}-${string}-${string}-${string}`;
clientCount: number;
hasRoomPassword: boolean;
}>>>, "controller-room-list">) & Pick<{
new (type: string, eventInitDict?: EventInit): Event;
prototype: Event;
readonly NONE: 0;
readonly CAPTURING_PHASE: 1;
readonly AT_TARGET: 2;
readonly BUBBLING_PHASE: 3;
}, "prototype" | "NONE" | "CAPTURING_PHASE" | "AT_TARGET" | "BUBBLING_PHASE"> & Pick<import("typed-event-target").TypedCustomEvent<Readonly<Partial<Record<`${string}-${string}-${string}-${string}-${string}`, {
roomName: string;
roomId: `${string}-${string}-${string}-${string}-${string}`;
clientCount: number;
hasRoomPassword: boolean;
}>>>, "controller-room-list">, "type">;
/**
* This is called whenever the room list updates, even if there were no changes to the room list.
* Note that room list updates are paused while the controller is connected to an actual room.
*
* @category Events
*/
export declare class ControllerRoomListEvent extends ControllerRoomListEvent_base {
}
declare const ControllerClientEvent_base: (new (eventInitDict: {
bubbles?: boolean;
cancelable?: boolean;
composed?: boolean;
detail: Readonly<import("type-fest/source/require-exactly-one.js")._RequireExactlyOne<{
newHost: Uuid;
newMember: Uuid;
lostHost: Uuid;
lostMember: Uuid;
}, "newHost" | "newMember" | "lostHost" | "lostMember">>;
}) => import("typed-event-target").TypedCustomEvent<Readonly<import("type-fest/source/require-exactly-one.js")._RequireExactlyOne<{
newHost: Uuid;
newMember: Uuid;
lostHost: Uuid;
lostMember: Uuid;
}, "newHost" | "newMember" | "lostHost" | "lostMember">>, "controller-client">) & Pick<{
new (type: string, eventInitDict?: EventInit): Event;
prototype: Event;
readonly NONE: 0;
readonly CAPTURING_PHASE: 1;
readonly AT_TARGET: 2;
readonly BUBBLING_PHASE: 3;
}, "prototype" | "NONE" | "CAPTURING_PHASE" | "AT_TARGET" | "BUBBLING_PHASE"> & Pick<import("typed-event-target").TypedCustomEvent<Readonly<import("type-fest/source/require-exactly-one.js")._RequireExactlyOne<{
newHost: Uuid;
newMember: Uuid;
lostHost: Uuid;
lostMember: Uuid;
}, "newHost" | "newMember" | "lostHost" | "lostMember">>, "controller-client">, "type">;
/**
* This is fired in the following situations:
*
* - A new host for the room was selected
* - The room host was lost
* - A new room client was added (only fired on the host client)
* - A room client was lost (only fired on the host client)
*
* @category Events
*/
export declare class ControllerClientEvent extends ControllerClientEvent_base {
}
declare const ControllerConnectionEvent_base: (new (eventInitDict: {
bubbles?: boolean;
cancelable?: boolean;
composed?: boolean;
detail: ServiceAndRoomConnectionState;
}) => import("typed-event-target").TypedCustomEvent<ServiceAndRoomConnectionState, "controller-connection">) & Pick<{
new (type: string, eventInitDict?: EventInit): Event;
prototype: Event;
readonly NONE: 0;
readonly CAPTURING_PHASE: 1;
readonly AT_TARGET: 2;
readonly BUBBLING_PHASE: 3;
}, "prototype" | "NONE" | "CAPTURING_PHASE" | "AT_TARGET" | "BUBBLING_PHASE"> & Pick<import("typed-event-target").TypedCustomEvent<ServiceAndRoomConnectionState, "controller-connection">, "type">;
/**
* Fires when the controller's connection state is updated.
*
* @category Events
*/
export declare class ControllerConnectionEvent extends ControllerConnectionEvent_base {
}
/**
* All events emitted by this controller.
*
* @category Internal
*/
export type AllMultiplayerControllerEvents<Action extends JsonCompatibleValue> = ControllerFrameEvent<Action> | ControllerRoomListEvent | ControllerClientEvent | ControllerConnectionEvent;
/**
* An all-in-one controller for singleplayer or lock-step multiplayer game state. Singleplayer mode
* requires no servers. Multiplayer mode requires a backend service running the
* {@link MultiplayerApi}.
*
* @category Main
*/
export declare class MultiplayerController<Action extends JsonCompatibleValue = any> extends ListenTarget<AllMultiplayerControllerEvents<Action>> {
protected readonly params: MultiplayerControllerParams<Action>;
/** All events emitted by this controller. */
static readonly events: {
ControllerFrameEvent: typeof ControllerFrameEvent;
ControllerRoomListEvent: typeof ControllerRoomListEvent;
ControllerClientEvent: typeof ControllerClientEvent;
ControllerConnectionEvent: typeof ControllerConnectionEvent;
};
/** All events emitted by this controller. */
readonly events: {
ControllerFrameEvent: typeof ControllerFrameEvent;
ControllerRoomListEvent: typeof ControllerRoomListEvent;
ControllerClientEvent: typeof ControllerClientEvent;
ControllerConnectionEvent: typeof ControllerConnectionEvent;
};
static readonly knownErrors: {
RoomRejectionError: typeof RoomRejectionError;
};
readonly knownErrors: {
RoomRejectionError: typeof RoomRejectionError;
};
/**
* Set to `false` to disable room updates, even when still not connected to a room in
* multiplayer mode.
*/
enableRoomUpdates: boolean;
/** Currently joined room id. If a room has not been joined yet, this will be empty. */
readonly roomId: Uuid | undefined;
/** The current connection state of the controller's connection to a backend service. */
readonly serviceConnectionState: ServiceAndRoomConnectionState['service'];
/** The current connection state of the controller's connection to a multiplayer room. */
readonly roomConnectionState: ServiceAndRoomConnectionState['room'];
/**
* Current WebRTC lock step connection with the room host (when not the host) or all room
* participants (when the host). This will only be initialized after calling
* {@link MultiplayerController.joinOrCreateRoom}.
*/
currentConnection: LockStepGameStateController | undefined;
/**
* Rooms that have rejected the current player, so the player doesn't keep trying to connect to
* them.
*/
protected rejectedRoomIds: Set<`${string}-${string}-${string}-${string}-${string}`>;
/** The current MultiplayerApi. This will be `undefined` if playing in single player. */
multiplayerApi: Promise<MultiplayerApi> | undefined;
/**
* Used to keep track of the room update interval. This will be set when the controller is
* constructed in multiplayer mode or when a room is left. This will be cleared when a room is
* joined or if the controller is destroyed.
*/
protected roomUpdateIntervalId: ReturnType<typeof globalThis.setInterval> | undefined;
/** This is populated if `.startMultiplayer` is called. */
protected multiplayerParams: Readonly<MultiplayerParams> | undefined;
/**
* Get the current client's WebRTC client id. This will return `undefined` if there is no
* current connection.
*/
getClientId(): Uuid | undefined;
/**
* Get all connected client ids.
*
* - For host clients, this will indicate how many member clients are connected to the host
* client, _not_ including the host itself.
* - For non-host clients, this will only list the host's client.
*
* For host clients, this does ont include the host client id whereas
* {@link MultiplayerController.getAllClientIds} does.
*/
getConnectedClientIds(): Uuid[];
/**
* Get all room client ids.
*
* - For host clients, this will indicate how many clients are connected to the room, including
* the host client itself.
* - For non-host clients, this will only list the host's client.
*
* For host clients, this includes the host client id whereas
* {@link MultiplayerController.getConnectedClientIds} does not.
*/
getAllClientIds(): Uuid[];
constructor(params: MultiplayerControllerParams<Action>);
/**
* Start multiplayer mode. This initializes {@link MultiplayerController.multiplayerApi} and
* {@link MultiplayerController.roomUpdateIntervalId}.
*/
startMultiplayer(params: Readonly<MultiplayerParams>): void;
/** Start singleplayer mode. */
startSingleplayer(): void;
/**
* Manually run the next frame.
*
* @throws Error if `frameDuration` has been set.
*/
runFrame(actions?: ReadonlyArray<Action> | undefined): void;
/** The current FPS of the data flow. */
getFps(): number;
/** Fire an action. This will be sent to all clients in the room so they can process it. */
act(actions: Action | ReadonlyArray<Action>): void;
/** Detects if this controller is the room host or not. */
isHost(): boolean;
/** Cleanup everything. */
destroy(): void;
/**
* Join or create a room.
*
* @throws `Error` if this controller is already connected to a room.
*/
joinOrCreateRoom(room: Readonly<RoomInput>): Promise<void>;
/** Leave the current room or single player connection. */
leaveRoom(): void;
/** Set the current connection state and fire listeners. */
protected updateConnectionState(state: Partial<ServiceAndRoomConnectionState>): void;
/** Starts polling the multiplayer server for room updates and fires listeners. */
protected startRoomInterval(): void;
}
export {};