@rpgjs/server 5.0.0-beta.5 → 5.0.0-beta.7

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,4 +1,4 @@
1
- import { a as isMapUpdateAuthorized, c as updateMap, h as injector, i as createMapUpdateHeaders, l as context, n as MAP_UPDATE_TOKEN_ENV, o as readMapUpdateToken, p as setInject, r as MAP_UPDATE_TOKEN_HEADER, s as resolveMapUpdateToken, t as provideServerModules } from "../module-BmvXIvlE.js";
1
+ import { a as isMapUpdateAuthorized, c as updateMap, h as injector, i as createMapUpdateHeaders, l as context, n as MAP_UPDATE_TOKEN_ENV, o as readMapUpdateToken, p as setInject, r as MAP_UPDATE_TOKEN_HEADER, s as resolveMapUpdateToken, t as provideServerModules } from "../module-5HOX9Ovu.js";
2
2
  //#region src/node/connection.ts
3
3
  function readEnvVariable(name) {
4
4
  const value = globalThis.process?.env?.[name];
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../src/node/connection.ts","../../src/node/room.ts","../../src/node/transport.ts"],"sourcesContent":["import type { RpgWebSocketConnection } from \"./types\";\n\ntype RuntimeProcess = {\n env?: Record<string, string | undefined>;\n};\n\nfunction readEnvVariable(name: string): string | undefined {\n const value = (globalThis as { process?: RuntimeProcess }).process?.env?.[name];\n return typeof value === \"string\" ? value : undefined;\n}\n\nexport class PartyConnection {\n public id: string;\n public uri: string;\n private _state: any = {};\n private messageQueue: Array<{ message: string; timestamp: number; sequence: number }> = [];\n private isProcessingQueue = false;\n private sequenceCounter = 0;\n private incomingQueue: Array<{\n message: string;\n timestamp: number;\n processor: (messages: string[]) => Promise<void>;\n }> = [];\n private isProcessingIncomingQueue = false;\n\n public static packetLossRate = parseFloat(readEnvVariable(\"RPGJS_PACKET_LOSS_RATE\") || \"0.1\");\n public static packetLossEnabled = readEnvVariable(\"RPGJS_ENABLE_PACKET_LOSS\") === \"true\";\n public static packetLossFilter = readEnvVariable(\"RPGJS_PACKET_LOSS_FILTER\") || \"\";\n public static bandwidthEnabled = readEnvVariable(\"RPGJS_ENABLE_BANDWIDTH\") === \"true\";\n public static bandwidthKbps = parseInt(readEnvVariable(\"RPGJS_BANDWIDTH_KBPS\") || \"100\");\n public static bandwidthFilter = readEnvVariable(\"RPGJS_BANDWIDTH_FILTER\") || \"\";\n public static latencyEnabled = readEnvVariable(\"RPGJS_ENABLE_LATENCY\") === \"true\";\n public static latencyMs = parseInt(readEnvVariable(\"RPGJS_LATENCY_MS\") || \"50\");\n public static latencyFilter = readEnvVariable(\"RPGJS_LATENCY_FILTER\") || \"\";\n\n constructor(private ws: RpgWebSocketConnection, id?: string, uri?: string) {\n this.id = id || this.generateId();\n this.uri = uri || \"\";\n }\n\n private generateId(): string {\n return `conn_${Date.now()}_${Math.random().toString(36).slice(2, 11)}`;\n }\n\n async send(data: any): Promise<void> {\n if (this.ws.readyState !== 1) {\n return;\n }\n\n const message = typeof data === \"string\" ? data : JSON.stringify(data);\n const timestamp = Date.now();\n const sequence = ++this.sequenceCounter;\n\n this.messageQueue.push({ message, timestamp, sequence });\n\n if (!this.isProcessingQueue) {\n void this.processMessageQueue();\n }\n }\n\n private async processMessageQueue(): Promise<void> {\n if (this.isProcessingQueue) {\n return;\n }\n this.isProcessingQueue = true;\n\n while (this.messageQueue.length > 0) {\n const queueItem = this.messageQueue.shift()!;\n\n if (this.shouldApplyLatency(queueItem.message)) {\n await this.waitUntil(queueItem.timestamp + PartyConnection.latencyMs);\n }\n\n if (PartyConnection.bandwidthEnabled && PartyConnection.bandwidthKbps > 0) {\n if (!PartyConnection.bandwidthFilter || queueItem.message.includes(PartyConnection.bandwidthFilter)) {\n const messageSizeBits = queueItem.message.length * 8;\n const transmissionTimeMs = (messageSizeBits / (PartyConnection.bandwidthKbps * 1000)) * 1000;\n const bandwidthDelayMs = Math.max(transmissionTimeMs, 10);\n console.log(\n `\\x1b[34m[BANDWIDTH SIMULATION]\\x1b[0m Connection ${this.id}: Message #${queueItem.sequence} transmission time: ${bandwidthDelayMs.toFixed(1)}ms`,\n );\n await new Promise((resolve) => setTimeout(resolve, bandwidthDelayMs));\n }\n }\n\n this.ws.send(queueItem.message);\n }\n\n this.isProcessingQueue = false;\n }\n\n private shouldApplyLatency(message: string): boolean {\n if (!PartyConnection.latencyEnabled || PartyConnection.latencyMs <= 0) {\n return false;\n }\n if (!PartyConnection.latencyFilter) {\n return true;\n }\n return message.includes(PartyConnection.latencyFilter);\n }\n\n private async waitUntil(targetTimestamp: number): Promise<void> {\n const delayMs = targetTimestamp - Date.now();\n if (delayMs <= 0) {\n return;\n }\n await new Promise((resolve) => setTimeout(resolve, delayMs));\n }\n\n close(): void {\n if (this.ws.readyState === 1) {\n this.ws.close();\n }\n }\n\n setState(value: any): void {\n this._state = value;\n }\n\n get state(): any {\n return this._state;\n }\n\n bufferIncoming(message: string, processor: (messages: string[]) => Promise<void>): void {\n this.incomingQueue.push({\n message,\n timestamp: Date.now(),\n processor,\n });\n\n if (!this.isProcessingIncomingQueue) {\n void this.processIncomingQueue();\n }\n }\n\n private async processIncomingQueue(): Promise<void> {\n if (this.isProcessingIncomingQueue) {\n return;\n }\n this.isProcessingIncomingQueue = true;\n\n while (this.incomingQueue.length > 0) {\n const item = this.incomingQueue.shift()!;\n if (this.shouldApplyLatency(item.message)) {\n await this.waitUntil(item.timestamp + PartyConnection.latencyMs);\n }\n try {\n await item.processor([item.message]);\n } catch (err) {\n console.error(\"Error processing incoming message:\", err);\n }\n }\n\n this.isProcessingIncomingQueue = false;\n }\n\n static configurePacketLoss(enabled: boolean, rate: number, filter?: string): void {\n PartyConnection.packetLossEnabled = enabled;\n PartyConnection.packetLossRate = Math.max(0, Math.min(1, rate));\n PartyConnection.packetLossFilter = filter || \"\";\n\n if (enabled && rate > 0) {\n const filterInfo = filter ? ` (filtered: \"${filter}\")` : \"\";\n console.log(`\\x1b[35m[PACKET LOSS SIMULATION]\\x1b[0m Enabled with ${(rate * 100).toFixed(1)}% loss rate${filterInfo}`);\n } else if (enabled) {\n console.log(\"\\x1b[35m[PACKET LOSS SIMULATION]\\x1b[0m Enabled but rate is 0% (no messages will be dropped)\");\n } else {\n console.log(\"\\x1b[35m[PACKET LOSS SIMULATION]\\x1b[0m Disabled\");\n }\n }\n\n static getPacketLossStatus(): { enabled: boolean; rate: number; filter: string } {\n return {\n enabled: PartyConnection.packetLossEnabled,\n rate: PartyConnection.packetLossRate,\n filter: PartyConnection.packetLossFilter,\n };\n }\n\n static configureBandwidth(enabled: boolean, kbps: number, filter?: string): void {\n PartyConnection.bandwidthEnabled = enabled;\n PartyConnection.bandwidthKbps = Math.max(1, kbps);\n PartyConnection.bandwidthFilter = filter || \"\";\n\n if (enabled && kbps > 0) {\n const filterInfo = filter ? ` (filtered: \"${filter}\")` : \"\";\n console.log(`\\x1b[35m[BANDWIDTH SIMULATION]\\x1b[0m Enabled with ${kbps} kbps bandwidth${filterInfo}`);\n } else if (enabled) {\n console.log(\"\\x1b[35m[BANDWIDTH SIMULATION]\\x1b[0m Enabled but bandwidth is 0 kbps (no delay will be applied)\");\n } else {\n console.log(\"\\x1b[35m[BANDWIDTH SIMULATION]\\x1b[0m Disabled\");\n }\n }\n\n static getBandwidthStatus(): { enabled: boolean; kbps: number; filter: string } {\n return {\n enabled: PartyConnection.bandwidthEnabled,\n kbps: PartyConnection.bandwidthKbps,\n filter: PartyConnection.bandwidthFilter,\n };\n }\n\n static configureLatency(enabled: boolean, ms: number, filter?: string): void {\n PartyConnection.latencyEnabled = enabled;\n PartyConnection.latencyMs = Math.max(0, ms);\n PartyConnection.latencyFilter = filter || \"\";\n\n if (enabled && ms > 0) {\n const filterInfo = filter ? ` (filtered: \"${filter}\")` : \"\";\n console.log(`\\x1b[35m[LATENCY SIMULATION]\\x1b[0m Enabled with ${ms}ms fixed latency${filterInfo}`);\n } else if (enabled) {\n console.log(\"\\x1b[35m[LATENCY SIMULATION]\\x1b[0m Enabled but latency is 0ms (no delay will be applied)\");\n } else {\n console.log(\"\\x1b[35m[LATENCY SIMULATION]\\x1b[0m Disabled\");\n }\n }\n\n static getLatencyStatus(): { enabled: boolean; ms: number; filter: string } {\n return {\n enabled: PartyConnection.latencyEnabled,\n ms: PartyConnection.latencyMs,\n filter: PartyConnection.latencyFilter,\n };\n }\n}\n\nexport function logNetworkSimulationStatus(): void {\n const packetLossStatus = PartyConnection.getPacketLossStatus();\n const bandwidthStatus = PartyConnection.getBandwidthStatus();\n const latencyStatus = PartyConnection.getLatencyStatus();\n\n if (packetLossStatus.enabled) {\n const filterInfo = packetLossStatus.filter ? ` (filter: \"${packetLossStatus.filter}\")` : \"\";\n console.log(\n `\\x1b[36m[NETWORK SIMULATION]\\x1b[0m Packet loss simulation: ${(packetLossStatus.rate * 100).toFixed(1)}% loss rate${filterInfo}`,\n );\n } else {\n console.log(\"\\x1b[36m[NETWORK SIMULATION]\\x1b[0m Packet loss simulation: disabled\");\n }\n\n if (bandwidthStatus.enabled) {\n const filterInfo = bandwidthStatus.filter ? ` (filter: \"${bandwidthStatus.filter}\")` : \"\";\n console.log(`\\x1b[36m[NETWORK SIMULATION]\\x1b[0m Bandwidth simulation: ${bandwidthStatus.kbps} kbps${filterInfo}`);\n } else {\n console.log(\"\\x1b[36m[NETWORK SIMULATION]\\x1b[0m Bandwidth simulation: disabled\");\n }\n\n if (latencyStatus.enabled) {\n const filterInfo = latencyStatus.filter ? ` (filter: \"${latencyStatus.filter}\")` : \"\";\n console.log(`\\x1b[36m[NETWORK SIMULATION]\\x1b[0m Latency simulation: ${latencyStatus.ms}ms ping${filterInfo}`);\n } else {\n console.log(\"\\x1b[36m[NETWORK SIMULATION]\\x1b[0m Latency simulation: disabled\");\n }\n}\n","import { PartyConnection } from \"./connection\";\n\nexport class PartyRoom {\n public id: string;\n public internalID: string;\n public env: Record<string, any> = {};\n public context: any = {};\n\n private connections = new Map<string, PartyConnection>();\n private storageData = new Map<string, any>();\n\n constructor(id: string) {\n this.id = id;\n this.internalID = `internal_${id}_${Date.now()}`;\n }\n\n async broadcast(message: any, except: string[] = []): Promise<void> {\n const data = typeof message === \"string\" ? message : JSON.stringify(message);\n const sendPromises: Promise<void>[] = [];\n\n for (const [connectionId, connection] of this.connections) {\n if (!except.includes(connectionId)) {\n sendPromises.push(connection.send(data));\n }\n }\n\n await Promise.all(sendPromises);\n }\n\n getConnection(id: string): PartyConnection | undefined {\n return this.connections.get(id);\n }\n\n getConnections(tag?: string): IterableIterator<PartyConnection> {\n void tag;\n return this.connections.values();\n }\n\n addConnection(connection: PartyConnection): void {\n this.connections.set(connection.id, connection);\n }\n\n removeConnection(connectionId: string): void {\n this.connections.delete(connectionId);\n }\n\n get storage() {\n return {\n put: async (key: string, value: any) => {\n this.storageData.set(key, value);\n },\n get: async <T = any>(key: string): Promise<T | undefined> => {\n return this.storageData.get(key) as T;\n },\n delete: async (key: string) => {\n this.storageData.delete(key);\n },\n list: async () => {\n return Array.from(this.storageData.entries());\n },\n };\n }\n}\n","import type { IncomingHttpHeaders, IncomingMessage, ServerResponse } from \"node:http\";\nimport type { Duplex } from \"node:stream\";\nimport { injector } from \"@signe/di\";\nimport { context as serverContext } from \"../core/context\";\nimport { setInject } from \"../core/inject\";\nimport { provideServerModules } from \"../module\";\nimport { PartyConnection } from \"./connection\";\nimport { createMapUpdateHeaders, resolveMapUpdateToken, updateMap } from \"./map\";\nimport { PartyRoom } from \"./room\";\nimport type {\n CreateRpgServerTransportOptions,\n HandleNodeRequestOptions,\n RpgTransportRequestLike,\n RpgTransportServer,\n RpgTransportServerConstructor,\n RpgWebSocketConnection,\n RpgWebSocketRequestLike,\n RpgWebSocketServer,\n SendMapUpdateOptions,\n} from \"./types\";\n\ntype PartiesFetchInit = {\n body?: any;\n headers?: HeadersInit | IncomingHttpHeaders | Map<string, string | undefined>;\n method?: string;\n};\n\nfunction normalizePathPrefix(path: string, fallback: string): string {\n const trimmed = (path || fallback).trim();\n if (!trimmed) {\n return fallback;\n }\n const prefixed = trimmed.startsWith(\"/\") ? trimmed : `/${trimmed}`;\n return prefixed !== \"/\" ? prefixed.replace(/\\/+$/, \"\") : prefixed;\n}\n\nfunction hasPathPrefix(pathname: string, prefix: string): boolean {\n return pathname === prefix || pathname.startsWith(`${prefix}/`);\n}\n\nfunction prependMountedPath(pathname: string, mountedPath?: string): string {\n if (!mountedPath) {\n return pathname;\n }\n const normalizedMountedPath = normalizePathPrefix(mountedPath, \"/\");\n if (hasPathPrefix(pathname, normalizedMountedPath)) {\n return pathname;\n }\n if (pathname === \"/\") {\n return normalizedMountedPath;\n }\n return `${normalizedMountedPath}${pathname.startsWith(\"/\") ? pathname : `/${pathname}`}`.replace(/\\/{2,}/g, \"/\");\n}\n\nfunction parseHttpRoute(pathname: string, partiesPath: string): { roomId: string; requestPath: string } | null {\n if (!hasPathPrefix(pathname, partiesPath)) {\n return null;\n }\n\n const remainder = pathname.slice(partiesPath.length);\n const segments = remainder.split(\"/\").filter(Boolean);\n if (segments.length < 2) {\n return null;\n }\n\n return {\n roomId: segments[0],\n requestPath: `/${segments.slice(1).join(\"/\")}`,\n };\n}\n\nfunction parseSocketRoute(pathname: string, partiesPath: string): { roomId: string } | null {\n if (!hasPathPrefix(pathname, partiesPath)) {\n return null;\n }\n\n const remainder = pathname.slice(partiesPath.length);\n const segments = remainder.split(\"/\").filter(Boolean);\n if (segments.length < 1) {\n return null;\n }\n\n return { roomId: segments[0] };\n}\n\nfunction toHeaders(\n input?: Headers | HeadersInit | IncomingHttpHeaders | Map<string, string | undefined>,\n): Headers {\n if (!input) {\n return new Headers();\n }\n if (input instanceof Headers) {\n return new Headers(input);\n }\n if (Array.isArray(input)) {\n return new Headers(input);\n }\n if (input instanceof Map) {\n const headers = new Headers();\n for (const [key, value] of input) {\n if (typeof value !== \"undefined\") {\n headers.set(key, value);\n }\n }\n return headers;\n }\n\n const headers = new Headers();\n Object.entries(input).forEach(([key, value]) => {\n if (Array.isArray(value)) {\n if (typeof value[0] !== \"undefined\") {\n headers.set(key, value[0]);\n }\n return;\n }\n if (typeof value !== \"undefined\") {\n headers.set(key, String(value));\n }\n });\n return headers;\n}\n\nfunction createRequestLike(url: string, method: string, headers: Headers, bodyText: string): RpgTransportRequestLike {\n return {\n url,\n method,\n headers,\n json: async () => {\n if (!bodyText) {\n return undefined;\n }\n return JSON.parse(bodyText);\n },\n text: async () => bodyText,\n };\n}\n\nasync function normalizeEngineResponse(result: any): Promise<Response> {\n if (result instanceof Response) {\n return result;\n }\n if (typeof result === \"string\") {\n return new Response(result, {\n status: 200,\n headers: {\n \"Content-Type\": \"text/plain\",\n },\n });\n }\n\n return new Response(JSON.stringify(result ?? {}), {\n status: 200,\n headers: {\n \"Content-Type\": \"application/json\",\n },\n });\n}\n\nasync function sendNodeResponse(res: ServerResponse, response: Response): Promise<void> {\n res.statusCode = response.status;\n response.headers.forEach((value, key) => {\n res.setHeader(key, value);\n });\n res.end(await response.text());\n}\n\nasync function readNodeBody(req: IncomingMessage): Promise<string> {\n return await new Promise<string>((resolve, reject) => {\n const chunks: Buffer[] = [];\n req.on(\"data\", (chunk: Buffer | string) => {\n chunks.push(typeof chunk === \"string\" ? Buffer.from(chunk) : chunk);\n });\n req.on(\"end\", () => {\n resolve(Buffer.concat(chunks).toString(\"utf8\"));\n });\n req.on(\"error\", reject);\n });\n}\n\nfunction resolveUrlFromSocketRequest(request: RpgWebSocketRequestLike): { headers: Headers; method?: string; rawUrl: string; url: URL } {\n const headers = toHeaders(request.headers);\n const host = headers.get(\"host\") || \"localhost\";\n const rawUrl = request.url || \"/\";\n const url = new URL(rawUrl, `http://${host}`);\n return {\n headers,\n method: request.method,\n rawUrl,\n url,\n };\n}\n\nfunction createConnectionContext(url: URL, headers: Headers, method?: string): any {\n const normalizedHeaders = new Map<string, string>();\n headers.forEach((value, key) => {\n normalizedHeaders.set(key.toLowerCase(), value);\n });\n\n return {\n request: {\n headers: {\n has: (name: string) => normalizedHeaders.has(name.toLowerCase()),\n get: (name: string) => normalizedHeaders.get(name.toLowerCase()),\n entries: () => normalizedHeaders.entries(),\n keys: () => normalizedHeaders.keys(),\n values: () => normalizedHeaders.values(),\n },\n method,\n url: url.toString(),\n },\n url,\n };\n}\n\nexport class RpgServerTransport {\n private serverContextInitialized = false;\n private partiesPath: string;\n private readonly initializeMaps: boolean;\n private readonly mapUpdateToken: string;\n private readonly tiledBasePaths?: string[];\n private readonly rooms = new Map<string, PartyRoom>();\n private readonly servers = new Map<string, RpgTransportServer>();\n private lastKnownHost = \"\";\n\n constructor(\n private readonly serverModule: RpgTransportServerConstructor,\n options: CreateRpgServerTransportOptions = {},\n ) {\n this.initializeMaps = options.initializeMaps ?? true;\n this.mapUpdateToken = resolveMapUpdateToken(options.mapUpdateToken);\n this.partiesPath = normalizePathPrefix(options.partiesPath || \"/parties/main\", \"/parties/main\");\n this.tiledBasePaths = options.tiledBasePaths;\n }\n\n private async ensureServerContext(): Promise<void> {\n if (this.serverContextInitialized) {\n return;\n }\n\n setInject(serverContext);\n await injector(serverContext, [provideServerModules([])]);\n this.serverContextInitialized = true;\n }\n\n getRoom(roomId: string): PartyRoom | undefined {\n return this.rooms.get(roomId);\n }\n\n getServer(roomId: string): RpgTransportServer | undefined {\n return this.servers.get(roomId);\n }\n\n private async ensureRoomAndServer(roomId: string, host?: string): Promise<{ room: PartyRoom; rpgServer: RpgTransportServer }> {\n if (host) {\n this.lastKnownHost = host;\n }\n\n let room = this.rooms.get(roomId);\n if (!room) {\n room = new PartyRoom(roomId);\n this.rooms.set(roomId, room);\n console.log(`Created new room: ${roomId}`);\n }\n\n let rpgServer = this.servers.get(roomId);\n if (!rpgServer) {\n await this.ensureServerContext();\n rpgServer = new this.serverModule(room);\n this.servers.set(roomId, rpgServer);\n console.log(`Created new server instance for room: ${roomId}`);\n\n if (typeof rpgServer.onStart === \"function\") {\n try {\n await rpgServer.onStart();\n console.log(`Server started for room: ${roomId}`);\n } catch (error) {\n console.error(`Error starting server for room ${roomId}:`, error);\n }\n }\n\n if (this.initializeMaps) {\n await updateMap(roomId, rpgServer, {\n host: host || this.lastKnownHost,\n mapUpdateToken: this.mapUpdateToken,\n tiledBasePaths: this.tiledBasePaths,\n });\n }\n }\n\n room.context.parties = this.buildPartiesContext();\n return { room, rpgServer };\n }\n\n private buildPartiesContext() {\n return {\n main: {\n get: async (targetRoomId: string) => {\n return {\n fetch: async (path: string, init?: PartiesFetchInit) => {\n const method = (init?.method || \"GET\").toUpperCase();\n const headers = toHeaders(init?.headers);\n const requestPath = path.startsWith(\"/\") ? path : `/${path}`;\n let bodyText = \"\";\n\n if (typeof init?.body === \"string\") {\n bodyText = init.body;\n } else if (typeof init?.body !== \"undefined\") {\n bodyText = JSON.stringify(init.body);\n }\n\n return this.dispatchRoomRequest(\n targetRoomId,\n createRequestLike(\n `http://localhost${this.partiesPath}/${targetRoomId}${requestPath}`,\n method,\n headers,\n bodyText,\n ),\n this.lastKnownHost,\n );\n },\n };\n },\n },\n } as any;\n }\n\n private async dispatchRoomRequest(roomId: string, requestLike: RpgTransportRequestLike, host?: string): Promise<Response> {\n const { room, rpgServer } = await this.ensureRoomAndServer(roomId, host);\n room.context.parties = this.buildPartiesContext();\n const result = await rpgServer.onRequest?.(requestLike);\n return normalizeEngineResponse(result);\n }\n\n async fetch(request: Request | string | URL, init?: RequestInit): Promise<Response> {\n const webRequest = request instanceof Request ? request : new Request(String(request), init);\n const url = new URL(webRequest.url);\n const route = parseHttpRoute(url.pathname, this.partiesPath);\n if (!route) {\n return new Response(JSON.stringify({ error: \"Not found\" }), {\n status: 404,\n headers: {\n \"Content-Type\": \"application/json\",\n },\n });\n }\n\n const bodyText = await webRequest.text();\n return this.dispatchRoomRequest(\n route.roomId,\n createRequestLike(webRequest.url, webRequest.method.toUpperCase(), toHeaders(webRequest.headers), bodyText),\n url.host,\n );\n }\n\n async updateMap(mapId: string, payload: any, options: SendMapUpdateOptions = {}): Promise<Response> {\n const roomId = mapId.startsWith(\"map-\") ? mapId : `map-${mapId}`;\n const headers = createMapUpdateHeaders(this.mapUpdateToken, options.headers);\n if (!headers.has(\"content-type\")) {\n headers.set(\"content-type\", \"application/json\");\n }\n\n return this.dispatchRoomRequest(\n roomId,\n createRequestLike(\n `http://localhost${this.partiesPath}/${roomId}/map/update`,\n \"POST\",\n headers,\n JSON.stringify(payload),\n ),\n options.host ?? this.lastKnownHost,\n );\n }\n\n async handleNodeRequest(\n req: IncomingMessage,\n res: ServerResponse,\n next?: () => void,\n options: HandleNodeRequestOptions = {},\n ): Promise<boolean> {\n try {\n const headers = toHeaders(req.headers);\n const host = headers.get(\"host\") || \"localhost\";\n const url = new URL(req.url || \"/\", `http://${host}`);\n const normalizedPathname = prependMountedPath(url.pathname, options.mountedPath);\n const normalizedUrl = new URL(url.toString());\n normalizedUrl.pathname = normalizedPathname;\n\n const route = parseHttpRoute(normalizedUrl.pathname, this.partiesPath);\n if (!route) {\n next?.();\n return false;\n }\n\n const bodyText = await readNodeBody(req);\n const response = await this.dispatchRoomRequest(\n route.roomId,\n createRequestLike(normalizedUrl.toString(), (req.method || \"GET\").toUpperCase(), headers, bodyText),\n host,\n );\n\n await sendNodeResponse(res, response);\n return true;\n } catch (error) {\n console.error(\"Error handling RPG-JS request:\", error);\n res.statusCode = 500;\n res.setHeader(\"Content-Type\", \"application/json\");\n res.end(JSON.stringify({ error: \"Internal server error\" }));\n return true;\n }\n }\n\n async acceptWebSocket(ws: RpgWebSocketConnection, request: RpgWebSocketRequestLike): Promise<boolean> {\n const normalizedRequest = resolveUrlFromSocketRequest(request);\n const route = parseSocketRoute(normalizedRequest.url.pathname, this.partiesPath);\n if (!route) {\n return false;\n }\n\n try {\n console.log(`WebSocket upgrade request: ${normalizedRequest.url.pathname}`);\n\n const queryParams = Object.fromEntries(normalizedRequest.url.searchParams.entries());\n console.log(`Room: ${route.roomId}, Query params:`, queryParams);\n\n const { room, rpgServer } = await this.ensureRoomAndServer(route.roomId, normalizedRequest.url.host);\n room.context.parties = this.buildPartiesContext();\n\n const connection = new PartyConnection(ws, queryParams._pk, normalizedRequest.rawUrl);\n room.addConnection(connection);\n\n console.log(`WebSocket connection established: ${connection.id} in room: ${route.roomId}`);\n\n let isClosed = false;\n const cleanup = async (logMessage?: string, error?: Error) => {\n if (isClosed) {\n return;\n }\n isClosed = true;\n if (logMessage) {\n console.log(logMessage);\n }\n if (error) {\n console.error(\"WebSocket error:\", error);\n }\n room.removeConnection(connection.id);\n await rpgServer.onClose?.(connection as any);\n };\n\n ws.on(\"message\", async (data: Buffer | string) => {\n try {\n const rawMessage = typeof data === \"string\" ? data : data.toString();\n\n if (PartyConnection.packetLossEnabled && PartyConnection.packetLossRate > 0) {\n if (!PartyConnection.packetLossFilter || rawMessage.includes(PartyConnection.packetLossFilter)) {\n const random = Math.random();\n if (random < PartyConnection.packetLossRate) {\n console.log(\n `\\x1b[31m[PACKET LOSS]\\x1b[0m Connection ${connection.id}: Server dropped an incoming packet (${(PartyConnection.packetLossRate * 100).toFixed(1)}% loss rate)`,\n );\n console.log(`\\x1b[33m[PACKET DATA]\\x1b[0m ${rawMessage.slice(0, 100)}${rawMessage.length > 100 ? \"...\" : \"\"}`);\n return;\n }\n }\n }\n\n connection.bufferIncoming(rawMessage, async (batch: string[]) => {\n for (const message of batch) {\n await rpgServer.onMessage?.(message, connection as any);\n }\n });\n } catch (error) {\n console.error(\"Error processing WebSocket message:\", error);\n }\n });\n\n ws.on(\"close\", () => {\n void cleanup(`WebSocket connection closed: ${connection.id} from room: ${route.roomId}`);\n });\n\n ws.on(\"error\", (error: Error) => {\n void cleanup(undefined, error);\n });\n\n if (typeof rpgServer.onConnect === \"function\") {\n await rpgServer.onConnect(\n connection as any,\n createConnectionContext(normalizedRequest.url, normalizedRequest.headers, normalizedRequest.method) as any,\n );\n }\n\n await connection.send({\n type: \"connected\",\n id: connection.id,\n message: \"Connected to RPG-JS server\",\n });\n\n return true;\n } catch (error) {\n console.error(\"Error establishing WebSocket connection:\", error);\n ws.close();\n return true;\n }\n }\n\n async handleUpgrade(\n wsServer: RpgWebSocketServer,\n request: IncomingMessage,\n socket: Duplex,\n head: Buffer,\n ): Promise<boolean> {\n const headers = toHeaders(request.headers);\n const host = headers.get(\"host\") || \"localhost\";\n const url = new URL(request.url || \"/\", `http://${host}`);\n if (!parseSocketRoute(url.pathname, this.partiesPath)) {\n return false;\n }\n\n wsServer.handleUpgrade(request, socket, head, (ws) => {\n void this.acceptWebSocket(ws, request);\n });\n\n return true;\n }\n}\n\nexport function createRpgServerTransport(\n serverModule: RpgTransportServerConstructor,\n options?: CreateRpgServerTransportOptions,\n): RpgServerTransport {\n return new RpgServerTransport(serverModule, options);\n}\n"],"mappings":";;AAMA,SAAS,gBAAgB,MAAkC;CACzD,MAAM,QAAS,WAA4C,SAAS,MAAM;AAC1E,QAAO,OAAO,UAAU,WAAW,QAAQ,KAAA;;AAG7C,IAAa,kBAAb,MAAa,gBAAgB;;wBAcI,WAAW,gBAAgB,yBAAyB,IAAI,MAAM;;;2BAC3D,gBAAgB,2BAA2B,KAAK;;;0BACjD,gBAAgB,2BAA2B,IAAI;;;0BAC/C,gBAAgB,yBAAyB,KAAK;;;uBACjD,SAAS,gBAAgB,uBAAuB,IAAI,MAAM;;;yBACxD,gBAAgB,yBAAyB,IAAI;;;wBAC9C,gBAAgB,uBAAuB,KAAK;;;mBACjD,SAAS,gBAAgB,mBAAmB,IAAI,KAAK;;;uBACjD,gBAAgB,uBAAuB,IAAI;;CAEzE,YAAY,IAAoC,IAAa,KAAc;AAAvD,OAAA,KAAA;gBArBE,EAAE;sBACgE,EAAE;2BAC9D;yBACF;uBAKrB,EAAE;mCAC6B;AAalC,OAAK,KAAK,MAAM,KAAK,YAAY;AACjC,OAAK,MAAM,OAAO;;CAGpB,aAA6B;AAC3B,SAAO,QAAQ,KAAK,KAAK,CAAC,GAAG,KAAK,QAAQ,CAAC,SAAS,GAAG,CAAC,MAAM,GAAG,GAAG;;CAGtE,MAAM,KAAK,MAA0B;AACnC,MAAI,KAAK,GAAG,eAAe,EACzB;EAGF,MAAM,UAAU,OAAO,SAAS,WAAW,OAAO,KAAK,UAAU,KAAK;EACtE,MAAM,YAAY,KAAK,KAAK;EAC5B,MAAM,WAAW,EAAE,KAAK;AAExB,OAAK,aAAa,KAAK;GAAE;GAAS;GAAW;GAAU,CAAC;AAExD,MAAI,CAAC,KAAK,kBACH,MAAK,qBAAqB;;CAInC,MAAc,sBAAqC;AACjD,MAAI,KAAK,kBACP;AAEF,OAAK,oBAAoB;AAEzB,SAAO,KAAK,aAAa,SAAS,GAAG;GACnC,MAAM,YAAY,KAAK,aAAa,OAAO;AAE3C,OAAI,KAAK,mBAAmB,UAAU,QAAQ,CAC5C,OAAM,KAAK,UAAU,UAAU,YAAY,gBAAgB,UAAU;AAGvE,OAAI,gBAAgB,oBAAoB,gBAAgB,gBAAgB;QAClE,CAAC,gBAAgB,mBAAmB,UAAU,QAAQ,SAAS,gBAAgB,gBAAgB,EAAE;KAEnG,MAAM,qBADkB,UAAU,QAAQ,SAAS,KACJ,gBAAgB,gBAAgB,OAAS;KACxF,MAAM,mBAAmB,KAAK,IAAI,oBAAoB,GAAG;AACzD,aAAQ,IACN,oDAAoD,KAAK,GAAG,aAAa,UAAU,SAAS,sBAAsB,iBAAiB,QAAQ,EAAE,CAAC,IAC/I;AACD,WAAM,IAAI,SAAS,YAAY,WAAW,SAAS,iBAAiB,CAAC;;;AAIzE,QAAK,GAAG,KAAK,UAAU,QAAQ;;AAGjC,OAAK,oBAAoB;;CAG3B,mBAA2B,SAA0B;AACnD,MAAI,CAAC,gBAAgB,kBAAkB,gBAAgB,aAAa,EAClE,QAAO;AAET,MAAI,CAAC,gBAAgB,cACnB,QAAO;AAET,SAAO,QAAQ,SAAS,gBAAgB,cAAc;;CAGxD,MAAc,UAAU,iBAAwC;EAC9D,MAAM,UAAU,kBAAkB,KAAK,KAAK;AAC5C,MAAI,WAAW,EACb;AAEF,QAAM,IAAI,SAAS,YAAY,WAAW,SAAS,QAAQ,CAAC;;CAG9D,QAAc;AACZ,MAAI,KAAK,GAAG,eAAe,EACzB,MAAK,GAAG,OAAO;;CAInB,SAAS,OAAkB;AACzB,OAAK,SAAS;;CAGhB,IAAI,QAAa;AACf,SAAO,KAAK;;CAGd,eAAe,SAAiB,WAAwD;AACtF,OAAK,cAAc,KAAK;GACtB;GACA,WAAW,KAAK,KAAK;GACrB;GACD,CAAC;AAEF,MAAI,CAAC,KAAK,0BACH,MAAK,sBAAsB;;CAIpC,MAAc,uBAAsC;AAClD,MAAI,KAAK,0BACP;AAEF,OAAK,4BAA4B;AAEjC,SAAO,KAAK,cAAc,SAAS,GAAG;GACpC,MAAM,OAAO,KAAK,cAAc,OAAO;AACvC,OAAI,KAAK,mBAAmB,KAAK,QAAQ,CACvC,OAAM,KAAK,UAAU,KAAK,YAAY,gBAAgB,UAAU;AAElE,OAAI;AACF,UAAM,KAAK,UAAU,CAAC,KAAK,QAAQ,CAAC;YAC7B,KAAK;AACZ,YAAQ,MAAM,sCAAsC,IAAI;;;AAI5D,OAAK,4BAA4B;;CAGnC,OAAO,oBAAoB,SAAkB,MAAc,QAAuB;AAChF,kBAAgB,oBAAoB;AACpC,kBAAgB,iBAAiB,KAAK,IAAI,GAAG,KAAK,IAAI,GAAG,KAAK,CAAC;AAC/D,kBAAgB,mBAAmB,UAAU;AAE7C,MAAI,WAAW,OAAO,GAAG;GACvB,MAAM,aAAa,SAAS,gBAAgB,OAAO,MAAM;AACzD,WAAQ,IAAI,yDAAyD,OAAO,KAAK,QAAQ,EAAE,CAAC,aAAa,aAAa;aAC7G,QACT,SAAQ,IAAI,+FAA+F;MAE3G,SAAQ,IAAI,mDAAmD;;CAInE,OAAO,sBAA0E;AAC/E,SAAO;GACL,SAAS,gBAAgB;GACzB,MAAM,gBAAgB;GACtB,QAAQ,gBAAgB;GACzB;;CAGH,OAAO,mBAAmB,SAAkB,MAAc,QAAuB;AAC/E,kBAAgB,mBAAmB;AACnC,kBAAgB,gBAAgB,KAAK,IAAI,GAAG,KAAK;AACjD,kBAAgB,kBAAkB,UAAU;AAE5C,MAAI,WAAW,OAAO,GAAG;GACvB,MAAM,aAAa,SAAS,gBAAgB,OAAO,MAAM;AACzD,WAAQ,IAAI,sDAAsD,KAAK,iBAAiB,aAAa;aAC5F,QACT,SAAQ,IAAI,mGAAmG;MAE/G,SAAQ,IAAI,iDAAiD;;CAIjE,OAAO,qBAAyE;AAC9E,SAAO;GACL,SAAS,gBAAgB;GACzB,MAAM,gBAAgB;GACtB,QAAQ,gBAAgB;GACzB;;CAGH,OAAO,iBAAiB,SAAkB,IAAY,QAAuB;AAC3E,kBAAgB,iBAAiB;AACjC,kBAAgB,YAAY,KAAK,IAAI,GAAG,GAAG;AAC3C,kBAAgB,gBAAgB,UAAU;AAE1C,MAAI,WAAW,KAAK,GAAG;GACrB,MAAM,aAAa,SAAS,gBAAgB,OAAO,MAAM;AACzD,WAAQ,IAAI,oDAAoD,GAAG,kBAAkB,aAAa;aACzF,QACT,SAAQ,IAAI,4FAA4F;MAExG,SAAQ,IAAI,+CAA+C;;CAI/D,OAAO,mBAAqE;AAC1E,SAAO;GACL,SAAS,gBAAgB;GACzB,IAAI,gBAAgB;GACpB,QAAQ,gBAAgB;GACzB;;;AAIL,SAAgB,6BAAmC;CACjD,MAAM,mBAAmB,gBAAgB,qBAAqB;CAC9D,MAAM,kBAAkB,gBAAgB,oBAAoB;CAC5D,MAAM,gBAAgB,gBAAgB,kBAAkB;AAExD,KAAI,iBAAiB,SAAS;EAC5B,MAAM,aAAa,iBAAiB,SAAS,cAAc,iBAAiB,OAAO,MAAM;AACzF,UAAQ,IACN,gEAAgE,iBAAiB,OAAO,KAAK,QAAQ,EAAE,CAAC,aAAa,aACtH;OAED,SAAQ,IAAI,uEAAuE;AAGrF,KAAI,gBAAgB,SAAS;EAC3B,MAAM,aAAa,gBAAgB,SAAS,cAAc,gBAAgB,OAAO,MAAM;AACvF,UAAQ,IAAI,6DAA6D,gBAAgB,KAAK,OAAO,aAAa;OAElH,SAAQ,IAAI,qEAAqE;AAGnF,KAAI,cAAc,SAAS;EACzB,MAAM,aAAa,cAAc,SAAS,cAAc,cAAc,OAAO,MAAM;AACnF,UAAQ,IAAI,2DAA2D,cAAc,GAAG,SAAS,aAAa;OAE9G,SAAQ,IAAI,mEAAmE;;;;ACzPnF,IAAa,YAAb,MAAuB;CASrB,YAAY,IAAY;aANU,EAAE;iBACd,EAAE;qCAEF,IAAI,KAA8B;qCAClC,IAAI,KAAkB;AAG1C,OAAK,KAAK;AACV,OAAK,aAAa,YAAY,GAAG,GAAG,KAAK,KAAK;;CAGhD,MAAM,UAAU,SAAc,SAAmB,EAAE,EAAiB;EAClE,MAAM,OAAO,OAAO,YAAY,WAAW,UAAU,KAAK,UAAU,QAAQ;EAC5E,MAAM,eAAgC,EAAE;AAExC,OAAK,MAAM,CAAC,cAAc,eAAe,KAAK,YAC5C,KAAI,CAAC,OAAO,SAAS,aAAa,CAChC,cAAa,KAAK,WAAW,KAAK,KAAK,CAAC;AAI5C,QAAM,QAAQ,IAAI,aAAa;;CAGjC,cAAc,IAAyC;AACrD,SAAO,KAAK,YAAY,IAAI,GAAG;;CAGjC,eAAe,KAAiD;AAE9D,SAAO,KAAK,YAAY,QAAQ;;CAGlC,cAAc,YAAmC;AAC/C,OAAK,YAAY,IAAI,WAAW,IAAI,WAAW;;CAGjD,iBAAiB,cAA4B;AAC3C,OAAK,YAAY,OAAO,aAAa;;CAGvC,IAAI,UAAU;AACZ,SAAO;GACL,KAAK,OAAO,KAAa,UAAe;AACtC,SAAK,YAAY,IAAI,KAAK,MAAM;;GAElC,KAAK,OAAgB,QAAwC;AAC3D,WAAO,KAAK,YAAY,IAAI,IAAI;;GAElC,QAAQ,OAAO,QAAgB;AAC7B,SAAK,YAAY,OAAO,IAAI;;GAE9B,MAAM,YAAY;AAChB,WAAO,MAAM,KAAK,KAAK,YAAY,SAAS,CAAC;;GAEhD;;;;;ACjCL,SAAS,oBAAoB,MAAc,UAA0B;CACnE,MAAM,WAAW,QAAQ,UAAU,MAAM;AACzC,KAAI,CAAC,QACH,QAAO;CAET,MAAM,WAAW,QAAQ,WAAW,IAAI,GAAG,UAAU,IAAI;AACzD,QAAO,aAAa,MAAM,SAAS,QAAQ,QAAQ,GAAG,GAAG;;AAG3D,SAAS,cAAc,UAAkB,QAAyB;AAChE,QAAO,aAAa,UAAU,SAAS,WAAW,GAAG,OAAO,GAAG;;AAGjE,SAAS,mBAAmB,UAAkB,aAA8B;AAC1E,KAAI,CAAC,YACH,QAAO;CAET,MAAM,wBAAwB,oBAAoB,aAAa,IAAI;AACnE,KAAI,cAAc,UAAU,sBAAsB,CAChD,QAAO;AAET,KAAI,aAAa,IACf,QAAO;AAET,QAAO,GAAG,wBAAwB,SAAS,WAAW,IAAI,GAAG,WAAW,IAAI,aAAa,QAAQ,WAAW,IAAI;;AAGlH,SAAS,eAAe,UAAkB,aAAqE;AAC7G,KAAI,CAAC,cAAc,UAAU,YAAY,CACvC,QAAO;CAIT,MAAM,WADY,SAAS,MAAM,YAAY,OAC5B,CAAU,MAAM,IAAI,CAAC,OAAO,QAAQ;AACrD,KAAI,SAAS,SAAS,EACpB,QAAO;AAGT,QAAO;EACL,QAAQ,SAAS;EACjB,aAAa,IAAI,SAAS,MAAM,EAAE,CAAC,KAAK,IAAI;EAC7C;;AAGH,SAAS,iBAAiB,UAAkB,aAAgD;AAC1F,KAAI,CAAC,cAAc,UAAU,YAAY,CACvC,QAAO;CAIT,MAAM,WADY,SAAS,MAAM,YAAY,OAC5B,CAAU,MAAM,IAAI,CAAC,OAAO,QAAQ;AACrD,KAAI,SAAS,SAAS,EACpB,QAAO;AAGT,QAAO,EAAE,QAAQ,SAAS,IAAI;;AAGhC,SAAS,UACP,OACS;AACT,KAAI,CAAC,MACH,QAAO,IAAI,SAAS;AAEtB,KAAI,iBAAiB,QACnB,QAAO,IAAI,QAAQ,MAAM;AAE3B,KAAI,MAAM,QAAQ,MAAM,CACtB,QAAO,IAAI,QAAQ,MAAM;AAE3B,KAAI,iBAAiB,KAAK;EACxB,MAAM,UAAU,IAAI,SAAS;AAC7B,OAAK,MAAM,CAAC,KAAK,UAAU,MACzB,KAAI,OAAO,UAAU,YACnB,SAAQ,IAAI,KAAK,MAAM;AAG3B,SAAO;;CAGT,MAAM,UAAU,IAAI,SAAS;AAC7B,QAAO,QAAQ,MAAM,CAAC,SAAS,CAAC,KAAK,WAAW;AAC9C,MAAI,MAAM,QAAQ,MAAM,EAAE;AACxB,OAAI,OAAO,MAAM,OAAO,YACtB,SAAQ,IAAI,KAAK,MAAM,GAAG;AAE5B;;AAEF,MAAI,OAAO,UAAU,YACnB,SAAQ,IAAI,KAAK,OAAO,MAAM,CAAC;GAEjC;AACF,QAAO;;AAGT,SAAS,kBAAkB,KAAa,QAAgB,SAAkB,UAA2C;AACnH,QAAO;EACL;EACA;EACA;EACA,MAAM,YAAY;AAChB,OAAI,CAAC,SACH;AAEF,UAAO,KAAK,MAAM,SAAS;;EAE7B,MAAM,YAAY;EACnB;;AAGH,eAAe,wBAAwB,QAAgC;AACrE,KAAI,kBAAkB,SACpB,QAAO;AAET,KAAI,OAAO,WAAW,SACpB,QAAO,IAAI,SAAS,QAAQ;EAC1B,QAAQ;EACR,SAAS,EACP,gBAAgB,cACjB;EACF,CAAC;AAGJ,QAAO,IAAI,SAAS,KAAK,UAAU,UAAU,EAAE,CAAC,EAAE;EAChD,QAAQ;EACR,SAAS,EACP,gBAAgB,oBACjB;EACF,CAAC;;AAGJ,eAAe,iBAAiB,KAAqB,UAAmC;AACtF,KAAI,aAAa,SAAS;AAC1B,UAAS,QAAQ,SAAS,OAAO,QAAQ;AACvC,MAAI,UAAU,KAAK,MAAM;GACzB;AACF,KAAI,IAAI,MAAM,SAAS,MAAM,CAAC;;AAGhC,eAAe,aAAa,KAAuC;AACjE,QAAO,MAAM,IAAI,SAAiB,SAAS,WAAW;EACpD,MAAM,SAAmB,EAAE;AAC3B,MAAI,GAAG,SAAS,UAA2B;AACzC,UAAO,KAAK,OAAO,UAAU,WAAW,OAAO,KAAK,MAAM,GAAG,MAAM;IACnE;AACF,MAAI,GAAG,aAAa;AAClB,WAAQ,OAAO,OAAO,OAAO,CAAC,SAAS,OAAO,CAAC;IAC/C;AACF,MAAI,GAAG,SAAS,OAAO;GACvB;;AAGJ,SAAS,4BAA4B,SAAmG;CACtI,MAAM,UAAU,UAAU,QAAQ,QAAQ;CAC1C,MAAM,OAAO,QAAQ,IAAI,OAAO,IAAI;CACpC,MAAM,SAAS,QAAQ,OAAO;CAC9B,MAAM,MAAM,IAAI,IAAI,QAAQ,UAAU,OAAO;AAC7C,QAAO;EACL;EACA,QAAQ,QAAQ;EAChB;EACA;EACD;;AAGH,SAAS,wBAAwB,KAAU,SAAkB,QAAsB;CACjF,MAAM,oCAAoB,IAAI,KAAqB;AACnD,SAAQ,SAAS,OAAO,QAAQ;AAC9B,oBAAkB,IAAI,IAAI,aAAa,EAAE,MAAM;GAC/C;AAEF,QAAO;EACL,SAAS;GACP,SAAS;IACP,MAAM,SAAiB,kBAAkB,IAAI,KAAK,aAAa,CAAC;IAChE,MAAM,SAAiB,kBAAkB,IAAI,KAAK,aAAa,CAAC;IAChE,eAAe,kBAAkB,SAAS;IAC1C,YAAY,kBAAkB,MAAM;IACpC,cAAc,kBAAkB,QAAQ;IACzC;GACD;GACA,KAAK,IAAI,UAAU;GACpB;EACD;EACD;;AAGH,IAAa,qBAAb,MAAgC;CAU9B,YACE,cACA,UAA2C,EAAE,EAC7C;AAFiB,OAAA,eAAA;kCAVgB;+BAKV,IAAI,KAAwB;iCAC1B,IAAI,KAAiC;uBACxC;AAMtB,OAAK,iBAAiB,QAAQ,kBAAkB;AAChD,OAAK,iBAAiB,sBAAsB,QAAQ,eAAe;AACnE,OAAK,cAAc,oBAAoB,QAAQ,eAAe,iBAAiB,gBAAgB;AAC/F,OAAK,iBAAiB,QAAQ;;CAGhC,MAAc,sBAAqC;AACjD,MAAI,KAAK,yBACP;AAGF,YAAU,QAAc;AACxB,QAAM,SAAS,SAAe,CAAC,qBAAqB,EAAE,CAAC,CAAC,CAAC;AACzD,OAAK,2BAA2B;;CAGlC,QAAQ,QAAuC;AAC7C,SAAO,KAAK,MAAM,IAAI,OAAO;;CAG/B,UAAU,QAAgD;AACxD,SAAO,KAAK,QAAQ,IAAI,OAAO;;CAGjC,MAAc,oBAAoB,QAAgB,MAA4E;AAC5H,MAAI,KACF,MAAK,gBAAgB;EAGvB,IAAI,OAAO,KAAK,MAAM,IAAI,OAAO;AACjC,MAAI,CAAC,MAAM;AACT,UAAO,IAAI,UAAU,OAAO;AAC5B,QAAK,MAAM,IAAI,QAAQ,KAAK;AAC5B,WAAQ,IAAI,qBAAqB,SAAS;;EAG5C,IAAI,YAAY,KAAK,QAAQ,IAAI,OAAO;AACxC,MAAI,CAAC,WAAW;AACd,SAAM,KAAK,qBAAqB;AAChC,eAAY,IAAI,KAAK,aAAa,KAAK;AACvC,QAAK,QAAQ,IAAI,QAAQ,UAAU;AACnC,WAAQ,IAAI,yCAAyC,SAAS;AAE9D,OAAI,OAAO,UAAU,YAAY,WAC/B,KAAI;AACF,UAAM,UAAU,SAAS;AACzB,YAAQ,IAAI,4BAA4B,SAAS;YAC1C,OAAO;AACd,YAAQ,MAAM,kCAAkC,OAAO,IAAI,MAAM;;AAIrE,OAAI,KAAK,eACP,OAAM,UAAU,QAAQ,WAAW;IACjC,MAAM,QAAQ,KAAK;IACnB,gBAAgB,KAAK;IACrB,gBAAgB,KAAK;IACtB,CAAC;;AAIN,OAAK,QAAQ,UAAU,KAAK,qBAAqB;AACjD,SAAO;GAAE;GAAM;GAAW;;CAG5B,sBAA8B;AAC5B,SAAO,EACL,MAAM,EACJ,KAAK,OAAO,iBAAyB;AACnC,UAAO,EACL,OAAO,OAAO,MAAc,SAA4B;IACtD,MAAM,UAAU,MAAM,UAAU,OAAO,aAAa;IACpD,MAAM,UAAU,UAAU,MAAM,QAAQ;IACxC,MAAM,cAAc,KAAK,WAAW,IAAI,GAAG,OAAO,IAAI;IACtD,IAAI,WAAW;AAEf,QAAI,OAAO,MAAM,SAAS,SACxB,YAAW,KAAK;aACP,OAAO,MAAM,SAAS,YAC/B,YAAW,KAAK,UAAU,KAAK,KAAK;AAGtC,WAAO,KAAK,oBACV,cACA,kBACE,mBAAmB,KAAK,YAAY,GAAG,eAAe,eACtD,QACA,SACA,SACD,EACD,KAAK,cACN;MAEJ;KAEJ,EACF;;CAGH,MAAc,oBAAoB,QAAgB,aAAsC,MAAkC;EACxH,MAAM,EAAE,MAAM,cAAc,MAAM,KAAK,oBAAoB,QAAQ,KAAK;AACxE,OAAK,QAAQ,UAAU,KAAK,qBAAqB;AAEjD,SAAO,wBAAwB,MADV,UAAU,YAAY,YAAY,CACjB;;CAGxC,MAAM,MAAM,SAAiC,MAAuC;EAClF,MAAM,aAAa,mBAAmB,UAAU,UAAU,IAAI,QAAQ,OAAO,QAAQ,EAAE,KAAK;EAC5F,MAAM,MAAM,IAAI,IAAI,WAAW,IAAI;EACnC,MAAM,QAAQ,eAAe,IAAI,UAAU,KAAK,YAAY;AAC5D,MAAI,CAAC,MACH,QAAO,IAAI,SAAS,KAAK,UAAU,EAAE,OAAO,aAAa,CAAC,EAAE;GAC1D,QAAQ;GACR,SAAS,EACP,gBAAgB,oBACjB;GACF,CAAC;EAGJ,MAAM,WAAW,MAAM,WAAW,MAAM;AACxC,SAAO,KAAK,oBACV,MAAM,QACN,kBAAkB,WAAW,KAAK,WAAW,OAAO,aAAa,EAAE,UAAU,WAAW,QAAQ,EAAE,SAAS,EAC3G,IAAI,KACL;;CAGH,MAAM,UAAU,OAAe,SAAc,UAAgC,EAAE,EAAqB;EAClG,MAAM,SAAS,MAAM,WAAW,OAAO,GAAG,QAAQ,OAAO;EACzD,MAAM,UAAU,uBAAuB,KAAK,gBAAgB,QAAQ,QAAQ;AAC5E,MAAI,CAAC,QAAQ,IAAI,eAAe,CAC9B,SAAQ,IAAI,gBAAgB,mBAAmB;AAGjD,SAAO,KAAK,oBACV,QACA,kBACE,mBAAmB,KAAK,YAAY,GAAG,OAAO,cAC9C,QACA,SACA,KAAK,UAAU,QAAQ,CACxB,EACD,QAAQ,QAAQ,KAAK,cACtB;;CAGH,MAAM,kBACJ,KACA,KACA,MACA,UAAoC,EAAE,EACpB;AAClB,MAAI;GACF,MAAM,UAAU,UAAU,IAAI,QAAQ;GACtC,MAAM,OAAO,QAAQ,IAAI,OAAO,IAAI;GACpC,MAAM,MAAM,IAAI,IAAI,IAAI,OAAO,KAAK,UAAU,OAAO;GACrD,MAAM,qBAAqB,mBAAmB,IAAI,UAAU,QAAQ,YAAY;GAChF,MAAM,gBAAgB,IAAI,IAAI,IAAI,UAAU,CAAC;AAC7C,iBAAc,WAAW;GAEzB,MAAM,QAAQ,eAAe,cAAc,UAAU,KAAK,YAAY;AACtE,OAAI,CAAC,OAAO;AACV,YAAQ;AACR,WAAO;;GAGT,MAAM,WAAW,MAAM,aAAa,IAAI;AAOxC,SAAM,iBAAiB,KAAK,MANL,KAAK,oBAC1B,MAAM,QACN,kBAAkB,cAAc,UAAU,GAAG,IAAI,UAAU,OAAO,aAAa,EAAE,SAAS,SAAS,EACnG,KACD,CAEoC;AACrC,UAAO;WACA,OAAO;AACd,WAAQ,MAAM,kCAAkC,MAAM;AACtD,OAAI,aAAa;AACjB,OAAI,UAAU,gBAAgB,mBAAmB;AACjD,OAAI,IAAI,KAAK,UAAU,EAAE,OAAO,yBAAyB,CAAC,CAAC;AAC3D,UAAO;;;CAIX,MAAM,gBAAgB,IAA4B,SAAoD;EACpG,MAAM,oBAAoB,4BAA4B,QAAQ;EAC9D,MAAM,QAAQ,iBAAiB,kBAAkB,IAAI,UAAU,KAAK,YAAY;AAChF,MAAI,CAAC,MACH,QAAO;AAGT,MAAI;AACF,WAAQ,IAAI,8BAA8B,kBAAkB,IAAI,WAAW;GAE3E,MAAM,cAAc,OAAO,YAAY,kBAAkB,IAAI,aAAa,SAAS,CAAC;AACpF,WAAQ,IAAI,SAAS,MAAM,OAAO,kBAAkB,YAAY;GAEhE,MAAM,EAAE,MAAM,cAAc,MAAM,KAAK,oBAAoB,MAAM,QAAQ,kBAAkB,IAAI,KAAK;AACpG,QAAK,QAAQ,UAAU,KAAK,qBAAqB;GAEjD,MAAM,aAAa,IAAI,gBAAgB,IAAI,YAAY,KAAK,kBAAkB,OAAO;AACrF,QAAK,cAAc,WAAW;AAE9B,WAAQ,IAAI,qCAAqC,WAAW,GAAG,YAAY,MAAM,SAAS;GAE1F,IAAI,WAAW;GACf,MAAM,UAAU,OAAO,YAAqB,UAAkB;AAC5D,QAAI,SACF;AAEF,eAAW;AACX,QAAI,WACF,SAAQ,IAAI,WAAW;AAEzB,QAAI,MACF,SAAQ,MAAM,oBAAoB,MAAM;AAE1C,SAAK,iBAAiB,WAAW,GAAG;AACpC,UAAM,UAAU,UAAU,WAAkB;;AAG9C,MAAG,GAAG,WAAW,OAAO,SAA0B;AAChD,QAAI;KACF,MAAM,aAAa,OAAO,SAAS,WAAW,OAAO,KAAK,UAAU;AAEpE,SAAI,gBAAgB,qBAAqB,gBAAgB,iBAAiB;UACpE,CAAC,gBAAgB,oBAAoB,WAAW,SAAS,gBAAgB,iBAAiB;WAC7E,KAAK,QAChB,GAAS,gBAAgB,gBAAgB;AAC3C,gBAAQ,IACN,2CAA2C,WAAW,GAAG,wCAAwC,gBAAgB,iBAAiB,KAAK,QAAQ,EAAE,CAAC,cACnJ;AACD,gBAAQ,IAAI,gCAAgC,WAAW,MAAM,GAAG,IAAI,GAAG,WAAW,SAAS,MAAM,QAAQ,KAAK;AAC9G;;;;AAKN,gBAAW,eAAe,YAAY,OAAO,UAAoB;AAC/D,WAAK,MAAM,WAAW,MACpB,OAAM,UAAU,YAAY,SAAS,WAAkB;OAEzD;aACK,OAAO;AACd,aAAQ,MAAM,uCAAuC,MAAM;;KAE7D;AAEF,MAAG,GAAG,eAAe;AACd,YAAQ,gCAAgC,WAAW,GAAG,cAAc,MAAM,SAAS;KACxF;AAEF,MAAG,GAAG,UAAU,UAAiB;AAC1B,YAAQ,KAAA,GAAW,MAAM;KAC9B;AAEF,OAAI,OAAO,UAAU,cAAc,WACjC,OAAM,UAAU,UACd,YACA,wBAAwB,kBAAkB,KAAK,kBAAkB,SAAS,kBAAkB,OAAO,CACpG;AAGH,SAAM,WAAW,KAAK;IACpB,MAAM;IACN,IAAI,WAAW;IACf,SAAS;IACV,CAAC;AAEF,UAAO;WACA,OAAO;AACd,WAAQ,MAAM,4CAA4C,MAAM;AAChE,MAAG,OAAO;AACV,UAAO;;;CAIX,MAAM,cACJ,UACA,SACA,QACA,MACkB;EAElB,MAAM,OADU,UAAU,QAAQ,QACrB,CAAQ,IAAI,OAAO,IAAI;AAEpC,MAAI,CAAC,iBAAiB,IADN,IAAI,QAAQ,OAAO,KAAK,UAAU,OAC5B,CAAI,UAAU,KAAK,YAAY,CACnD,QAAO;AAGT,WAAS,cAAc,SAAS,QAAQ,OAAO,OAAO;AAC/C,QAAK,gBAAgB,IAAI,QAAQ;IACtC;AAEF,SAAO;;;AAIX,SAAgB,yBACd,cACA,SACoB;AACpB,QAAO,IAAI,mBAAmB,cAAc,QAAQ"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../src/node/connection.ts","../../src/node/room.ts","../../src/node/transport.ts"],"sourcesContent":["import type { RpgWebSocketConnection } from \"./types\";\n\ntype RuntimeProcess = {\n env?: Record<string, string | undefined>;\n};\n\nfunction readEnvVariable(name: string): string | undefined {\n const value = (globalThis as { process?: RuntimeProcess }).process?.env?.[name];\n return typeof value === \"string\" ? value : undefined;\n}\n\nexport class PartyConnection {\n public id: string;\n public uri: string;\n private _state: any = {};\n private messageQueue: Array<{ message: string; timestamp: number; sequence: number }> = [];\n private isProcessingQueue = false;\n private sequenceCounter = 0;\n private incomingQueue: Array<{\n message: string;\n timestamp: number;\n processor: (messages: string[]) => Promise<void>;\n }> = [];\n private isProcessingIncomingQueue = false;\n\n public static packetLossRate = parseFloat(readEnvVariable(\"RPGJS_PACKET_LOSS_RATE\") || \"0.1\");\n public static packetLossEnabled = readEnvVariable(\"RPGJS_ENABLE_PACKET_LOSS\") === \"true\";\n public static packetLossFilter = readEnvVariable(\"RPGJS_PACKET_LOSS_FILTER\") || \"\";\n public static bandwidthEnabled = readEnvVariable(\"RPGJS_ENABLE_BANDWIDTH\") === \"true\";\n public static bandwidthKbps = parseInt(readEnvVariable(\"RPGJS_BANDWIDTH_KBPS\") || \"100\");\n public static bandwidthFilter = readEnvVariable(\"RPGJS_BANDWIDTH_FILTER\") || \"\";\n public static latencyEnabled = readEnvVariable(\"RPGJS_ENABLE_LATENCY\") === \"true\";\n public static latencyMs = parseInt(readEnvVariable(\"RPGJS_LATENCY_MS\") || \"50\");\n public static latencyFilter = readEnvVariable(\"RPGJS_LATENCY_FILTER\") || \"\";\n\n constructor(private ws: RpgWebSocketConnection, id?: string, uri?: string) {\n this.id = id || this.generateId();\n this.uri = uri || \"\";\n }\n\n private generateId(): string {\n return `conn_${Date.now()}_${Math.random().toString(36).slice(2, 11)}`;\n }\n\n async send(data: any): Promise<void> {\n if (this.ws.readyState !== 1) {\n return;\n }\n\n const message = typeof data === \"string\" ? data : JSON.stringify(data);\n const timestamp = Date.now();\n const sequence = ++this.sequenceCounter;\n\n this.messageQueue.push({ message, timestamp, sequence });\n\n if (!this.isProcessingQueue) {\n void this.processMessageQueue();\n }\n }\n\n private async processMessageQueue(): Promise<void> {\n if (this.isProcessingQueue) {\n return;\n }\n this.isProcessingQueue = true;\n\n while (this.messageQueue.length > 0) {\n const queueItem = this.messageQueue.shift()!;\n\n if (this.shouldApplyLatency(queueItem.message)) {\n await this.waitUntil(queueItem.timestamp + PartyConnection.latencyMs);\n }\n\n if (PartyConnection.bandwidthEnabled && PartyConnection.bandwidthKbps > 0) {\n if (!PartyConnection.bandwidthFilter || queueItem.message.includes(PartyConnection.bandwidthFilter)) {\n const messageSizeBits = queueItem.message.length * 8;\n const transmissionTimeMs = (messageSizeBits / (PartyConnection.bandwidthKbps * 1000)) * 1000;\n const bandwidthDelayMs = Math.max(transmissionTimeMs, 10);\n console.log(\n `\\x1b[34m[BANDWIDTH SIMULATION]\\x1b[0m Connection ${this.id}: Message #${queueItem.sequence} transmission time: ${bandwidthDelayMs.toFixed(1)}ms`,\n );\n await new Promise((resolve) => setTimeout(resolve, bandwidthDelayMs));\n }\n }\n\n this.ws.send(queueItem.message);\n }\n\n this.isProcessingQueue = false;\n }\n\n private shouldApplyLatency(message: string): boolean {\n if (!PartyConnection.latencyEnabled || PartyConnection.latencyMs <= 0) {\n return false;\n }\n if (!PartyConnection.latencyFilter) {\n return true;\n }\n return message.includes(PartyConnection.latencyFilter);\n }\n\n private async waitUntil(targetTimestamp: number): Promise<void> {\n const delayMs = targetTimestamp - Date.now();\n if (delayMs <= 0) {\n return;\n }\n await new Promise((resolve) => setTimeout(resolve, delayMs));\n }\n\n close(): void {\n if (this.ws.readyState === 1) {\n this.ws.close();\n }\n }\n\n setState(value: any): void {\n this._state = value;\n }\n\n get state(): any {\n return this._state;\n }\n\n bufferIncoming(message: string, processor: (messages: string[]) => Promise<void>): void {\n this.incomingQueue.push({\n message,\n timestamp: Date.now(),\n processor,\n });\n\n if (!this.isProcessingIncomingQueue) {\n void this.processIncomingQueue();\n }\n }\n\n private async processIncomingQueue(): Promise<void> {\n if (this.isProcessingIncomingQueue) {\n return;\n }\n this.isProcessingIncomingQueue = true;\n\n while (this.incomingQueue.length > 0) {\n const item = this.incomingQueue.shift()!;\n if (this.shouldApplyLatency(item.message)) {\n await this.waitUntil(item.timestamp + PartyConnection.latencyMs);\n }\n try {\n await item.processor([item.message]);\n } catch (err) {\n console.error(\"Error processing incoming message:\", err);\n }\n }\n\n this.isProcessingIncomingQueue = false;\n }\n\n static configurePacketLoss(enabled: boolean, rate: number, filter?: string): void {\n PartyConnection.packetLossEnabled = enabled;\n PartyConnection.packetLossRate = Math.max(0, Math.min(1, rate));\n PartyConnection.packetLossFilter = filter || \"\";\n\n if (enabled && rate > 0) {\n const filterInfo = filter ? ` (filtered: \"${filter}\")` : \"\";\n console.log(`\\x1b[35m[PACKET LOSS SIMULATION]\\x1b[0m Enabled with ${(rate * 100).toFixed(1)}% loss rate${filterInfo}`);\n } else if (enabled) {\n console.log(\"\\x1b[35m[PACKET LOSS SIMULATION]\\x1b[0m Enabled but rate is 0% (no messages will be dropped)\");\n } else {\n console.log(\"\\x1b[35m[PACKET LOSS SIMULATION]\\x1b[0m Disabled\");\n }\n }\n\n static getPacketLossStatus(): { enabled: boolean; rate: number; filter: string } {\n return {\n enabled: PartyConnection.packetLossEnabled,\n rate: PartyConnection.packetLossRate,\n filter: PartyConnection.packetLossFilter,\n };\n }\n\n static configureBandwidth(enabled: boolean, kbps: number, filter?: string): void {\n PartyConnection.bandwidthEnabled = enabled;\n PartyConnection.bandwidthKbps = Math.max(1, kbps);\n PartyConnection.bandwidthFilter = filter || \"\";\n\n if (enabled && kbps > 0) {\n const filterInfo = filter ? ` (filtered: \"${filter}\")` : \"\";\n console.log(`\\x1b[35m[BANDWIDTH SIMULATION]\\x1b[0m Enabled with ${kbps} kbps bandwidth${filterInfo}`);\n } else if (enabled) {\n console.log(\"\\x1b[35m[BANDWIDTH SIMULATION]\\x1b[0m Enabled but bandwidth is 0 kbps (no delay will be applied)\");\n } else {\n console.log(\"\\x1b[35m[BANDWIDTH SIMULATION]\\x1b[0m Disabled\");\n }\n }\n\n static getBandwidthStatus(): { enabled: boolean; kbps: number; filter: string } {\n return {\n enabled: PartyConnection.bandwidthEnabled,\n kbps: PartyConnection.bandwidthKbps,\n filter: PartyConnection.bandwidthFilter,\n };\n }\n\n static configureLatency(enabled: boolean, ms: number, filter?: string): void {\n PartyConnection.latencyEnabled = enabled;\n PartyConnection.latencyMs = Math.max(0, ms);\n PartyConnection.latencyFilter = filter || \"\";\n\n if (enabled && ms > 0) {\n const filterInfo = filter ? ` (filtered: \"${filter}\")` : \"\";\n console.log(`\\x1b[35m[LATENCY SIMULATION]\\x1b[0m Enabled with ${ms}ms fixed latency${filterInfo}`);\n } else if (enabled) {\n console.log(\"\\x1b[35m[LATENCY SIMULATION]\\x1b[0m Enabled but latency is 0ms (no delay will be applied)\");\n } else {\n console.log(\"\\x1b[35m[LATENCY SIMULATION]\\x1b[0m Disabled\");\n }\n }\n\n static getLatencyStatus(): { enabled: boolean; ms: number; filter: string } {\n return {\n enabled: PartyConnection.latencyEnabled,\n ms: PartyConnection.latencyMs,\n filter: PartyConnection.latencyFilter,\n };\n }\n}\n\nexport function logNetworkSimulationStatus(): void {\n const packetLossStatus = PartyConnection.getPacketLossStatus();\n const bandwidthStatus = PartyConnection.getBandwidthStatus();\n const latencyStatus = PartyConnection.getLatencyStatus();\n\n if (packetLossStatus.enabled) {\n const filterInfo = packetLossStatus.filter ? ` (filter: \"${packetLossStatus.filter}\")` : \"\";\n console.log(\n `\\x1b[36m[NETWORK SIMULATION]\\x1b[0m Packet loss simulation: ${(packetLossStatus.rate * 100).toFixed(1)}% loss rate${filterInfo}`,\n );\n } else {\n console.log(\"\\x1b[36m[NETWORK SIMULATION]\\x1b[0m Packet loss simulation: disabled\");\n }\n\n if (bandwidthStatus.enabled) {\n const filterInfo = bandwidthStatus.filter ? ` (filter: \"${bandwidthStatus.filter}\")` : \"\";\n console.log(`\\x1b[36m[NETWORK SIMULATION]\\x1b[0m Bandwidth simulation: ${bandwidthStatus.kbps} kbps${filterInfo}`);\n } else {\n console.log(\"\\x1b[36m[NETWORK SIMULATION]\\x1b[0m Bandwidth simulation: disabled\");\n }\n\n if (latencyStatus.enabled) {\n const filterInfo = latencyStatus.filter ? ` (filter: \"${latencyStatus.filter}\")` : \"\";\n console.log(`\\x1b[36m[NETWORK SIMULATION]\\x1b[0m Latency simulation: ${latencyStatus.ms}ms ping${filterInfo}`);\n } else {\n console.log(\"\\x1b[36m[NETWORK SIMULATION]\\x1b[0m Latency simulation: disabled\");\n }\n}\n","import { PartyConnection } from \"./connection\";\n\nexport class PartyRoom {\n public id: string;\n public internalID: string;\n public env: Record<string, any> = {};\n public context: any = {};\n\n private connections = new Map<string, PartyConnection>();\n private storageData = new Map<string, any>();\n\n constructor(id: string) {\n this.id = id;\n this.internalID = `internal_${id}_${Date.now()}`;\n }\n\n async broadcast(message: any, except: string[] = []): Promise<void> {\n const data = typeof message === \"string\" ? message : JSON.stringify(message);\n const sendPromises: Promise<void>[] = [];\n\n for (const [connectionId, connection] of this.connections) {\n if (!except.includes(connectionId)) {\n sendPromises.push(connection.send(data));\n }\n }\n\n await Promise.all(sendPromises);\n }\n\n getConnection(id: string): PartyConnection | undefined {\n return this.connections.get(id);\n }\n\n getConnections(tag?: string): IterableIterator<PartyConnection> {\n void tag;\n return this.connections.values();\n }\n\n addConnection(connection: PartyConnection): void {\n this.connections.set(connection.id, connection);\n }\n\n removeConnection(connectionId: string): void {\n this.connections.delete(connectionId);\n }\n\n get storage() {\n return {\n put: async (key: string, value: any) => {\n this.storageData.set(key, value);\n },\n get: async <T = any>(key: string): Promise<T | undefined> => {\n return this.storageData.get(key) as T;\n },\n delete: async (key: string) => {\n this.storageData.delete(key);\n },\n list: async () => {\n return Array.from(this.storageData.entries());\n },\n };\n }\n}\n","import type { IncomingHttpHeaders, IncomingMessage, ServerResponse } from \"node:http\";\nimport type { Duplex } from \"node:stream\";\nimport { injector } from \"@signe/di\";\nimport { context as serverContext } from \"../core/context\";\nimport { setInject } from \"../core/inject\";\nimport { provideServerModules } from \"../module\";\nimport { PartyConnection } from \"./connection\";\nimport { createMapUpdateHeaders, resolveMapUpdateToken, updateMap } from \"./map\";\nimport { PartyRoom } from \"./room\";\nimport type {\n CreateRpgServerTransportOptions,\n HandleNodeRequestOptions,\n RpgTransportRequestLike,\n RpgTransportServer,\n RpgTransportServerConstructor,\n RpgWebSocketConnection,\n RpgWebSocketRequestLike,\n RpgWebSocketServer,\n SendMapUpdateOptions,\n} from \"./types\";\n\ntype PartiesFetchInit = {\n body?: any;\n headers?: HeadersInit | IncomingHttpHeaders | Map<string, string | undefined>;\n method?: string;\n};\n\nfunction normalizePathPrefix(path: string, fallback: string): string {\n const trimmed = (path || fallback).trim();\n if (!trimmed) {\n return fallback;\n }\n const prefixed = trimmed.startsWith(\"/\") ? trimmed : `/${trimmed}`;\n return prefixed !== \"/\" ? prefixed.replace(/\\/+$/, \"\") : prefixed;\n}\n\nfunction hasPathPrefix(pathname: string, prefix: string): boolean {\n return pathname === prefix || pathname.startsWith(`${prefix}/`);\n}\n\nfunction prependMountedPath(pathname: string, mountedPath?: string): string {\n if (!mountedPath) {\n return pathname;\n }\n const normalizedMountedPath = normalizePathPrefix(mountedPath, \"/\");\n if (hasPathPrefix(pathname, normalizedMountedPath)) {\n return pathname;\n }\n if (pathname === \"/\") {\n return normalizedMountedPath;\n }\n return `${normalizedMountedPath}${pathname.startsWith(\"/\") ? pathname : `/${pathname}`}`.replace(/\\/{2,}/g, \"/\");\n}\n\nfunction parseHttpRoute(pathname: string, partiesPath: string): { roomId: string; requestPath: string } | null {\n if (!hasPathPrefix(pathname, partiesPath)) {\n return null;\n }\n\n const remainder = pathname.slice(partiesPath.length);\n const segments = remainder.split(\"/\").filter(Boolean);\n if (segments.length < 2) {\n return null;\n }\n\n return {\n roomId: segments[0],\n requestPath: `/${segments.slice(1).join(\"/\")}`,\n };\n}\n\nfunction parseSocketRoute(pathname: string, partiesPath: string): { roomId: string } | null {\n if (!hasPathPrefix(pathname, partiesPath)) {\n return null;\n }\n\n const remainder = pathname.slice(partiesPath.length);\n const segments = remainder.split(\"/\").filter(Boolean);\n if (segments.length < 1) {\n return null;\n }\n\n return { roomId: segments[0] };\n}\n\nfunction toHeaders(\n input?: Headers | HeadersInit | IncomingHttpHeaders | Map<string, string | undefined>,\n): Headers {\n if (!input) {\n return new Headers();\n }\n if (input instanceof Headers) {\n return new Headers(input);\n }\n if (Array.isArray(input)) {\n return new Headers(input);\n }\n if (input instanceof Map) {\n const headers = new Headers();\n for (const [key, value] of input) {\n if (typeof value !== \"undefined\") {\n headers.set(key, value);\n }\n }\n return headers;\n }\n\n const headers = new Headers();\n Object.entries(input).forEach(([key, value]) => {\n if (Array.isArray(value)) {\n if (typeof value[0] !== \"undefined\") {\n headers.set(key, value[0]);\n }\n return;\n }\n if (typeof value !== \"undefined\") {\n headers.set(key, String(value));\n }\n });\n return headers;\n}\n\nfunction createRequestLike(url: string, method: string, headers: Headers, bodyText: string): RpgTransportRequestLike {\n return {\n url,\n method,\n headers,\n json: async () => {\n if (!bodyText) {\n return undefined;\n }\n return JSON.parse(bodyText);\n },\n text: async () => bodyText,\n };\n}\n\nasync function normalizeEngineResponse(result: any): Promise<Response> {\n if (result instanceof Response) {\n return result;\n }\n if (typeof result === \"string\") {\n return new Response(result, {\n status: 200,\n headers: {\n \"Content-Type\": \"text/plain\",\n },\n });\n }\n\n return new Response(JSON.stringify(result ?? {}), {\n status: 200,\n headers: {\n \"Content-Type\": \"application/json\",\n },\n });\n}\n\nasync function sendNodeResponse(res: ServerResponse, response: Response): Promise<void> {\n res.statusCode = response.status;\n response.headers.forEach((value, key) => {\n res.setHeader(key, value);\n });\n res.end(await response.text());\n}\n\nasync function readNodeBody(req: IncomingMessage): Promise<string> {\n return await new Promise<string>((resolve, reject) => {\n const chunks: Buffer[] = [];\n req.on(\"data\", (chunk: Buffer | string) => {\n chunks.push(typeof chunk === \"string\" ? Buffer.from(chunk) : chunk);\n });\n req.on(\"end\", () => {\n resolve(Buffer.concat(chunks).toString(\"utf8\"));\n });\n req.on(\"error\", reject);\n });\n}\n\nfunction resolveUrlFromSocketRequest(request: RpgWebSocketRequestLike): { headers: Headers; method?: string; rawUrl: string; url: URL } {\n const headers = toHeaders(request.headers);\n const host = headers.get(\"host\") || \"localhost\";\n const rawUrl = request.url || \"/\";\n const url = new URL(rawUrl, `http://${host}`);\n return {\n headers,\n method: request.method,\n rawUrl,\n url,\n };\n}\n\nfunction createConnectionContext(url: URL, headers: Headers, method?: string): any {\n const normalizedHeaders = new Map<string, string>();\n headers.forEach((value, key) => {\n normalizedHeaders.set(key.toLowerCase(), value);\n });\n\n return {\n request: {\n headers: {\n has: (name: string) => normalizedHeaders.has(name.toLowerCase()),\n get: (name: string) => normalizedHeaders.get(name.toLowerCase()),\n entries: () => normalizedHeaders.entries(),\n keys: () => normalizedHeaders.keys(),\n values: () => normalizedHeaders.values(),\n },\n method,\n url: url.toString(),\n },\n url,\n };\n}\n\nexport class RpgServerTransport {\n private serverContextInitialized = false;\n private partiesPath: string;\n private readonly initializeMaps: boolean;\n private readonly mapUpdateToken: string;\n private readonly tiledBasePaths?: string[];\n private readonly rooms = new Map<string, PartyRoom>();\n private readonly servers = new Map<string, RpgTransportServer>();\n private lastKnownHost = \"\";\n\n constructor(\n private readonly serverModule: RpgTransportServerConstructor,\n options: CreateRpgServerTransportOptions = {},\n ) {\n this.initializeMaps = options.initializeMaps ?? true;\n this.mapUpdateToken = resolveMapUpdateToken(options.mapUpdateToken);\n this.partiesPath = normalizePathPrefix(options.partiesPath || \"/parties/main\", \"/parties/main\");\n this.tiledBasePaths = options.tiledBasePaths;\n }\n\n private async ensureServerContext(): Promise<void> {\n if (this.serverContextInitialized) {\n return;\n }\n\n setInject(serverContext);\n await injector(serverContext, [provideServerModules([])]);\n this.serverContextInitialized = true;\n }\n\n getRoom(roomId: string): PartyRoom | undefined {\n return this.rooms.get(roomId);\n }\n\n getServer(roomId: string): RpgTransportServer | undefined {\n return this.servers.get(roomId);\n }\n\n private async ensureRoomAndServer(roomId: string, host?: string): Promise<{ room: PartyRoom; rpgServer: RpgTransportServer }> {\n if (host) {\n this.lastKnownHost = host;\n }\n\n let room = this.rooms.get(roomId);\n if (!room) {\n room = new PartyRoom(roomId);\n this.rooms.set(roomId, room);\n console.log(`Created new room: ${roomId}`);\n }\n\n let rpgServer = this.servers.get(roomId);\n if (!rpgServer) {\n await this.ensureServerContext();\n rpgServer = new this.serverModule(room);\n this.servers.set(roomId, rpgServer);\n console.log(`Created new server instance for room: ${roomId}`);\n\n if (typeof rpgServer.onStart === \"function\") {\n try {\n await rpgServer.onStart();\n console.log(`Server started for room: ${roomId}`);\n } catch (error) {\n console.error(`Error starting server for room ${roomId}:`, error);\n }\n }\n\n if (this.initializeMaps) {\n await updateMap(roomId, rpgServer, {\n host: host || this.lastKnownHost,\n mapUpdateToken: this.mapUpdateToken,\n tiledBasePaths: this.tiledBasePaths,\n });\n }\n }\n\n room.context.parties = this.buildPartiesContext();\n return { room, rpgServer };\n }\n\n private buildPartiesContext() {\n return {\n main: {\n get: async (targetRoomId: string) => {\n return {\n fetch: async (path: string, init?: PartiesFetchInit) => {\n const method = (init?.method || \"GET\").toUpperCase();\n const headers = toHeaders(init?.headers);\n const requestPath = path.startsWith(\"/\") ? path : `/${path}`;\n let bodyText = \"\";\n\n if (typeof init?.body === \"string\") {\n bodyText = init.body;\n } else if (typeof init?.body !== \"undefined\") {\n bodyText = JSON.stringify(init.body);\n }\n\n return this.dispatchRoomRequest(\n targetRoomId,\n createRequestLike(\n `http://localhost${this.partiesPath}/${targetRoomId}${requestPath}`,\n method,\n headers,\n bodyText,\n ),\n this.lastKnownHost,\n );\n },\n };\n },\n },\n } as any;\n }\n\n private async dispatchRoomRequest(roomId: string, requestLike: RpgTransportRequestLike, host?: string): Promise<Response> {\n const { room, rpgServer } = await this.ensureRoomAndServer(roomId, host);\n room.context.parties = this.buildPartiesContext();\n const result = await rpgServer.onRequest?.(requestLike);\n return normalizeEngineResponse(result);\n }\n\n async fetch(request: Request | string | URL, init?: RequestInit): Promise<Response> {\n const webRequest = request instanceof Request ? request : new Request(String(request), init);\n const url = new URL(webRequest.url);\n const route = parseHttpRoute(url.pathname, this.partiesPath);\n if (!route) {\n return new Response(JSON.stringify({ error: \"Not found\" }), {\n status: 404,\n headers: {\n \"Content-Type\": \"application/json\",\n },\n });\n }\n\n const bodyText = await webRequest.text();\n return this.dispatchRoomRequest(\n route.roomId,\n createRequestLike(webRequest.url, webRequest.method.toUpperCase(), toHeaders(webRequest.headers), bodyText),\n url.host,\n );\n }\n\n async updateMap(mapId: string, payload: any, options: SendMapUpdateOptions = {}): Promise<Response> {\n const roomId = mapId.startsWith(\"map-\") ? mapId : `map-${mapId}`;\n const headers = createMapUpdateHeaders(this.mapUpdateToken, options.headers);\n if (!headers.has(\"content-type\")) {\n headers.set(\"content-type\", \"application/json\");\n }\n\n return this.dispatchRoomRequest(\n roomId,\n createRequestLike(\n `http://localhost${this.partiesPath}/${roomId}/map/update`,\n \"POST\",\n headers,\n JSON.stringify(payload),\n ),\n options.host ?? this.lastKnownHost,\n );\n }\n\n async handleNodeRequest(\n req: IncomingMessage,\n res: ServerResponse,\n next?: () => void,\n options: HandleNodeRequestOptions = {},\n ): Promise<boolean> {\n try {\n const headers = toHeaders(req.headers);\n const host = headers.get(\"host\") || \"localhost\";\n const url = new URL(req.url || \"/\", `http://${host}`);\n const normalizedPathname = prependMountedPath(url.pathname, options.mountedPath);\n const normalizedUrl = new URL(url.toString());\n normalizedUrl.pathname = normalizedPathname;\n\n const route = parseHttpRoute(normalizedUrl.pathname, this.partiesPath);\n if (!route) {\n next?.();\n return false;\n }\n\n const bodyText = await readNodeBody(req);\n const response = await this.dispatchRoomRequest(\n route.roomId,\n createRequestLike(normalizedUrl.toString(), (req.method || \"GET\").toUpperCase(), headers, bodyText),\n host,\n );\n\n await sendNodeResponse(res, response);\n return true;\n } catch (error) {\n console.error(\"Error handling RPG-JS request:\", error);\n res.statusCode = 500;\n res.setHeader(\"Content-Type\", \"application/json\");\n res.end(JSON.stringify({ error: \"Internal server error\" }));\n return true;\n }\n }\n\n async acceptWebSocket(ws: RpgWebSocketConnection, request: RpgWebSocketRequestLike): Promise<boolean> {\n const normalizedRequest = resolveUrlFromSocketRequest(request);\n const route = parseSocketRoute(normalizedRequest.url.pathname, this.partiesPath);\n if (!route) {\n return false;\n }\n\n try {\n console.log(`WebSocket upgrade request: ${normalizedRequest.url.pathname}`);\n\n const queryParams = Object.fromEntries(normalizedRequest.url.searchParams.entries());\n console.log(`Room: ${route.roomId}, Query params:`, queryParams);\n\n const { room, rpgServer } = await this.ensureRoomAndServer(route.roomId, normalizedRequest.url.host);\n room.context.parties = this.buildPartiesContext();\n\n const connection = new PartyConnection(ws, queryParams._pk, normalizedRequest.rawUrl);\n room.addConnection(connection);\n\n console.log(`WebSocket connection established: ${connection.id} in room: ${route.roomId}`);\n\n let isClosed = false;\n const cleanup = async (logMessage?: string, error?: Error) => {\n if (isClosed) {\n return;\n }\n isClosed = true;\n if (logMessage) {\n console.log(logMessage);\n }\n if (error) {\n console.error(\"WebSocket error:\", error);\n }\n room.removeConnection(connection.id);\n await rpgServer.onClose?.(connection as any);\n };\n\n ws.on(\"message\", async (data: Buffer | string) => {\n try {\n const rawMessage = typeof data === \"string\" ? data : data.toString();\n\n if (PartyConnection.packetLossEnabled && PartyConnection.packetLossRate > 0) {\n if (!PartyConnection.packetLossFilter || rawMessage.includes(PartyConnection.packetLossFilter)) {\n const random = Math.random();\n if (random < PartyConnection.packetLossRate) {\n console.log(\n `\\x1b[31m[PACKET LOSS]\\x1b[0m Connection ${connection.id}: Server dropped an incoming packet (${(PartyConnection.packetLossRate * 100).toFixed(1)}% loss rate)`,\n );\n console.log(`\\x1b[33m[PACKET DATA]\\x1b[0m ${rawMessage.slice(0, 100)}${rawMessage.length > 100 ? \"...\" : \"\"}`);\n return;\n }\n }\n }\n\n connection.bufferIncoming(rawMessage, async (batch: string[]) => {\n for (const message of batch) {\n await rpgServer.onMessage?.(message, connection as any);\n }\n });\n } catch (error) {\n console.error(\"Error processing WebSocket message:\", error);\n }\n });\n\n ws.on(\"close\", () => {\n void cleanup(`WebSocket connection closed: ${connection.id} from room: ${route.roomId}`);\n });\n\n ws.on(\"error\", (error: Error) => {\n void cleanup(undefined, error);\n });\n\n if (typeof rpgServer.onConnect === \"function\") {\n await rpgServer.onConnect(\n connection as any,\n createConnectionContext(normalizedRequest.url, normalizedRequest.headers, normalizedRequest.method) as any,\n );\n }\n\n await connection.send({\n type: \"connected\",\n id: connection.id,\n message: \"Connected to RPG-JS server\",\n });\n\n return true;\n } catch (error) {\n console.error(\"Error establishing WebSocket connection:\", error);\n ws.close();\n return true;\n }\n }\n\n async handleUpgrade(\n wsServer: RpgWebSocketServer,\n request: IncomingMessage,\n socket: Duplex,\n head: Buffer,\n ): Promise<boolean> {\n const headers = toHeaders(request.headers);\n const host = headers.get(\"host\") || \"localhost\";\n const url = new URL(request.url || \"/\", `http://${host}`);\n if (!parseSocketRoute(url.pathname, this.partiesPath)) {\n return false;\n }\n\n wsServer.handleUpgrade(request, socket, head, (ws) => {\n void this.acceptWebSocket(ws, request);\n });\n\n return true;\n }\n}\n\nexport function createRpgServerTransport(\n serverModule: RpgTransportServerConstructor,\n options?: CreateRpgServerTransportOptions,\n): RpgServerTransport {\n return new RpgServerTransport(serverModule, options);\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rpgjs/server",
3
- "version": "5.0.0-beta.5",
3
+ "version": "5.0.0-beta.7",
4
4
  "main": "./dist/index.js",
5
5
  "types": "./dist/index.d.ts",
6
6
  "exports": {
@@ -21,21 +21,21 @@
21
21
  "license": "MIT",
22
22
  "description": "",
23
23
  "dependencies": {
24
- "@canvasengine/tiled": "2.0.0-beta.58",
25
- "@rpgjs/common": "5.0.0-beta.5",
26
- "@rpgjs/physic": "5.0.0-beta.5",
27
- "@rpgjs/testing": "5.0.0-beta.5",
24
+ "@canvasengine/tiled": "2.0.0-beta.61",
25
+ "@rpgjs/common": "5.0.0-beta.7",
26
+ "@rpgjs/physic": "5.0.0-beta.7",
27
+ "@rpgjs/testing": "5.0.0-beta.7",
28
28
  "@rpgjs/database": "^4.3.0",
29
- "@signe/di": "^2.9.0",
30
- "@signe/reactive": "^2.9.0",
31
- "@signe/room": "^2.9.0",
32
- "@signe/sync": "^2.9.0",
29
+ "@signe/di": "2.10.0",
30
+ "@signe/reactive": "2.10.0",
31
+ "@signe/room": "2.10.0",
32
+ "@signe/sync": "2.10.0",
33
33
  "rxjs": "^7.8.2",
34
- "zod": "^4.3.6"
34
+ "zod": "^4.4.3"
35
35
  },
36
36
  "devDependencies": {
37
- "vite": "^8.0.10",
38
- "vite-plugin-dts": "^4.5.4"
37
+ "vite": "^8.0.11",
38
+ "vite-plugin-dts": "^5.0.0"
39
39
  },
40
40
  "type": "module",
41
41
  "scripts": {
@@ -461,7 +461,7 @@ export class RpgPlayer extends BasicPlayerMixins(RpgCommonPlayer) {
461
461
  const centerX = positions.x + width / 2;
462
462
  const centerY = positions.y + height / 2;
463
463
 
464
- this.map.physic.teleport(entity, { x: centerX, y: centerY });
464
+ this.map.physic.teleportEntity(entity, { x: centerX, y: centerY });
465
465
  }
466
466
  }
467
467
  this.x.set(positions.x)
@@ -1476,15 +1476,64 @@ export class RpgEvent extends RpgPlayer {
1476
1476
  *
1477
1477
  * Stops all movements before removing to prevent "unable to resolve entity" errors
1478
1478
  * from the MovementManager when the entity is destroyed while moving.
1479
+ *
1480
+ * Pass options to keep the sprite visible briefly on clients while
1481
+ * `sprite.onBeforeRemove` runs a visual transition. Gameplay collision is
1482
+ * removed immediately; the event is deleted from the map after `timeoutMs`.
1483
+ *
1484
+ * The server only sends the removal context. The client decides how to render
1485
+ * `transition` in `sprite.onBeforeRemove`, so the payload can describe an
1486
+ * animation, sound, particle effect, GUI transition, or project-specific data.
1487
+ *
1488
+ * @example
1489
+ * ```ts
1490
+ * event.remove({
1491
+ * reason: 'defeated',
1492
+ * transition: {
1493
+ * type: 'enemy-death',
1494
+ * animation: 'die',
1495
+ * graphic: 'slime_die',
1496
+ * sound: 'slime-death',
1497
+ * duration: 700
1498
+ * },
1499
+ * timeoutMs: 700
1500
+ * })
1501
+ * ```
1479
1502
  */
1480
- remove() {
1503
+ remove(options?: {
1504
+ reason?: string;
1505
+ data?: any;
1506
+ transition?: {
1507
+ animation?: string;
1508
+ graphic?: string | string[];
1509
+ duration?: number;
1510
+ effect?: string;
1511
+ };
1512
+ timeoutMs?: number;
1513
+ }) {
1481
1514
  const map = this.getCurrentMap();
1482
1515
  if (!map) return;
1483
1516
 
1484
1517
  // Stop all movements before removing to prevent MovementManager errors
1485
1518
  this.stopMoveTo();
1486
-
1487
- map.removeEvent(this.id);
1519
+
1520
+ const timeoutMs = Math.max(0, options?.timeoutMs ?? options?.transition?.duration ?? 0);
1521
+ if (!options || timeoutMs <= 0) {
1522
+ map.removeEvent(this.id);
1523
+ return;
1524
+ }
1525
+
1526
+ this._removeTransition.set(JSON.stringify({
1527
+ active: true,
1528
+ reason: options.reason,
1529
+ data: options.data,
1530
+ transition: options.transition,
1531
+ timeoutMs,
1532
+ }));
1533
+ (map as any).removeHitbox?.(this.id, this, "npc");
1534
+ setTimeout(() => {
1535
+ map.removeEvent(this.id);
1536
+ }, timeoutMs);
1488
1537
  }
1489
1538
 
1490
1539
  override isEvent(): boolean {
package/src/rooms/map.ts CHANGED
@@ -24,7 +24,6 @@ import { Subject } from "rxjs";
24
24
  import { BehaviorSubject } from "rxjs";
25
25
  import { COEFFICIENT_ELEMENTS, DAMAGE_CRITICAL, DAMAGE_PHYSIC, DAMAGE_SKILL } from "../presets";
26
26
  import { z } from "zod";
27
- import { EntityState } from "@rpgjs/physic";
28
27
  import { MapOptions } from "../decorators/map";
29
28
  import { EventMode } from "../decorators/event";
30
29
  import { BaseRoom } from "./BaseRoom";
@@ -1054,10 +1053,20 @@ export class RpgMap extends RpgCommonMap<RpgPlayer> implements RoomOnJoin {
1054
1053
  */
1055
1054
  @Action('action')
1056
1055
  onAction(player: RpgPlayer, action: any) {
1057
- // Get collisions using the helper method from RpgCommonMap
1058
- const collisions = (this as any).getCollisions(player.id);
1059
- const events = collisions
1060
- .map(id => this.getEvent(id))
1056
+ const direction =
1057
+ typeof player.getDirection === "function"
1058
+ ? player.getDirection()
1059
+ : typeof player.direction === "function"
1060
+ ? player.direction()
1061
+ : undefined;
1062
+ const collisions = new Set<string>((this as any).getCollisions(player.id));
1063
+ const interactionCollisions = (this as any).getInteractionCollisions?.(player.id, direction);
1064
+ if (Array.isArray(interactionCollisions)) {
1065
+ interactionCollisions.forEach(id => collisions.add(id));
1066
+ }
1067
+
1068
+ const events = Array.from(collisions)
1069
+ .map(id => this.getEvent<RpgEvent>(id))
1061
1070
  .filter((event): event is RpgEvent => !!event && this.isEventVisibleForPlayer(event, player));
1062
1071
  if (events.length > 0) {
1063
1072
  events.forEach(event => {
@@ -2500,18 +2509,15 @@ export class RpgMap extends RpgCommonMap<RpgPlayer> implements RoomOnJoin {
2500
2509
  const centerX = x + width / 2;
2501
2510
  const centerY = y + height / 2;
2502
2511
 
2503
- // Create static entity (hitbox) in physics engine
2512
+ // Create static obstacle in physics engine
2504
2513
  const entityId = `shape-${name}`;
2505
- const entity = this.physic.createEntity({
2506
- uuid: entityId,
2507
- position: { x: centerX, y: centerY },
2508
- width: width,
2509
- height: height,
2510
- mass: Infinity, // Static entity
2511
- state: EntityState.Static,
2512
- restitution: 0, // No bounce
2514
+ this.physic.createStaticObstacle(entityId, {
2515
+ x: centerX,
2516
+ y: centerY,
2517
+ width,
2518
+ height,
2519
+ restitution: 0,
2513
2520
  });
2514
- entity.freeze(); // Ensure it's frozen
2515
2521
 
2516
2522
  // Build properties object
2517
2523
  const properties: Record<string, any> = {
@@ -0,0 +1,97 @@
1
+ import { afterEach, beforeEach, describe, expect, test } from "vitest";
2
+ import { testing, TestingFixture } from "@rpgjs/testing";
3
+ import { Control, createModule, defineModule, Direction } from "@rpgjs/common";
4
+ import { RpgClient } from "../../client/src";
5
+ import { RpgPlayer, RpgServer } from "../src";
6
+
7
+ let actionCount = 0;
8
+
9
+ const serverModule = defineModule<RpgServer>({
10
+ maps: [
11
+ {
12
+ id: "interaction-map",
13
+ file: "",
14
+ },
15
+ ],
16
+ player: {
17
+ async onConnected(player) {
18
+ await player.changeMap("interaction-map", { x: 100, y: 100 });
19
+ },
20
+ },
21
+ });
22
+
23
+ const clientModule = defineModule<RpgClient>({});
24
+
25
+ let fixture: TestingFixture;
26
+ let client: any;
27
+ let player: RpgPlayer;
28
+
29
+ beforeEach(async () => {
30
+ actionCount = 0;
31
+ const myModule = createModule("ActionInteractionTestModule", [
32
+ {
33
+ server: serverModule,
34
+ client: clientModule,
35
+ },
36
+ ]);
37
+
38
+ fixture = await testing(myModule);
39
+ client = await fixture.createClient();
40
+ player = await client.waitForMapChange("interaction-map");
41
+ });
42
+
43
+ afterEach(async () => {
44
+ await fixture.clear();
45
+ });
46
+
47
+ describe("Action interactions", () => {
48
+ test("triggers onAction for an event just in front of the player", async () => {
49
+ const map = player.getCurrentMap() as any;
50
+ const hitbox = player.hitbox();
51
+
52
+ await map.createDynamicEvent({
53
+ id: "front-event",
54
+ x: player.x(),
55
+ y: player.y() + hitbox.h + 2,
56
+ event: {
57
+ onAction() {
58
+ actionCount += 1;
59
+ },
60
+ },
61
+ });
62
+ await fixture.nextTick();
63
+
64
+ player.changeDirection(Direction.Down);
65
+
66
+ expect(map.getCollisions(player.id)).not.toContain("front-event");
67
+ expect(map.getInteractionCollisions(player.id, Direction.Down)).toContain("front-event");
68
+
69
+ map.onAction(player, { action: Control.Action });
70
+ await fixture.wait(0);
71
+
72
+ expect(actionCount).toBe(1);
73
+ });
74
+
75
+ test("ignores a nearby event behind the player", async () => {
76
+ const map = player.getCurrentMap() as any;
77
+ const hitbox = player.hitbox();
78
+
79
+ await map.createDynamicEvent({
80
+ id: "behind-event",
81
+ x: player.x(),
82
+ y: player.y() - hitbox.h - 2,
83
+ event: {
84
+ onAction() {
85
+ actionCount += 1;
86
+ },
87
+ },
88
+ });
89
+ await fixture.nextTick();
90
+
91
+ player.changeDirection(Direction.Down);
92
+ map.onAction(player, { action: Control.Action });
93
+ await fixture.wait(0);
94
+
95
+ expect(actionCount).toBe(0);
96
+ });
97
+ });
@@ -1,10 +1,11 @@
1
1
  import { beforeEach, test, expect, afterEach, describe, vi } from "vitest";
2
2
  import { testing, waitForSyncComplete, TestingFixture } from "@rpgjs/testing";
3
3
  import { defineModule, createModule } from "@rpgjs/common";
4
- import { RpgPlayer, RpgServer } from "../src";
4
+ import { RpgEvent, RpgPlayer, RpgServer } from "../src";
5
5
  import { RpgClient } from "../../client/src";
6
6
  import { ItemLog } from "../src/logs";
7
7
  import type { ItemObject } from "../src/Player/ItemManager";
8
+ import { syncClass } from "@signe/sync";
8
9
 
9
10
  // Define test items as objects for database
10
11
  const TestPotion = {
@@ -124,6 +125,76 @@ describe("Item Management - Basic Operations", () => {
124
125
  expect(item.quantity()).toBe(5);
125
126
  expect(item.name()).toBe("Test Potion");
126
127
  });
128
+
129
+ test("should sync cloneable event items and equipments initialized before sync", () => {
130
+ const event = new RpgEvent();
131
+ event.id = "enemy-event";
132
+ event.setMap(player.getCurrentMap()!);
133
+ event.execMethod = vi.fn() as any;
134
+ event.addItem("TestSword", 1);
135
+ event.equip("TestSword", true);
136
+
137
+ const packets: any[] = [];
138
+ const memory: Record<string, any> = {};
139
+ const setPath = (target: Record<string, any>, path: string, value: any) => {
140
+ const parts = path.split(".");
141
+ let current = target;
142
+ for (let i = 0; i < parts.length - 1; i++) {
143
+ current = current[parts[i]] ??= {};
144
+ }
145
+ current[parts[parts.length - 1]] = value;
146
+ };
147
+
148
+ syncClass(event, {
149
+ onSync(values) {
150
+ const packet: Record<string, any> = {};
151
+ for (const [path, value] of values) {
152
+ setPath(packet, path, value);
153
+ setPath(memory, path, value);
154
+ }
155
+ structuredClone({ type: "sync", value: packet });
156
+ packets.push(packet);
157
+ values.clear();
158
+ },
159
+ });
160
+
161
+ expect(packets).toContainEqual({
162
+ items: {
163
+ "0": expect.objectContaining({
164
+ id: "TestSword",
165
+ name: "Test Sword",
166
+ quantity: 1,
167
+ }),
168
+ },
169
+ });
170
+ expect(packets).toContainEqual({
171
+ equipments: {
172
+ "0": expect.objectContaining({
173
+ id: "TestSword",
174
+ name: "Test Sword",
175
+ quantity: 1,
176
+ }),
177
+ },
178
+ });
179
+ expect(memory).toEqual(
180
+ expect.objectContaining({
181
+ items: {
182
+ "0": expect.objectContaining({
183
+ id: "TestSword",
184
+ name: "Test Sword",
185
+ quantity: 1,
186
+ }),
187
+ },
188
+ equipments: {
189
+ "0": expect.objectContaining({
190
+ id: "TestSword",
191
+ name: "Test Sword",
192
+ quantity: 1,
193
+ }),
194
+ },
195
+ })
196
+ );
197
+ });
127
198
 
128
199
 
129
200
  test("should add item using object", () => {