import { ClientArray, type CompatClient, Room, room, validate } from './room'; export type Type = new (...args: unknown[]) => T; class MiniEventEmitter { private readonly handlers = new Map void>>(); on(event: string, handler: (...args: unknown[]) => void): this { let eventHandlers = this.handlers.get(event); if (!eventHandlers) { eventHandlers = new Set(); this.handlers.set(event, eventHandlers); } eventHandlers.add(handler); return this; } once(event: string, handler: (...args: unknown[]) => void): this { const wrapped = (...args: unknown[]) => { this.off(event, wrapped); handler(...args); }; return this.on(event, wrapped); } off(event: string, handler: (...args: unknown[]) => void): this { this.handlers.get(event)?.delete(handler); return this; } emit(event: string, ...args: unknown[]): boolean { const eventHandlers = this.handlers.get(event); if (!eventHandlers || eventHandlers.size === 0) return false; for (const handler of [...eventHandlers]) handler(...args); return true; } } export const ClientState = { JOINING: 0, JOINED: 1, RECONNECTING: 2, RECONNECTED: 3, LEAVING: 4, CLOSED: 5, } as const; export const Protocol = { JOIN_ROOM: 10, ERROR: 11, LEAVE_ROOM: 12, ROOM_DATA: 13, ROOM_STATE: 14, ROOM_STATE_PATCH: 15, ROOM_DATA_BYTES: 16, PING: 17, PONG: 18, } as const; export const ErrorCode = { MATCHMAKE_NO_HANDLER: 4210, MATCHMAKE_INVALID_CRITERIA: 4211, MATCHMAKE_INVALID_ROOM_ID: 4212, MATCHMAKE_UNHANDLED: 4213, MATCHMAKE_EXPIRED: 4214, AUTH_FAILED: 4215, APPLICATION_ERROR: 4216, } as const; export const CloseCode = { CONSENTED: 4000, DEVMODE_RESTART: 4010, } as const; export const RoomInternalState = { CREATING: 0, CREATED: 1, DISPOSING: 2, } as const; export class ServerError extends Error { constructor( readonly code: number, message?: string, ) { super(message ?? String(code)); this.name = 'ServerError'; } } export class OnCreateException extends Error {} export class OnAuthException extends Error {} export class OnJoinException extends Error {} export class OnLeaveException extends Error {} export class OnDisposeException extends Error {} export class OnMessageException extends Error {} export class SimulationIntervalException extends Error {} export class TimedEventException extends Error {} export class Deferred implements Promise { readonly [Symbol.toStringTag] = 'Promise'; private resolvePromise!: (value: T | PromiseLike) => void; private rejectPromise!: (reason?: unknown) => void; private readonly promise = new Promise((resolve, reject) => { this.resolvePromise = resolve; this.rejectPromise = reject; }); resolve(value: T | PromiseLike): void { this.resolvePromise(value); } reject(reason?: unknown): void { this.rejectPromise(reason); } // biome-ignore lint/suspicious/noThenProperty: Colyseus exposes Deferred as a Promise-compatible thenable. then( onfulfilled?: ((value: T) => TResult1 | PromiseLike) | null, onrejected?: ((reason: unknown) => TResult2 | PromiseLike) | null, ): Promise { return this.promise.then(onfulfilled, onrejected); } catch( onrejected?: ((reason: unknown) => TResult | PromiseLike) | null, ): Promise { return this.promise.catch(onrejected); } finally(onfinally?: (() => void) | null): Promise { return this.promise.finally(onfinally); } } export abstract class Transport { protocol?: string; server?: unknown; listen( _port?: number | string, _hostname?: string, _backlog?: number, _listeningListener?: () => void, ): this { return this; } shutdown(): void {} simulateLatency(_milliseconds?: number): void {} } export class LocalPresence { private readonly values = new Map(); private readonly subscriptions = new Map void>>(); async subscribe(topic: string, callback: (message: unknown) => void): Promise { let handlers = this.subscriptions.get(topic); if (!handlers) { handlers = new Set(); this.subscriptions.set(topic, handlers); } handlers.add(callback); } async unsubscribe(topic: string, callback?: (message: unknown) => void): Promise { if (callback) this.subscriptions.get(topic)?.delete(callback); else this.subscriptions.delete(topic); } publish(topic: string, message: unknown): void { for (const callback of this.subscriptions.get(topic) ?? []) callback(message); } async set(key: string, value: string): Promise { this.values.set(key, value); } async get(key: string): Promise { return this.values.get(key); } async del(key: string): Promise { this.values.delete(key); } async exists(key: string): Promise { return this.values.has(key); } async setex(key: string, value: string, _seconds: number): Promise { await this.set(key, value); } async expire(_key: string, _seconds: number): Promise { return true; } } export class Clock { setTimeout(callback: () => void, ms: number): ReturnType { return setTimeout(callback, ms); } setInterval(callback: () => void, ms: number): ReturnType { return setInterval(callback, ms); } clear(): void {} } export class Delayed { constructor( readonly callback: () => void = () => {}, readonly time = 0, ) {} clear(): void {} } export class RedisPresence extends LocalPresence { shutdown(): void {} setMaxListeners(): void {} } export class RedisDriver { rooms: RoomCache[] = []; async findOne(conditions: Partial): Promise { return this.rooms.find((room) => Object.entries(conditions).every( ([key, value]) => value === undefined || (room as unknown as Record)[key] === value, ), ); } query(conditions: Partial = {}): RoomCache[] { return this.rooms.filter((room) => Object.entries(conditions).every( ([key, value]) => value === undefined || (room as unknown as Record)[key] === value, ), ); } persist(room: RoomCache): boolean { const index = this.rooms.findIndex((candidate) => candidate.roomId === room.roomId); if (index >= 0) this.rooms[index] = room; else this.rooms.push(room); return true; } remove(roomId: string): boolean { const before = this.rooms.length; this.rooms = this.rooms.filter((room) => room.roomId !== roomId); return this.rooms.length !== before; } clear(): void { this.rooms = []; } shutdown(): void { this.clear(); } } export class LocalDriver extends RedisDriver {} export class SchemaSerializer { state?: T; reset(newState: T): void { this.state = newState; } } export interface RoomCache { name: string; roomId: string; processId: string; clients: number; maxClients: number; locked: boolean; private: boolean; metadata?: Metadata; } export interface SeatReservation { name: string; sessionId: string; roomId: string; publicAddress?: string; processId?: string; reconnectionToken?: string; devMode?: boolean; } export class RegisteredHandler extends MiniEventEmitter { '~room'!: RoomType; name = ''; filterOptions: string[] = []; sortOptions?: Record; realtimeListingEnabled = false; constructor( readonly klass: Type, readonly options?: unknown, ) { super(); } enableRealtimeListing(): this { this.realtimeListingEnabled = true; return this; } filterBy(options: string[]): this { this.filterOptions = options; return this; } sortBy(options: Record): this { this.sortOptions = options; return this; } getMetadataFromOptions(options: unknown): { metadata?: Record } { const record = asRecord(options); if (!record) return {}; const metadata: Record = {}; for (const key of this.filterOptions) { if (key in record) metadata[key] = record[key]; } return Object.keys(metadata).length > 0 ? { metadata } : {}; } getFilterOptions(options: unknown): Record { const record = asRecord(options); if (!record) return {}; const out: Record = {}; for (const key of this.filterOptions) { if (key in record) out[key] = record[key]; } return out; } } const handlers = new Map(); const rooms = new Map(); function asRecord(value: unknown): Record | undefined { return typeof value === 'object' && value !== null ? (value as Record) : undefined; } export const processId = 'p2p-colyseus-local'; export let publicAddress = 'localhost'; export const presence = new LocalPresence(); export const driver = { rooms: [...rooms.values()], clear: () => rooms.clear(), }; export async function setup( _presence?: LocalPresence, _driver?: unknown, address?: string, ): Promise { if (address) publicAddress = address; } export async function accept(): Promise {} export function defineRoomType( roomName: string, klass: Type, defaultOptions?: Parameters>[0], ): RegisteredHandler { const handler = new RegisteredHandler(klass, defaultOptions); handler.name = roomName; handlers.set(roomName, handler); return handler; } export function addRoomType(handler: RegisteredHandler): void { handlers.set(handler.name, handler); } export function removeRoomType(roomName: string): void { handlers.delete(roomName); } export function getAllHandlers(): Record { return Object.fromEntries(handlers); } export function getHandler(roomName: string): RegisteredHandler { const handler = handlers.get(roomName); if (!handler) throw new ServerError(404, `Room not defined: ${roomName}`); return handler; } export function getRoomClass(roomName: string): Type { return getHandler(roomName).klass; } export async function createRoom(roomName: string, clientOptions?: unknown): Promise { const handler = getHandler(roomName); const roomId = generateId(9); const cache: RoomCache = { name: roomName, roomId, processId, clients: 0, maxClients: Number.POSITIVE_INFINITY, locked: false, private: false, metadata: handler.getMetadataFromOptions(clientOptions).metadata, }; rooms.set(roomId, cache); return cache; } export const handleCreateRoom = createRoom; export async function getRoomById(roomId: string): Promise { const room = rooms.get(roomId); if (!room) throw new ServerError(404, `Room not found: ${roomId}`); return room; } export function getLocalRoomById(_roomId: string): Room | undefined { return undefined; } export async function query(conditions: Partial = {}): Promise { return [...rooms.values()].filter((room) => Object.entries(conditions).every( ([key, value]) => value === undefined || (room as unknown as Record)[key] === value, ), ); } export async function findOneRoomAvailable(roomName: string): Promise { const room = [...rooms.values()].find( (candidate) => candidate.name === roomName && !candidate.locked && !candidate.private, ); if (room) return room; return createRoom(roomName); } export async function reserveSeatFor( room: RoomCache, _options?: unknown, ): Promise { const sessionId = generateId(); room.clients++; return buildSeatReservation(room, sessionId); } export async function reserveMultipleSeatsFor( room: RoomCache, clientsData: Array<{ sessionId: string; options: unknown; auth: unknown }>, ): Promise { room.clients += clientsData.length; return clientsData.map(() => true); } export function buildSeatReservation(room: RoomCache, sessionId: string): SeatReservation { return { name: room.name, roomId: room.roomId, sessionId, processId: room.processId, publicAddress, }; } export async function create(roomName: string, clientOptions?: unknown): Promise { return reserveSeatFor(await createRoom(roomName, clientOptions), clientOptions); } export async function joinOrCreate( roomName: string, clientOptions?: unknown, ): Promise { return reserveSeatFor(await findOneRoomAvailable(roomName), clientOptions); } export async function join(roomName: string, clientOptions?: unknown): Promise { return joinOrCreate(roomName, clientOptions); } export async function joinById(roomId: string, clientOptions?: unknown): Promise { return reserveSeatFor(await getRoomById(roomId), clientOptions); } export async function reconnect(roomId: string, clientOptions?: unknown): Promise { const reservation = await joinById(roomId, clientOptions); reservation.reconnectionToken = String( asRecord(clientOptions)?.['reconnectionToken'] ?? reservation.sessionId, ); return reservation; } export async function remoteRoomCall(_roomId: string, _method: string): Promise { return undefined; } export function disconnectAll(): Promise[] { rooms.clear(); return []; } export async function gracefullyShutdown(): Promise { rooms.clear(); handlers.clear(); } export async function healthCheckAllProcesses(): Promise {} export async function healthCheckProcessId(): Promise { return true; } export const MatchMakerState = { INITIALIZING: 0, READY: 1, SHUTTING_DOWN: 2, } as const; export const state = MatchMakerState.READY; export const onReady = new Deferred(); onReady.resolve(); export function setHealthChecksEnabled(): void {} export const controller = { async invokeMethod(method: string, roomName: string, clientOptions?: unknown): Promise { if (method === 'joinOrCreate') return joinOrCreate(roomName, clientOptions); if (method === 'create') return create(roomName, clientOptions); if (method === 'join') return join(roomName, clientOptions); return undefined; }, }; export const stats = {}; export const matchMaker = { setup, accept, joinOrCreate, create, join, reconnect, joinById, query, findOneRoomAvailable, remoteRoomCall, defineRoomType, addRoomType, removeRoomType, getAllHandlers, getHandler, getRoomClass, createRoom, handleCreateRoom, getRoomById, getLocalRoomById, disconnectAll, gracefullyShutdown, reserveSeatFor, reserveMultipleSeatsFor, buildSeatReservation, healthCheckAllProcesses, healthCheckProcessId, setHealthChecksEnabled, controller, stats, processId, presence, driver, publicAddress, MatchMakerState, state, onReady, }; export const __globalEndpoints: unknown[] = []; export interface Router { routes?: unknown[]; } export function createRouter(): Router { return { routes: [] }; } export function createEndpoint( handler: (...args: unknown[]) => unknown, ): (...args: unknown[]) => unknown { return handler; } export function createInternalContext(context: unknown): unknown { return context; } export function createMiddleware( middleware: (...args: unknown[]) => unknown, ): (...args: unknown[]) => unknown { return middleware; } export function toNodeHandler( handler: (...args: unknown[]) => unknown, ): (...args: unknown[]) => unknown { return handler; } export function isDevMode(): boolean { return false; } export async function subscribeIPC(): Promise {} export async function requestFromIPC(): Promise { return undefined; } export interface ServerOptions { publicAddress?: string; transport?: Transport; presence?: LocalPresence; greet?: boolean; beforeListen?: () => Promise | void; } export class Server< RoomTypes extends Record = Record, > { '~rooms'!: RoomTypes; '~routes'!: unknown; transport: Transport; router = {}; readonly options: ServerOptions; // biome-ignore lint/suspicious/noConfusingVoidType: mirrors Colyseus's onShutdown contract — sync callbacks return nothing, async ones a promise private readonly shutdownCallbacks = new Set<() => void | Promise>(); // biome-ignore lint/suspicious/noConfusingVoidType: same contract as shutdownCallbacks private readonly beforeShutdownCallbacks = new Set<() => void | Promise>(); constructor(options: ServerOptions = {}) { this.options = options; this.transport = options.transport ?? new NoopTransport(); } async attach(options: ServerOptions): Promise { Object.assign(this.options, options); if (options.transport) this.transport = options.transport; } async listen( port: number | string, hostname?: string, backlog?: number, listeningListener?: () => void, ): Promise { await this.options.beforeListen?.(); this.transport.listen(port, hostname, backlog, listeningListener); listeningListener?.(); return this.transport; } define>( roomClass: T, defaultOptions?: unknown, ): RegisteredHandler>; define>( name: string, roomClass: T, defaultOptions?: unknown, ): RegisteredHandler>; define>( nameOrClass: string | T, roomClassOrOptions?: T | unknown, defaultOptions?: unknown, ): RegisteredHandler> { const name = typeof nameOrClass === 'string' ? nameOrClass : nameOrClass.name; const klass = (typeof nameOrClass === 'string' ? roomClassOrOptions : nameOrClass) as T; return defineRoomType( name, klass, defaultOptions ?? (typeof nameOrClass === 'string' ? undefined : roomClassOrOptions), ) as RegisteredHandler>; } removeRoomType(name: string): void { removeRoomType(name); } simulateLatency(milliseconds: number): void { this.transport.simulateLatency(milliseconds); } // biome-ignore lint/suspicious/noConfusingVoidType: mirrors Colyseus's onShutdown signature verbatim onShutdown(callback: () => void | Promise): void { this.shutdownCallbacks.add(callback); } // biome-ignore lint/suspicious/noConfusingVoidType: mirrors Colyseus's onBeforeShutdown signature verbatim onBeforeShutdown(callback: () => void | Promise): void { this.beforeShutdownCallbacks.add(callback); } async gracefullyShutdown(): Promise { for (const callback of this.beforeShutdownCallbacks) await callback(); await gracefullyShutdown(); for (const callback of this.shutdownCallbacks) await callback(); this.transport.shutdown(); } } export function defineRoom>( roomKlass: T, defaultOptions?: unknown, ): RegisteredHandler> { return new RegisteredHandler(roomKlass, defaultOptions) as RegisteredHandler>; } export function defineServer( options: ServerOptions & { rooms?: Record }, ): Server { const server = new Server(options); for (const [name, handler] of Object.entries(options.rooms ?? {})) { handler.name = name; addRoomType(handler); } return server; } export class NoopTransport extends Transport {} export class WebSocketClient {} export interface TransportOptions { server?: unknown; } export class WebSocketTransport extends Transport { constructor(readonly options: TransportOptions = {}) { super(); this.server = options.server; } } export function generateId(length = 9): string { const id = Math.random().toString(36).slice(2); return id.length >= length ? id.slice(0, length) : id.padEnd(length, '0'); } export function spliceOne(array: T[], index: number): boolean { if (index < 0 || index >= array.length) return false; array.splice(index, 1); return true; } export function getBearerToken(value?: string): string | undefined { return value?.startsWith('Bearer ') ? value.slice('Bearer '.length) : undefined; } export async function dynamicImport(path: string): Promise { return import(path) as Promise; } export function getMessageBytes(message: Uint8Array): Uint8Array { return message; } export function updateLobby(): void {} export function subscribeLobby(): () => void { return () => {}; } export function initializeRoomCache(cache: RoomCache): RoomCache { return cache; } export function connectClientToRoom(): Promise { return Promise.resolve(); } export function debugMatchMaking(): void {} export function debugMessage(): void {} export function debugPatch(): void {} export function debugError(): void {} export function debugConnection(): void {} export function debugDriver(): void {} export function debugPresence(): void {} export function debugAndPrintError(error: unknown): void { // biome-ignore lint/suspicious/noConsole: printing IS this compat shim's purpose (mirrors colyseus's debugAndPrintError) if (error) console.error(error); } export const logger = console; export class LobbyRoom extends Room {} export class RelayRoom extends Room {} export class QueueRoom extends Room {} // biome-ignore lint/complexity/noStaticOnlyClass: mirrors @colyseus/auth's Hash export shape — consumers call Hash.make/Hash.compare statically export class Hash { static async make(password: string): Promise { return password; } static async compare(password: string, hash: string): Promise { return password === hash; } } export const JWT = { async sign(payload: unknown): Promise { return btoa(JSON.stringify(payload)); }, async verify(token: string): Promise { return JSON.parse(atob(token)); }, }; export const auth = { settings: {}, prefix: '/auth', routes: {}, }; export function monitor(): { use?: (...args: unknown[]) => unknown } { return {}; } export function playground(): { use?: (...args: unknown[]) => unknown } { return {}; } export async function listen< RoomTypes extends Record = Record, >( optionsOrServer: Server | (ServerOptions & { rooms?: RoomTypes }), port = Number(process.env['PORT'] ?? 2567), ): Promise> { const server = optionsOrServer instanceof Server ? optionsOrServer : defineServer(optionsOrServer); await server.listen(port); return server as Server; } export async function getTransport(options: ServerOptions = {}): Promise { return options.transport ?? new NoopTransport(); } export { Room, ClientArray, room, validate, type CompatClient as Client };