UNPKG

rock-mod

Version:

Rock-Mod is a powerful framework designed for creating and managing mods for Grand Theft Auto (GTA) games.

154 lines (153 loc) 5.98 kB
/// <reference types="@classic-mp/types/client" /> /** * Имя приватного rock-mod события для зеркалирования клиентских `console.*` * вызовов в серверный stdout. * * Серверная сторона (`src/server/net/ccmp/CCMPNetManager.ts`) ловит это * событие и печатает payload через свой `console.log`/`error`/`warn`. Это * критически важно для отладки CCMP: на клиенте `console.log` идёт в игровую * консоль (F8), и разработчик в серверном терминале не видит ничего. */ const CLIENT_LOG_EVENT = "rm::clientLog"; const FORWARDED_LEVELS = ["log", "info", "warn", "error", "debug"]; function safeJsonStringify(value) { try { return JSON.stringify(value, Object.getOwnPropertyNames(value)); } catch (_a) { return "[unjsonable]"; } } function safeToString(value) { try { const s = String(value); return s === "[object Object]" ? safeJsonStringify(value) : s; } catch (_a) { return "[unstringifiable]"; } } function safeReadString(value, key) { try { const v = value[key]; return typeof v === "string" ? v : undefined; } catch (_a) { return undefined; } } function looksLikeError(value) { if (value instanceof Error) { return true; } if (typeof value !== "object" || value === null) { return false; } const v = value; // Структурные признаки. Хотя бы один из: stack (V8 ставит на ошибки), // или name+message (классический duck-type). return typeof v["stack"] === "string" || (typeof v["name"] === "string" && typeof v["message"] === "string"); } /** * Превращает аргумент в JSON-сериализуемое значение. * * Проблема: `JSON.stringify(new Error("..."))` возвращает `"{}"` — у Error * свойства `message`/`stack` не enumerable. Плюс под deno_core встречаются * "ошибки" не наследующие `Error.prototype` (cross-realm, ops_throw, * NotImplementedProxy через Proxy traps), для которых `instanceof Error` * возвращает false, но визуально они ошибки. * * Стратегия: * 1. Скаляры — как есть. * 2. Если есть `.message`/`.stack` / похожее на Error — пакуем в * `__error`-обёртку с явно перечисленными полями. * 3. Объекты — пробуем `JSON.stringify`. Если пусто (`{}`/`[]`) или падает * — добавляем `__toString` с `String(value)`. * 4. Худший случай — голая строка `String(value)`. */ function serializeArg(value) { var _a, _b; if (value === null || value === undefined) { return value; } const type = typeof value; if (type === "string" || type === "number" || type === "boolean") { return value; } if (looksLikeError(value)) { return { __error: true, name: (_a = safeReadString(value, "name")) !== null && _a !== void 0 ? _a : "Error", message: (_b = safeReadString(value, "message")) !== null && _b !== void 0 ? _b : safeToString(value), stack: safeReadString(value, "stack"), toString: safeToString(value), }; } try { const json = JSON.stringify(value); if (json && json !== "{}" && json !== "[]") { return value; } // Пустой JSON для непустого объекта — non-enumerable props. Дублируем // через String(...) чтобы на сервере хоть что-то прочиталось. return { __toString: safeToString(value), __raw: value, }; } catch (_c) { return { __toString: safeToString(value), }; } } /** * Создаёт обёртку для конкретного level. Извлечено в именованную функцию, * чтобы избежать `no-loop-func` (захват `ccmp` в замыкании внутри `for`). */ function createForwardingMethod(level, original) { return (...args) => { try { original(...args); } catch (_a) { // Локальный консоль-вывод не должен ломать форвард. } try { const payload = { level, args: args.map(serializeArg), }; ccmp.emitServer(CLIENT_LOG_EVENT, payload); } catch (_b) { // Если сервер недоступен (раннее в boot до handshake) — молча. } }; } /** * Оборачивает `globalThis.console` так, чтобы каждый вызов * `console.log/info/warn/error/debug` дополнительно отправлялся на сервер. * Оригинальное поведение (вывод в локальный игровой лог) сохраняется. * * Идемпотентно: повторный `install()` ничего не делает. */ export class CCMPConsoleForwarder { constructor() { this._installed = false; } install() { var _a; if (this._installed) { return; } this._installed = true; for (const level of FORWARDED_LEVELS) { const original = (_a = console[level]) === null || _a === void 0 ? void 0 : _a.bind(console); if (!original) { continue; } console[level] = createForwardingMethod(level, original); } } }