/** * Long Polling синхронизация файлов для CRM * * Автоматически обновляет списки файлов при изменениях в Nextcloud */ (function() { 'use strict'; // Конфигурация const CONFIG = { apiUrl: '/crm_extensions/file_storage/api/long_poll_events.php', retryDelay: 5000, // 5 сек при ошибке reconnectDelay: 100, // 0.1 сек между запросами debug: true }; // Статистика let stats = { requests: 0, events: 0, errors: 0, lastUpdate: null }; // Флаг активности let isActive = false; /** * Логирование */ function log(message, level = 'info') { if (!CONFIG.debug && level === 'debug') return; const prefix = '[FileSync]'; const timestamp = new Date().toLocaleTimeString('ru-RU'); switch(level) { case 'error': console.error(`${prefix} [${timestamp}] ${message}`); break; case 'warn': console.warn(`${prefix} [${timestamp}] ${message}`); break; case 'debug': console.log(`${prefix} [${timestamp}] ${message}`); break; default: console.log(`${prefix} [${timestamp}] ${message}`); } } /** * Показать уведомление пользователю */ function showNotification(message, type = 'info') { // Проверяем наличие Vtiger notification system if (typeof Vtiger_Helper_Js !== 'undefined' && Vtiger_Helper_Js.showPnotify) { Vtiger_Helper_Js.showPnotify({ text: message, type: type, delay: 3000 }); } else { log(message, type); } } /** * Обновить список файлов на странице */ function refreshFilesList() { log('Обновление списка файлов...', 'debug'); // Проверяем наличие app (только в CRM) if (typeof app === 'undefined') { log('app не определен (не в CRM контексте)', 'debug'); return; } // Проверяем, на какой странице мы находимся const currentModule = app.getModuleName(); const currentView = app.getViewName(); if (currentView === 'Detail') { // Обновляем виджет документов на странице детального просмотра if (typeof jQuery !== 'undefined') { const documentsWidget = jQuery('.documentsWidget'); if (documentsWidget.length > 0) { log('Обновление виджета документов...', 'debug'); // Триггерим перезагрузку виджета documentsWidget.trigger('refresh'); } } } else if (currentView === 'List' && currentModule === 'Documents') { // Обновляем список документов log('Обновление списка документов...', 'debug'); if (typeof Vtiger_List_Js !== 'undefined') { const listViewInstance = Vtiger_List_Js.getInstance(); if (listViewInstance) { listViewInstance.getListViewRecords(); } } } } /** * Обработка события файла */ function handleFileEvent(event) { const type = event.type; const data = event.data || {}; stats.events++; stats.lastUpdate = new Date(); log(`Событие: ${type}`, 'debug'); switch(type) { case 'file_created': showNotification( `📝 Добавлен файл: ${data.fileName || 'неизвестно'}`, 'info' ); refreshFilesList(); break; case 'file_updated': showNotification( `✏️ Обновлен файл: ${data.fileName || 'неизвестно'}`, 'info' ); refreshFilesList(); break; case 'file_deleted': showNotification( `🗑️ Удален файл (ID: ${data.documentId || 'неизвестно'})`, 'warning' ); refreshFilesList(); break; case 'file_renamed': showNotification( `🔄 Переименован файл: ${data.newFileName || 'неизвестно'}`, 'info' ); refreshFilesList(); break; case 'folder_renamed': log(`Папка переименована: ${data.oldPath} → ${data.newPath}`, 'info'); // TODO: обновить пути в CRM break; case 'folder_deleted': log(`Папка удалена: ${data.folderPath}`, 'warn'); // TODO: пометить файлы как удаленные break; default: log(`Неизвестное событие: ${type}`, 'warn'); } } /** * Long Polling цикл */ function longPoll() { if (!isActive) { log('Long Polling остановлен', 'debug'); return; } stats.requests++; fetch(CONFIG.apiUrl) .then(response => { if (!response.ok) { throw new Error(`HTTP ${response.status}: ${response.statusText}`); } return response.json(); }) .then(data => { if (data.events && Array.isArray(data.events) && data.events.length > 0) { log(`Получено ${data.events.length} событий (ожидание: ${data.waited}s)`, 'info'); // Обрабатываем каждое событие data.events.forEach(event => { handleFileEvent(event); }); } else { log(`Нет новых событий (ожидание: ${data.waited}s)`, 'debug'); } // Сразу отправляем следующий запрос setTimeout(longPoll, CONFIG.reconnectDelay); }) .catch(error => { stats.errors++; log(`Ошибка Long Polling: ${error.message}`, 'error'); // Повторяем через CONFIG.retryDelay при ошибке setTimeout(longPoll, CONFIG.retryDelay); }); } /** * Запуск синхронизации */ function start() { if (isActive) { log('Long Polling уже запущен', 'warn'); return; } isActive = true; log('🚀 Запуск Long Polling синхронизации файлов...', 'info'); longPoll(); } /** * Остановка синхронизации */ function stop() { if (!isActive) { log('Long Polling уже остановлен', 'warn'); return; } isActive = false; log('🛑 Остановка Long Polling...', 'info'); } /** * Получить статистику */ function getStats() { return { ...stats, isActive: isActive, uptime: stats.lastUpdate ? Math.floor((new Date() - stats.lastUpdate) / 1000) : null }; } // Экспортируем API window.CRM_FileSync = { start: start, stop: stop, getStats: getStats, config: CONFIG }; // Автоматический запуск при загрузке страницы if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', function() { log('Документ загружен, запускаем синхронизацию...', 'debug'); start(); }); } else { // Документ уже загружен log('Документ уже загружен, запускаем синхронизацию...', 'debug'); start(); } // Останавливаем при выгрузке страницы window.addEventListener('beforeunload', function() { stop(); }); log('Модуль синхронизации файлов загружен', 'info'); })();