studio/src/editor/engine/devlog.js
МИН 31adbf151b Initial public release: Студия Рублокса v1.0
Open-source веб-студия для создания игр Рублокса, двойная лицензия
AGPL-3.0 + Коммерческая.

Главное:
- Vite 5 + React 18 + Babylon 7.54.3 + Monaco Editor + Colyseus 0.16
- Самодостаточный движок ~28к строк (66 файлов): BlockManager,
  TerrainVoxelBuilder, ModelManager, DecoManager, PlayerController,
  ScriptSandboxWorker, MultiplayerSync, 30+ GD-гейммодов
- Главный редактор KubikonEditor (~37к строк) + панели, ScriptEditor (Monaco)
- Витрина игр (KubikonFeed, KubikonStudio, KubikonDocs, KubikonLearn)
- Geometry Dash sub-app (GdMenu, GdShop, GdRules, GdCoverArt)
- 10 admin-preview каталогов для дизайнеров (скины, музыка, порталы и т.д.)
- Конфигурируемый бэкенд через VITE_API_BASE — работает со staging
  (dev-api.rublox.pro) без настройки
- Standalone-режим (VITE_STANDALONE=true) — открыть пустой редактор без бэка
- Полная документация (на русском): README, ARCHITECTURE, CONTRIBUTING,
  SECURITY, CHANGELOG
- ESLint + Prettier + EditorConfig
- Legal: LICENSE (AGPL-3.0), LICENSE-COMMERCIAL.md, CLA.md, COPYRIGHT.md
- Issue templates: bug_report, feature_request, security_disclosure

Перед публикацией:
- Все импорты из minecraftia заменены на локальные
- Все хардкоды URL (minecraftia-school.ru) и внутренних IP убраны → env
- Admin-эндпоинты Kubikon3DService вырезаны (остаются в приватном репо)
- AdminKubikonModeration не публикуется (модерация — в team.rublox.pro)
- 93 МБ ассетов public/kubikon-assets вынесены в .gitignore
  (раздаются через release artifact)
2026-05-27 23:41:10 +03:00

132 lines
5.6 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* Dev-log клиент — шлёт логи на локальный HTTP-сервер на localhost:8765.
*
* Полезно когда нужно собирать console.* логи из браузера в файл для
* последующего просмотра (например при отладке вместе с AI-ассистентом).
*
* Активен только на localhost. На любом другом хосте запросы тихо
* игнорируются (если devlog-сервер не запущен).
*
* Использование:
* import { devlog, attachConsoleHook } from './devlog';
* devlog('info', 'PerfReport', { fps: 30, triangles: 5000000 });
* attachConsoleHook(); // перехват console.log/warn/error
*/
const DEVLOG_URL = 'http://localhost:8765/log';
const RESET_URL = 'http://localhost:8765/reset';
// Активируем только в development (localhost)
const isDev = typeof window !== 'undefined'
&& (window.location.hostname === 'localhost'
|| window.location.hostname === '127.0.0.1');
let devlogEnabled = isDev;
let pendingTimer = null;
const pendingQueue = [];
/**
* Отправить одну запись в devlog. Не блокирует — fire-and-forget.
* Запросы батчатся (флашатся через 200мс), чтобы не спамить сервер.
*/
export function devlog(level, msg, data) {
if (!devlogEnabled) return;
pendingQueue.push({ level, msg, data });
if (!pendingTimer) {
pendingTimer = setTimeout(flushQueue, 200);
}
}
function flushQueue() {
pendingTimer = null;
if (!devlogEnabled) { pendingQueue.length = 0; return; }
if (pendingQueue.length === 0) return;
const batch = pendingQueue.splice(0, pendingQueue.length);
// ВАЖНО: шлём ОДИН POST на весь батч, а не N штук. Раньше цикл
// запускал все fetch сразу — а devlogEnabled=false из .catch()
// выставлялся асинхронно уже после вылета всех 50 запросов, и
// консоль засорялась десятками ERR_CONNECTION_REFUSED. Теперь —
// один запрос: упал → один лог в консоли → devlog навсегда выключен.
try {
fetch(DEVLOG_URL, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
// Сервер принимает либо одиночную запись, либо массив.
body: JSON.stringify(batch.length === 1 ? batch[0] : batch),
// keepalive чтобы запрос не отменился при уходе со страницы
keepalive: true,
}).catch(() => {
// Сервер не запущен — глушим devlog навсегда (один раз).
devlogEnabled = false;
pendingQueue.length = 0;
});
} catch (e) {
devlogEnabled = false;
}
}
/**
* Сбросить лог-файл (очистить). Вызывать перед новым тестом.
*/
export function devlogReset() {
if (!devlogEnabled) return;
try {
fetch(RESET_URL, { method: 'POST', keepalive: true })
.catch(() => { devlogEnabled = false; });
} catch (e) { /* ignore */ }
}
/**
* Подключить перехват console.log/warn/error/info — все логи будут
* автоматически попадать в devlog.txt. Безопасно вызывать многократно
* (повторный вызов — no-op).
*/
let consoleHooked = false;
export function attachConsoleHook() {
if (!devlogEnabled || consoleHooked) return;
consoleHooked = true;
const original = {
log: console.log,
warn: console.warn,
error: console.error,
info: console.info,
};
const formatArg = (arg) => {
if (arg === null || arg === undefined) return String(arg);
if (typeof arg === 'string') return arg;
if (typeof arg === 'number' || typeof arg === 'boolean') return String(arg);
if (arg instanceof Error) return `${arg.name}: ${arg.message}\n${arg.stack || ''}`;
try {
return JSON.stringify(arg, (key, value) => {
// Защита от циклических ссылок и больших объектов Babylon
if (typeof value === 'object' && value !== null) {
if (value.constructor?.name === 'Scene') return '[Babylon.Scene]';
if (value.constructor?.name === 'Mesh') return `[Mesh ${value.name}]`;
if (value.constructor?.name === 'Engine') return '[Babylon.Engine]';
}
return value;
}, 0);
} catch (e) {
return String(arg);
}
};
const hook = (level, originalFn) => (...args) => {
try {
const formatted = args.map(formatArg).join(' ');
devlog(level, formatted);
} catch (e) { /* ignore */ }
try { originalFn.apply(console, args); } catch (e) {}
};
console.log = hook('log', original.log);
console.warn = hook('warn', original.warn);
console.error = hook('error', original.error);
console.info = hook('info', original.info);
}
// Глобальные хелперы для ручного дёрганья из консоли DevTools
if (typeof window !== 'undefined' && isDev) {
window.__devlog = devlog;
window.__devlogReset = devlogReset;
window.__devlogAttach = attachConsoleHook;
}