caps_enhancer/modules/core.js

396 lines
No EOL
16 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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.

// ==================== ЯДРО РАСШИРЕНИЯ ====================
// ============ ГЛАВНАЯ КОНФИГУРАЦИЯ РАСШИРЕНИЯ ============
// Значения по умолчанию (будут перезаписаны из chrome.storage)
let CONFIG = {
// Настройки отладки
debug: {
enabled: true, // Главный переключатель (если false, все логи отключены)
modules: {
core: true, // Логи ядра
video: true, // Логи модуля видео
mnemo: true, // Логи модуля мнемосхемы
close: true, // Логи модуля закрытия
content: true, // Логи главного файла
novnc: true, // Логи модуля noVNC
autologin: true // Логи модуля автологина
}
},
// Настройки портов
ports: {
video: '5000', // Порт для видео/настроек
scheme: '8080' // Порт для мнемосхемы
},
// Настройки модуля noVNC
novnc: {
mode: 'button', // 'redirect' - автоматическая переадресация, 'button' - добавить кнопку
redirectDelay: 500 // Задержка перед переадресацией (мс), только для mode: 'redirect'
},
// Настройки кнопки "Мнемосхема"
mnemo: {
mode: 'host-port', // 'host-port' — host:порт мнемосхемы, 'host-scheme' — http://хост/scheme
skipOnDomain: false, // не добавлять кнопку при заходе по домену (не по IP)
addVideoButton: true, // добавлять кнопку "Лещ" (переход на host:порт видео)
addSchemeButton: true // добавлять кнопку "Мнемосхема"
},
// Настройки автологина
autologin: {
enabled: false, // Включить/выключить автологин
profilePage1: 0, // Индекс профиля для страницы логина типа 1 (порт 5000, /login)
profilePage2: 1, // Индекс профиля для страницы логина типа 2 (auth-logo.png)
subnetRules: [], // Правила по подсетям: [{ subnet, action: 'skip'|'profile', profileIndex }]
profiles: [ // Массив профилей учётных данных
{ name: 'Профиль 1', username: '', password: '', autoSubmit: true, delay: 500 }
]
},
// Сайты, на которых расширение отключено (массив hostname, например ['example.com'])
disabledSites: [],
// Замена логотипа CleverPark на иконку CAPS (icons/caps.svg)
replaceLogo: true,
// Настройки видео-окна
videoWindow: {
ipButtonMode: 'button' // 'button' — отдельная кнопка с IP, 'clickable-title' — заголовок окна становится кликабельным
},
// Показывать ли улучшения расширения (кнопки, логотип)
uiVisible: true
};
// Обновление конфигурации (изменяет существующий объект, а не создает новый)
function updateConfig(newConfig) {
// Обновляем debug
if (newConfig.debug) {
CONFIG.debug.enabled = newConfig.debug.enabled;
if (newConfig.debug.modules) {
Object.assign(CONFIG.debug.modules, newConfig.debug.modules);
}
}
// Обновляем ports
if (newConfig.ports) {
Object.assign(CONFIG.ports, newConfig.ports);
}
// Обновляем novnc
if (newConfig.novnc) {
Object.assign(CONFIG.novnc, newConfig.novnc);
}
// Обновляем mnemo (совместимость со старыми значениями mode)
if (newConfig.mnemo) {
const m = newConfig.mnemo;
const mode = m.mode === 'ip-scheme' ? 'host-scheme' : (m.mode === 'skip-on-domain' || m.mode === 'default' ? 'host-port' : (m.mode || 'host-port'));
CONFIG.mnemo.mode = mode;
CONFIG.mnemo.skipOnDomain = m.skipOnDomain === true || m.mode === 'skip-on-domain';
if (typeof m.addVideoButton === 'boolean') CONFIG.mnemo.addVideoButton = m.addVideoButton;
else if (CONFIG.mnemo.addVideoButton === undefined) CONFIG.mnemo.addVideoButton = true;
if (typeof m.addSchemeButton === 'boolean') CONFIG.mnemo.addSchemeButton = m.addSchemeButton;
else if (CONFIG.mnemo.addSchemeButton === undefined) CONFIG.mnemo.addSchemeButton = true;
}
// Обновляем autologin
if (newConfig.autologin) {
const a = newConfig.autologin;
// Миграция старого формата (username/password) → profiles[0]
if ((a.username || a.password) && !Array.isArray(a.profiles)) {
CONFIG.autologin.profiles = [{
name: 'Профиль 1',
username: a.username || '',
password: a.password || '',
autoSubmit: typeof a.autoSubmit === 'boolean' ? a.autoSubmit : true,
delay: a.delay || 500
}];
} else if (Array.isArray(a.profiles)) {
CONFIG.autologin.profiles = a.profiles;
}
if (typeof a.enabled === 'boolean') CONFIG.autologin.enabled = a.enabled;
if (typeof a.profilePage1 === 'number') CONFIG.autologin.profilePage1 = a.profilePage1;
if (typeof a.profilePage2 === 'number') CONFIG.autologin.profilePage2 = a.profilePage2;
if (Array.isArray(a.subnetRules)) CONFIG.autologin.subnetRules = a.subnetRules;
}
// Обновляем disabledSites
if (Array.isArray(newConfig.disabledSites)) {
CONFIG.disabledSites = newConfig.disabledSites;
}
// Обновляем replaceLogo
if (typeof newConfig.replaceLogo === 'boolean') {
CONFIG.replaceLogo = newConfig.replaceLogo;
}
// Обновляем videoWindow
if (newConfig.videoWindow) {
Object.assign(CONFIG.videoWindow, newConfig.videoWindow);
}
// Обновляем uiVisible
if (typeof newConfig.uiVisible === 'boolean') {
CONFIG.uiVisible = newConfig.uiVisible;
}
// Обновляем константы портов
window.VideoIPRedirect.TARGET_PORT_VIDEO = CONFIG.ports.video;
window.VideoIPRedirect.TARGET_PORT_SCHEME = CONFIG.ports.scheme;
}
// Загрузка конфигурации из chrome.storage (возвращает Promise)
function loadConfig() {
return new Promise((resolve) => {
if (typeof chrome !== 'undefined' && chrome.storage) {
chrome.storage.sync.get(['config'], (result) => {
if (result.config) {
// Обновляем CONFIG из сохранённых настроек
updateConfig(result.config);
debugLog('core', '⚙️ Конфигурация загружена из настроек:', CONFIG);
} else {
debugLog('core', ' Используется конфигурация по умолчанию');
}
resolve();
});
} else {
debugLog('core', ' chrome.storage недоступен, используется конфигурация по умолчанию');
resolve();
}
});
}
// Слушаем обновления конфигурации
if (typeof chrome !== 'undefined' && chrome.runtime) {
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
if (message.type === 'CONFIG_UPDATED') {
updateConfig(message.config);
debugLog('core', '🔄 Конфигурация обновлена:', CONFIG);
// Отправляем событие об обновлении конфигурации
document.dispatchEvent(new CustomEvent('VideoIPRedirect:ConfigUpdated', {
detail: { config: CONFIG }
}));
debugLog('core', '💡 Для применения всех изменений рекомендуется перезагрузить страницу');
}
});
}
// Константы портов (для обратной совместимости)
const TARGET_PORT_VIDEO = CONFIG.ports.video;
const TARGET_PORT_SCHEME = CONFIG.ports.scheme;
// Улучшенная функция отладочного логирования
function debugLog(module, ...args) {
// Проверяем глобальный флаг и флаг конкретного модуля
if (!CONFIG.debug.enabled || !CONFIG.debug.modules[module]) {
return;
}
// Цвета для разных модулей
const moduleColors = {
core: '#4CAF50', // Зелёный
video: '#2196F3', // Синий
mnemo: '#FF9800', // Оранжевый
close: '#F44336', // Красный
content: '#9C27B0', // Фиолетовый
novnc: '#00BCD4', // Голубой
autologin: '#8BC34A' // Светло-зелёный
};
const color = moduleColors[module] || '#607D8B';
const prefix = `[CAPS_Enhancer::${module.toUpperCase()}]`;
console.log(
`%c${prefix}`,
`background: ${color}; color: white; padding: 2px 5px; border-radius: 3px; font-weight: bold;`,
...args
);
}
// ПРОСТАЯ проверка типа страницы
function checkPageType() {
debugLog('core', '=== ПРОВЕРКА ТИПА СТРАНИЦЫ ===');
// 1. Проверяем маркер приложения
const hasAppMarker = checkAppMarker();
debugLog('core', 'Маркер приложения:', hasAppMarker ? '✅' : '❌');
// 2. Проверяем логотип (/images/cleverpark.svg или alt="CLEVER PARK")
const logoSelector = 'img[src*="cleverpark.svg"], img[src*="cleverpark"], img[alt*="CLEVER PARK"], img[alt*="CLEVER"]';
const logo = document.querySelector(logoSelector);
const hasLogo = !!logo;
debugLog('core', 'Логотип CLEVER PARK:', hasLogo ? '✅' : '❌');
if (logo) debugLog('core', ' Найден лого:', logo.src);
// 3. Проверяем признаки страницы noVNC (экран на 80 порту)
const isNoVNC = checkNoVNCPage();
debugLog('core', 'Страница noVNC:', isNoVNC ? '✅' : '❌');
const pageType = {
isMainPage: hasLogo && hasAppMarker && !isNoVNC,
isVideoPage: !hasLogo && hasAppMarker && !isNoVNC,
isNoVNCPage: isNoVNC,
hasAppMarker: hasAppMarker,
hasLogo: hasLogo
};
debugLog('core', 'ИТОГОВЫЙ ТИП СТРАНИЦЫ:',
pageType.isNoVNCPage ? 'NOVNC (экран)' :
pageType.isMainPage ? 'ГЛАВНАЯ' :
pageType.isVideoPage ? 'ВИДЕО' :
'НЕ ЦЕЛЕВАЯ');
return pageType;
}
// Проверка страницы noVNC по характерным признакам
function checkNoVNCPage() {
// Признак 1: noscript с текстом "You need to enable JavaScript to run this app."
const noscripts = document.querySelectorAll('noscript');
let hasNoVNCNoscript = false;
for (let noscript of noscripts) {
if (noscript.textContent.includes('You need to enable JavaScript to run this app')) {
hasNoVNCNoscript = true;
break;
}
}
// Признак 2: div с классом начинающимся на "app_AppWindow__"
// ИЛИ title страницы содержит "Платёжный терминал" (присутствует сразу при загрузке)
const hasAppWindow = !!(
document.querySelector('div[class*="app_AppWindow__"]') ||
document.title.includes('Платёжный терминал')
);
// Страница noVNC определяется по наличию обоих признаков
return hasNoVNCNoscript && hasAppWindow;
}
// Проверка маркера приложения
function checkAppMarker() {
const noscripts = document.querySelectorAll('noscript');
for (let noscript of noscripts) {
if (noscript.textContent.includes('You need to enable JavaScript')) {
return true;
}
}
return document.body?.textContent?.includes('You need to enable JavaScript') || false;
}
// ПРОСТОЕ извлечение IP
function extractIpAndPort(videoUrl) {
if (!videoUrl) return null;
const match = videoUrl.match(/\/getVideo\/http:\/\/([^\/:?#]+)(?::(\d+))?/i);
return match ? { ip: match[1], originalPort: match[2] || '80' } : null;
}
// Проверка видимости элемента (общая функция для всех модулей)
function isElementVisible(element) {
if (!element) return false;
try {
const style = window.getComputedStyle(element);
if (style.display === 'none' || style.visibility === 'hidden' || style.opacity === '0') {
return false;
}
const rect = element.getBoundingClientRect();
if (rect.width === 0 || rect.height === 0) {
return false;
}
return true;
} catch (e) {
return false;
}
}
// Создание стилизованной кнопки (общая функция)
function createStyledButton(text, onClick, className = 'redirect-btn') {
const button = document.createElement('button');
button.className = className;
button.textContent = text;
button.style.cssText = `
margin-left: 12px;
padding: 6px 16px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border: none;
border-radius: 18px;
cursor: pointer;
font-size: 13px;
font-weight: bold;
display: inline-block;
vertical-align: middle;
transition: all 0.2s ease;
border: 1px solid rgba(255,255,255,0.5);
box-shadow: 0 2px 8px rgba(0,0,0,0.2);
font-family: Arial, sans-serif;
`;
button.onmouseover = () => {
button.style.transform = 'scale(1.05)';
button.style.boxShadow = '0 4px 12px rgba(0,0,0,0.3)';
};
button.onmouseout = () => {
button.style.transform = 'scale(1)';
button.style.boxShadow = '0 2px 8px rgba(0,0,0,0.2)';
};
button.onclick = onClick;
return button;
}
// Утилита для повторных попыток
function retryWithBackoff(fn, maxAttempts = 3, delays = [500, 1500, 3000]) {
delays.forEach((delay, index) => {
setTimeout(() => {
if (index < maxAttempts) {
fn();
}
}, delay);
});
}
// Глобальный объект
window.VideoIPRedirect = {
CONFIG: CONFIG,
debugLog: debugLog,
checkPageType: checkPageType,
extractIpAndPort: extractIpAndPort,
isElementVisible: isElementVisible,
createStyledButton: createStyledButton,
retryWithBackoff: retryWithBackoff,
TARGET_PORT_VIDEO: TARGET_PORT_VIDEO,
TARGET_PORT_SCHEME: TARGET_PORT_SCHEME,
activeSchemeButton: false,
// Для обратной совместимости
DEBUG_CONFIG: CONFIG.debug
};
debugLog('core', '✅ CORE: Модуль инициализирован');
debugLog('core', '📌 СТРАНИЦА:', window.location.href);
debugLog('core', '📌 USER AGENT:', navigator.userAgent);
// Флаг готовности конфигурации
window.VideoIPRedirect.configReady = false;
// Загружаем конфигурацию из настроек
loadConfig().then(() => {
window.VideoIPRedirect.configReady = true;
debugLog('core', '🎯 Конфигурация готова к использованию');
// Отправляем событие о готовности конфигурации
document.dispatchEvent(new CustomEvent('VideoIPRedirect:ConfigReady', {
detail: { config: CONFIG }
}));
});