import { ClockTimer as Clock } from '@colyseus/timer'; import type { Presence } from './presence/Presence.ts'; import type { Serializer } from './serializer/Serializer.ts'; import { type Type, Deferred } from './utils/Utils.ts'; import { type AuthContext, type Client, ClientArray, type ISendOptions, type MessageArgs } from './Transport.ts'; import { type RoomMethodName, type RoomException } from './errors/RoomExceptions.ts'; import { type StandardSchemaV1 } from './utils/StandardSchema.ts'; import { type MessageHandlerWithFormat as SharedMessageHandlerWithFormat, type MessageHandler as SharedMessageHandler, type Messages as SharedMessages } from '@colyseus/shared-types'; export declare const DEFAULT_SEAT_RESERVATION_TIME: number; export type SimulationCallback = (deltaTime: number) => void; export interface RoomOptions { state?: object; metadata?: any; client?: Client; } export type ExtractRoomState = T extends { state?: infer S extends object; } ? S : any; export type ExtractRoomMetadata = T extends { metadata?: infer M; } ? M : any; export type ExtractRoomClient = T extends { client?: infer C extends Client; } ? C : Client; export interface IBroadcastOptions extends ISendOptions { except?: Client | Client[]; } /** * Message handler with automatic type inference from format schema. * When a format is provided, the message type is automatically inferred from the schema. */ export type MessageHandlerWithFormat = SharedMessageHandlerWithFormat; export type MessageHandler = SharedMessageHandler; /** * A map of message types to message handlers. */ export type Messages = SharedMessages; /** * Helper function to create a validated message handler with automatic type inference. * * @example * ```typescript * messages = { * move: validate(z.object({ x: z.number(), y: z.number() }), (client, message) => { * // message.x and message.y are automatically typed as numbers * console.log(message.x, message.y); * }) * } * ``` */ export declare function validate(format: T, handler: (this: This, client: Client, message: StandardSchemaV1.InferOutput) => void): MessageHandlerWithFormat; export declare const RoomInternalState: { readonly CREATING: 0; readonly CREATED: 1; readonly DISPOSING: 2; }; export type RoomInternalState = (typeof RoomInternalState)[keyof typeof RoomInternalState]; export type OnCreateOptions> = Parameters['onCreate']>>[0]; /** * A Room class is meant to implement a game session, and/or serve as the communication channel * between a group of clients. * * - Rooms are created on demand during matchmaking by default * - Room classes must be exposed using `.define()` * * @example * ```typescript * class MyRoom extends Room<{ * state: MyState, * metadata: { difficulty: string }, * client: MyClient * }> { * // ... * } * ``` */ export declare class Room { #private; '~client': ExtractRoomClient; '~state': ExtractRoomState; '~metadata': ExtractRoomMetadata; /** * This property will change on these situations: * - The maximum number of allowed clients has been reached (`maxClients`) * - You manually locked, or unlocked the room using lock() or `unlock()`. * * @readonly */ get locked(): boolean; /** * Get the room's matchmaking metadata. */ get metadata(): ExtractRoomMetadata; /** * Set the room's matchmaking metadata. * * **Note**: This setter does NOT automatically persist. Use `setMatchmaking()` for automatic persistence. * * @example * ```typescript * class MyRoom extends Room<{ metadata: { difficulty: string; rating: number } }> { * async onCreate() { * this.metadata = { difficulty: "hard", rating: 1500 }; * } * } * ``` */ set metadata(meta: ExtractRoomMetadata); /** * The room listing cache for matchmaking. * @internal */ private _listing; /** * Timing events tied to the room instance. * Intervals and timeouts are cleared when the room is disposed. */ clock: Clock; /** * Maximum number of clients allowed to connect into the room. When room reaches this limit, * it is locked automatically. Unless the room was explicitly locked by you via `lock()` method, * the room will be unlocked as soon as a client disconnects from it. */ maxClients: number; /** * Automatically dispose the room when last client disconnects. * * @default true */ autoDispose: boolean; /** * Frequency to send the room state to connected clients, in milliseconds. * * @default 50ms (20fps) */ patchRate: number | null; /** * Maximum number of messages a client can send to the server per second. * If a client sends more messages than this, it will be disconnected. * * @default Infinity */ maxMessagesPerSecond: number; /** * The state instance you provided to `setState()`. */ state: ExtractRoomState; /** * The presence instance. Check Presence API for more details. * * @see [Presence API](https://docs.colyseus.io/server/presence) */ presence: Presence; /** * The array of connected clients. * * @see [Client instance](https://docs.colyseus.io/room#client) */ clients: ClientArray>; /** * Set the number of seconds a room can wait for a client to effectively join the room. * You should consider how long your `onAuth()` will have to wait for setting a different seat reservation time. * The default value is 15 seconds. You may set the `COLYSEUS_SEAT_RESERVATION_TIME` * environment variable if you'd like to change the seat reservation time globally. * * @default 15 seconds */ seatReservationTimeout: number; private _events; private _reservedSeats; private _reservedSeatTimeouts; private _reconnections; private _reconnectionAttempts; messages?: Messages; private onMessageEvents; private onMessageValidators; private onMessageFallbacks; private _serializer; private _afterNextPatchQueue; private _simulationInterval; private _internalState; private _lockedExplicitly; private _autoDisposeTimeout; constructor(); /** * This method is called by the MatchMaker before onCreate() * @internal */ private __init; /** * The name of the room you provided as first argument for `gameServer.define()`. * * @returns roomName string */ get roomName(): string; /** * Setting the name of the room. Overwriting this property is restricted. * * @param roomName */ set roomName(roomName: string); /** * A unique, auto-generated, 9-character-long id of the room. * You may replace `this.roomId` during `onCreate()`. * * @returns roomId string */ get roomId(): string; /** * Setting the roomId, is restricted in room lifetime except upon room creation. * * @param roomId * @returns roomId string */ set roomId(roomId: string); /** * This method is called before the latest version of the room's state is broadcasted to all clients. */ onBeforePatch?(state: ExtractRoomState): void | Promise; /** * This method is called when the room is created. * @param options - The options passed to the room when it is created. */ onCreate?(options: any): void | Promise; /** * This method is called when a client joins the room. * @param client - The client that joined the room. * @param options - The options passed to the client when it joined the room. * @param auth - The data returned by the `onAuth` method - (Deprecated: use `client.auth` instead) */ onJoin?(client: ExtractRoomClient, options?: any, auth?: any): void | Promise; /** * This method is called when a client leaves the room without consent. * You may allow the client to reconnect by calling `allowReconnection` within this method. * * @param client - The client that was dropped from the room. * @param code - The close code of the leave event. */ onDrop?(client: ExtractRoomClient, code?: number): void | Promise; /** * This method is called when a client reconnects to the room. * @param client - The client that reconnected to the room. */ onReconnect?(client: ExtractRoomClient): void | Promise; /** * This method is called when a client effectively leaves the room. * @param client - The client that left the room. * @param code - The close code of the leave event. */ onLeave?(client: ExtractRoomClient, code?: number): void | Promise; /** * This method is called when the room is disposed. */ onDispose?(): void | Promise; /** * Define a custom exception handler. * If defined, all lifecycle hooks will be wrapped by try/catch, and the exception will be forwarded to this method. * * These methods will be wrapped by try/catch: * - `onMessage` * - `onAuth` / `onJoin` / `onLeave` / `onCreate` / `onDispose` * - `clock.setTimeout` / `clock.setInterval` * - `setSimulationInterval` * * (Experimental: this feature is subject to change in the future - we're currently getting feedback to improve it) */ onUncaughtException?(error: RoomException, methodName: RoomMethodName): void; /** * This method is called before onJoin() - this is where you should authenticate the client * @param client - The client that is authenticating. * @param options - The options passed to the client when it is authenticating. * @param context - The authentication context, including the token and the client's IP address. * @returns The authentication result. * * @example * ```typescript * return { * userId: 123, * username: "John Doe", * email: "john.doe@example.com", * }; * ``` */ onAuth(client: Client, options: any, context: AuthContext): any | Promise; static onAuth(token: string, options: any, context: AuthContext): Promise; /** * This method is called during graceful shutdown of the server process * You may override this method to dispose the room in your own way. * * Once process reaches room count of 0, the room process will be terminated. */ onBeforeShutdown(): void; /** * devMode: When `devMode` is enabled, `onCacheRoom` method is called during * graceful shutdown. * * Implement this method to return custom data to be cached. `onRestoreRoom` * will be called with the data returned by `onCacheRoom` */ onCacheRoom?(): any; /** * devMode: When `devMode` is enabled, `onRestoreRoom` method is called during * process startup, with the data returned by the `onCacheRoom` method. */ onRestoreRoom?(cached?: any): void; /** * Returns whether the sum of connected clients and reserved seats exceeds maximum number of clients. * * @returns boolean */ hasReachedMaxClients(): boolean; /** * @deprecated Use `seatReservationTimeout=` instead. */ setSeatReservationTime(seconds: number): this; hasReservedSeat(sessionId: string, reconnectionToken?: string): boolean; checkReconnectionToken(reconnectionToken: string): string; /** * (Optional) Set a simulation interval that can change the state of the game. * The simulation interval is your game loop. * * @default 16.6ms (60fps) * * @param onTickCallback - You can implement your physics or world updates here! * This is a good place to update the room state. * @param delay - Interval delay on executing `onTickCallback` in milliseconds. */ setSimulationInterval(onTickCallback?: SimulationCallback, delay?: number): void; /** * @deprecated Use `.patchRate=` instead. */ setPatchRate(milliseconds: number | null): void; /** * @deprecated Use `.state =` instead. */ setState(newState: ExtractRoomState): void; setSerializer(serializer: Serializer>): void; setMetadata(meta: Partial>, persist?: boolean): Promise; setPrivate(bool?: boolean, persist?: boolean): Promise; /** * Update multiple matchmaking/listing properties at once with a single persist operation. * This is the recommended way to update room listing properties. * * @param updates - Object containing the properties to update * * @example * ```typescript * // Update multiple properties at once * await this.setMatchmaking({ * metadata: { difficulty: "hard", rating: 1500 }, * private: true, * locked: true, * maxClients: 10 * }); * ``` * * @example * ```typescript * // Update only metadata * await this.setMatchmaking({ * metadata: { status: "in_progress" } * }); * ``` * * @example * ```typescript * // Partial metadata update (merges with existing) * await this.setMatchmaking({ * metadata: { ...this.metadata, round: this.metadata.round + 1 } * }); * ``` */ setMatchmaking(updates: { metadata?: ExtractRoomMetadata; private?: boolean; locked?: boolean; maxClients?: number; unlisted?: boolean; [key: string]: any; }): Promise; /** * Lock the room. This prevents new clients from joining this room. */ lock(): Promise; /** * Unlock the room. This allows new clients to join this room, if maxClients is not reached. */ unlock(): Promise; /** * @deprecated Use `client.send(...)` instead. */ send(client: Client, type: string | number, message: any, options?: ISendOptions): void; /** * Broadcast a message to all connected clients. * @param type - The type of the message. * @param message - The message to broadcast. * @param options - The options for the broadcast. * * @example * ```typescript * this.broadcast('message', { message: 'Hello, world!' }); * ``` */ broadcast['~messages'] & string | number>(type: K, ...args: MessageArgs['~messages'][K], IBroadcastOptions>): void; /** * Broadcast bytes (UInt8Arrays) to a particular room */ broadcastBytes(type: string | number, message: Uint8Array, options: IBroadcastOptions): void; /** * Checks whether mutations have occurred in the state, and broadcast them to all connected clients. */ broadcastPatch(): boolean; /** * Register a message handler for a specific message type. * This method is used to handle messages sent by clients to the room. * @param messageType - The type of the message. * @param callback - The callback to call when the message is received. * @returns A function to unbind the callback. * * @example * ```typescript * this.onMessage('message', (client, message) => { * console.log(message); * }); * ``` * * @example * ```typescript * const unbind = this.onMessage('message', (client, message) => { * console.log(message); * }); * * // Unbind the callback when no longer needed * unbind(); * ``` */ onMessage>(messageType: '*', callback: (client: C, type: string | number, message: T) => void): any; onMessage>(messageType: string | number, callback: (client: C, message: T) => void): any; onMessage>(messageType: string | number, validationSchema: StandardSchemaV1, callback: (client: C, message: T) => void): any; onMessageBytes>(messageType: string | number, callback: (client: C, message: T) => void): any; onMessageBytes>(messageType: string | number, validationSchema: StandardSchemaV1, callback: (client: C, message: T) => void): any; /** * Disconnect all connected clients, and then dispose the room. * * @param closeCode WebSocket close code (default = 4000, which is a "consented leave") * @returns Promise */ disconnect(closeCode?: number): Promise; private _rejectPendingReconnections; private _onJoin; /** * Allow the specified client to reconnect into the room. Must be used inside `onLeave()` method. * If seconds is provided, the reconnection is going to be cancelled after the provided amount of seconds. * * @param client - The client that is allowed to reconnect into the room. * @param seconds - The time in seconds that the client is allowed to reconnect into the room. * * @returns Deferred - The differed is a promise like type. * This type can forcibly reject the promise by calling `.reject()`. * * @example * ```typescript * onDrop(client: Client, code: CloseCode) { * // Allow the client to reconnect into the room with a 15 seconds timeout. * this.allowReconnection(client, 15); * } * ``` */ allowReconnection(previousClient: Client, seconds: number | "manual"): Deferred; private resetAutoDisposeTimeout; private broadcastMessageType; private sendFullState; private _dequeueAfterPatchMessages; private _reserveSeat; private _reserveMultipleSeats; private _onMessage; private _onLeave; } /** * (WIP) Alternative, method-based room definition. * We should be able to define */ type RoomLifecycleMethods = 'messages' | 'onCreate' | 'onJoin' | 'onLeave' | 'onDispose' | 'onCacheRoom' | 'onRestoreRoom' | 'onDrop' | 'onReconnect' | 'onUncaughtException' | 'onAuth' | 'onBeforeShutdown' | 'onBeforePatch'; type DefineRoomOptions = Partial, RoomLifecycleMethods>> & { state?: ExtractRoomState | (() => ExtractRoomState); } & ThisType, RoomLifecycleMethods>> & ThisType>; export declare function room(options: DefineRoomOptions): typeof Room; export {};