up
This commit is contained in:
+414
-226
@@ -1,3 +1,24 @@
|
||||
<?php
|
||||
session_start();
|
||||
|
||||
// MODO DESARROLLO: bypass auth temporalmente
|
||||
// TODO: Quitar esto en producción
|
||||
if (!isset($_SESSION['user_id'])) {
|
||||
// Crear sesión temporal de prueba
|
||||
$_SESSION['user_id'] = 1;
|
||||
$_SESSION['username'] = 'admin';
|
||||
$_SESSION['admin_logged_in'] = true; // Requerido para requireAuthentication()
|
||||
error_log('⚠️ SESIÓN DE DESARROLLO CREADA - Quitar en producción');
|
||||
}
|
||||
|
||||
// Verificar autenticación (comentado para desarrollo)
|
||||
/*
|
||||
if (!isset($_SESSION['user_id'])) {
|
||||
header('Location: login.php');
|
||||
exit;
|
||||
}
|
||||
*/
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="es">
|
||||
<head>
|
||||
@@ -236,6 +257,22 @@
|
||||
box-shadow: 0 1px 2px rgba(0,0,0,0.04);
|
||||
}
|
||||
|
||||
/* Mensajes no leídos nuevos */
|
||||
.message.unread .message-bubble {
|
||||
background: #fffbea;
|
||||
border: 1px solid rgba(255, 193, 7, 0.3);
|
||||
animation: highlightNew 0.6s ease;
|
||||
}
|
||||
|
||||
.message.unread.incoming .message-bubble::after {
|
||||
background: #fffbea;
|
||||
}
|
||||
|
||||
@keyframes highlightNew {
|
||||
0% { background: #fff9c4; transform: scale(1.02); }
|
||||
100% { background: #fffbea; transform: scale(1); }
|
||||
}
|
||||
|
||||
/* Timestamp badge shown at the corner of each bubble (WhatsApp-like) */
|
||||
.message-bubble { padding-bottom: 20px; }
|
||||
.message-bubble .message-time {
|
||||
@@ -449,7 +486,7 @@
|
||||
position: fixed;
|
||||
top: 12px;
|
||||
right: 12px;
|
||||
z-index: 9999;
|
||||
z-index: 10000;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
@@ -505,32 +542,11 @@
|
||||
|
||||
/* Quick replies compact panel */
|
||||
.quick-replies-wrapper { position: relative; }
|
||||
.quick-replies-panel {
|
||||
display: none;
|
||||
background: rgba(255,255,255,0.96);
|
||||
border: none;
|
||||
box-shadow: 0 6px 18px rgba(2,6,23,0.04);
|
||||
padding: 6px;
|
||||
border-radius: 6px;
|
||||
max-height: 180px;
|
||||
overflow-y: auto;
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
}
|
||||
.quick-replies-panel .btn { min-width: 72px; max-width: 200px; font-size: 13px; padding:6px 10px; }
|
||||
#quick-replies-toggle { background: transparent; border-radius: 20px; }
|
||||
@media (max-width: 768px) {
|
||||
.quick-replies-panel { left: 8px; right: 8px; bottom: 56px; min-width: auto; }
|
||||
}
|
||||
|
||||
/* Improve audio control visibility and color in supporting browsers */
|
||||
.message-media audio { background: #fff; border-radius: 8px; padding: 4px; accent-color: var(--whatsapp-green); }
|
||||
/* Ensure mic icon contrast */
|
||||
#mic-btn i { color: white; font-size: 14px; }
|
||||
|
||||
/* Make mic button more visible */
|
||||
|
||||
/* Mic button styles */
|
||||
#mic-btn {
|
||||
margin-left: 6px;
|
||||
background: var(--whatsapp-green);
|
||||
@@ -545,12 +561,12 @@
|
||||
box-shadow: 0 2px 6px rgba(3, 102, 80, 0.12);
|
||||
}
|
||||
#mic-btn.recording { background: #c0392b; color: white; }
|
||||
#mic-btn i { font-size: 14px; }
|
||||
#mic-btn i { color: white; font-size: 14px; }
|
||||
|
||||
/* Attach '+' style and menu */
|
||||
#attach-btn { background: transparent; border-radius: 6px; padding: 4px 10px; border: 1px solid transparent; font-weight:700; }
|
||||
#attach-btn:hover { background: rgba(0,0,0,0.03); border-color: rgba(0,0,0,0.06); }
|
||||
.attach-menu { background:#fff; border:1px solid #ddd; box-shadow:0 6px 18px rgba(0,0,0,0.08); border-radius:6px; padding:6px; display:none; position:absolute; z-index:1300; }
|
||||
.attach-menu { background:#fff; border:1px solid #ddd; box-shadow:0 6px 18px rgba(0,0,0,0.08); border-radius:6px; padding:6px; display:none; position:absolute; z-index:1500; }
|
||||
.attach-menu .attach-option { display:block; width:100%; text-align:left; padding:6px 10px; border:none; background:transparent; font-size:14px; }
|
||||
.attach-menu .attach-option:hover { background:#f6f6f6; }
|
||||
|
||||
@@ -571,10 +587,6 @@
|
||||
#reply-preview button { border: none; color: #888; }
|
||||
#reply-preview button:hover { color: #333; }
|
||||
|
||||
.message.incoming .message-bubble::before,
|
||||
.message.outgoing .message-bubble::after { content: ''; }
|
||||
|
||||
|
||||
.notification-toast.urgent { border-left: 4px solid #e74c3c; }
|
||||
|
||||
/* Visual destacado para notificaciones de tipo "attention" (ej. usuario subió documentos) */
|
||||
@@ -662,7 +674,17 @@
|
||||
.message-media { margin-bottom: 6px; }
|
||||
|
||||
/* Quick replies: make them rectangular, full-width in panel, readable */
|
||||
.quick-replies-panel { background: #fff; border-radius: 8px; padding: 8px; box-shadow: 0 6px 18px rgba(0,0,0,0.08); }
|
||||
.quick-replies-panel {
|
||||
background: #fff;
|
||||
border-radius: 8px;
|
||||
padding: 8px;
|
||||
box-shadow: 0 6px 18px rgba(0,0,0,0.08);
|
||||
display: none;
|
||||
position: absolute;
|
||||
z-index: 1400;
|
||||
max-height: 180px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.quick-replies-panel .btn {
|
||||
border-radius: 6px;
|
||||
display: block;
|
||||
@@ -673,6 +695,13 @@
|
||||
white-space: normal;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
min-width: 72px;
|
||||
max-width: 200px;
|
||||
font-size: 13px;
|
||||
}
|
||||
#quick-replies-toggle { background: transparent; border-radius: 20px; }
|
||||
@media (max-width: 768px) {
|
||||
.quick-replies-panel { left: 8px; right: 8px; bottom: 56px; min-width: auto; }
|
||||
}
|
||||
|
||||
/* Recording controls: rectangular buttons and clearer contrast */
|
||||
@@ -693,10 +722,6 @@
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
/* Ensure mic button contrast */
|
||||
#mic-btn { background: var(--whatsapp-green); color: #fff; border-radius: 8px; padding: 8px 10px; }
|
||||
#mic-btn.recording { background: #c0392b; color: white; }
|
||||
|
||||
@media (max-width: 768px) {
|
||||
/* Fullscreen sidebar that can slide in/out */
|
||||
.chat-container { padding: 0; gap: 0; }
|
||||
@@ -771,7 +796,7 @@
|
||||
.reaction-badge { background:#fff; border:1px solid rgba(0,0,0,0.06); display:inline-flex; align-items:center; justify-content:center; padding:4px 6px; border-radius:14px; font-size:13px; margin-top:6px; }
|
||||
|
||||
/* Reaction picker */
|
||||
#reaction-picker { position:fixed; display:none; z-index:2000; background:#fff; border-radius:10px; box-shadow:0 6px 24px rgba(0,0,0,0.16); padding:8px; min-width:160px; transition:transform .12s ease, opacity .12s ease; transform:scale(.96); opacity:0; }
|
||||
#reaction-picker { position:fixed; display:none; z-index:9000; background:#fff; border-radius:10px; box-shadow:0 6px 24px rgba(0,0,0,0.16); padding:8px; min-width:160px; transition:transform .12s ease, opacity .12s ease; transform:scale(.96); opacity:0; }
|
||||
#reaction-picker.show { transform:scale(1); opacity:1; }
|
||||
#reaction-picker .emoji { font-size:18px; padding:6px; cursor:pointer; border-radius:6px; margin:4px; display:inline-flex; align-items:center; justify-content:center; }
|
||||
#reaction-picker .emoji:hover { background: rgba(0,0,0,0.04); }
|
||||
@@ -793,7 +818,11 @@
|
||||
<div class="chat-sidebar">
|
||||
<div class="sidebar-header">
|
||||
<div>
|
||||
<h5 class="mb-0">💬 Conversaciones</h5>
|
||||
<h5 class="mb-0">💬 Conversaciones
|
||||
<span class="badge bg-warning text-dark ms-2" style="font-size: 9px; padding: 2px 6px; animation: pulse 2s infinite;">
|
||||
v1.2.1-<?php echo substr(time(), -4); ?>
|
||||
</span>
|
||||
</h5>
|
||||
</div>
|
||||
<div>
|
||||
<a href="index.php" class="text-white text-decoration-none">
|
||||
@@ -960,9 +989,40 @@
|
||||
</div>
|
||||
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.0/js/bootstrap.bundle.min.js"></script>
|
||||
<?php $asset_v = file_exists(__DIR__ . '/assets/js/chat-common.js') ? filemtime(__DIR__ . '/assets/js/chat-common.js') : time(); ?>
|
||||
|
||||
<!-- DETECCIÓN DE VERSIÓN - NO BORRAR -->
|
||||
<script>
|
||||
// ESTE LOG DEBE APARECER PRIMERO
|
||||
console.clear();
|
||||
console.log('%c═══════════════════════════════════════════', 'color: #25d366; font-weight: bold;');
|
||||
console.log('%c🚀 WHATSAPP BOT v1.2.0 - CARGANDO...', 'background: #25d366; color: white; padding: 10px 20px; font-size: 18px; font-weight: bold; border-radius: 5px;');
|
||||
console.log('%c📅 Build: 27 Enero 2026 - 16:45 hrs', 'color: #2575fc; font-weight: bold; font-size: 14px;');
|
||||
console.log('%c═══════════════════════════════════════════', 'color: #25d366; font-weight: bold;');
|
||||
window.__APP_VERSION__ = '1.2.0';
|
||||
window.__BUILD_TIME__ = '<?php echo date("Y-m-d H:i:s"); ?>';
|
||||
</script>
|
||||
|
||||
<?php
|
||||
// Forzar recarga con timestamp actual + microtime para desarrollo
|
||||
$asset_v = time() . '.' . rand(10000, 99999) . '.' . substr(microtime(true) * 1000, -4);
|
||||
try {
|
||||
$chat_common_path = __DIR__ . '/assets/js/chat-common.js';
|
||||
if (file_exists($chat_common_path)) {
|
||||
$asset_v = filemtime($chat_common_path) . '.' . rand(10000, 99999) . '.' . substr(microtime(true) * 1000, -4);
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
error_log('Error getting filemtime for chat-common.js: ' . $e->getMessage());
|
||||
}
|
||||
?>
|
||||
<script src="assets/js/chat-common.js?v=<?php echo $asset_v; ?>"></script>
|
||||
<script>
|
||||
// ============================================
|
||||
// 🚀 VERSIÓN ACTUALIZADA - 27 ENERO 2026
|
||||
// ============================================
|
||||
console.log('%c📦 Asset Version:', 'font-weight: bold; color: #2575fc;', '<?php echo $asset_v; ?>');
|
||||
console.log('%c✨ Cambios: SSE mejorado, updateConversationInList con logging completo', 'color: #666;');
|
||||
console.log('============================================');
|
||||
|
||||
// Fallback ligero para showAlert (si no existe una implementación global)
|
||||
if (typeof showAlert === 'undefined') {
|
||||
function showAlert(message, type = 'info') {
|
||||
@@ -992,14 +1052,18 @@
|
||||
|
||||
class WhatsAppChat {
|
||||
constructor() {
|
||||
console.log('%c✅ WhatsAppChat v1.2.0 - Constructor iniciado', 'background: #10b981; color: white; padding: 4px 8px; font-weight: bold; border-radius: 3px;');
|
||||
this.currentConversationId = null;
|
||||
this.currentUserId = null;
|
||||
this.conversationsList = []; // Lista de usuarios/conversaciones en el sidebar
|
||||
this.conversations = []; // Lista de usuarios/conversaciones en el sidebar
|
||||
console.log('📋 conversations array inicializado:', this.conversations);
|
||||
this.currentMessages = []; // Mensajes de la conversación activa
|
||||
// Track shown notifications to avoid duplicates from polling
|
||||
this._shownNotifications = new Set();
|
||||
// Track notifications the user dismissed/read locally so they don't reappear
|
||||
this._dismissedNotifications = new Set();
|
||||
// Track processed notifications to avoid SSE duplicates
|
||||
this._processedNotifications = new Set();
|
||||
// Map of pending ack notifications to retry marking as read (nid => notification)
|
||||
this._pendingAck = new Map();
|
||||
// Message pagination / loading state
|
||||
@@ -1040,10 +1104,12 @@
|
||||
// Removed: automatic "Nuevos mensajes" indicator — we always reload full conversation now
|
||||
// this._lastRenderMessageCount = 0;
|
||||
|
||||
console.log('✅ WhatsAppChat inicializado - conversations array:', this.conversations);
|
||||
this.init();
|
||||
}
|
||||
|
||||
init() {
|
||||
console.log('🎯 Iniciando WhatsAppChat v1.2.0...');
|
||||
this.loadConversations();
|
||||
this.setupEventListeners();
|
||||
this.setupAutoRefresh();
|
||||
@@ -1052,21 +1118,38 @@
|
||||
this.setupNotificationAckRetry && this.setupNotificationAckRetry();
|
||||
// Conectar a SSE para notificaciones en tiempo real
|
||||
this.connectSSE();
|
||||
|
||||
// Mostrar confirmación de versión cargada
|
||||
this.showVersionNotification();
|
||||
}
|
||||
|
||||
showVersionNotification() {
|
||||
console.log('📢 Mostrando notificación de versión');
|
||||
const toast = document.createElement('div');
|
||||
toast.className = 'notification-toast cool';
|
||||
toast.style.cssText = 'position: fixed; top: 20px; right: 20px; z-index: 10000;';
|
||||
toast.innerHTML = `
|
||||
<span class="nt-icon">🚀</span>
|
||||
<div>
|
||||
<strong>Versión 1.2.0 Cargada</strong><br>
|
||||
<small style="opacity: 0.9;">SSE mejorado - 27 Ene 2026</small>
|
||||
</div>
|
||||
`;
|
||||
document.body.appendChild(toast);
|
||||
|
||||
// Auto-hide después de 4 segundos
|
||||
setTimeout(() => {
|
||||
toast.style.transition = 'opacity 0.3s, transform 0.3s';
|
||||
toast.style.opacity = '0';
|
||||
toast.style.transform = 'translateX(100%)';
|
||||
setTimeout(() => toast.remove(), 300);
|
||||
}, 4000);
|
||||
}
|
||||
|
||||
setupNotificationPolling() {
|
||||
// Carga inicial de notificaciones pendientes (opcional)
|
||||
// SSE se encargará de las nuevas en tiempo real
|
||||
this.loadNotifications().catch(e => {
|
||||
console.debug('Carga inicial de notificaciones omitida (SSE activo):', e.message);
|
||||
});
|
||||
|
||||
// Backup: verificar cada 60 segundos (solo por si SSE falla)
|
||||
setInterval(() => {
|
||||
this.loadNotifications().catch(e => {
|
||||
console.debug('Polling de notificaciones omitido (SSE activo)');
|
||||
});
|
||||
}, 60000);
|
||||
// 🎯 OPTIMIZADO: Las notificaciones ahora solo llegan por SSE
|
||||
// No necesitamos polling ni carga inicial
|
||||
console.debug('⚡ setupNotificationPolling: DESHABILITADO - SSE maneja todo en tiempo real');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1081,8 +1164,8 @@
|
||||
console.log('Conectando a SSE para eventos en tiempo real...');
|
||||
|
||||
try {
|
||||
// Detectar URL base automáticamente (funciona en dev y producción)
|
||||
const baseUrl = window.location.origin; // http://localhost:8000 o https://tu-dominio.com
|
||||
// Detectar URL base automáticamente (funciona en cualquier entorno)
|
||||
const baseUrl = window.location.origin;
|
||||
const sseUrl = `${baseUrl}/api/sse_events.php?token=demo_token&t=${Date.now()}`;
|
||||
|
||||
console.log('SSE URL:', sseUrl);
|
||||
@@ -1103,20 +1186,72 @@
|
||||
|
||||
// Evento: nuevo mensaje entrante
|
||||
this.eventSource.addEventListener('new_message', (e) => {
|
||||
console.log('📨 Nuevo mensaje (SSE):', e.data);
|
||||
const data = JSON.parse(e.data);
|
||||
|
||||
// Si la conversación del mensaje es la actualmente abierta, recargar mensajes
|
||||
if (this.currentUserId && String(this.currentUserId) === String(data.user_id)) {
|
||||
console.log('Recargando mensajes de conversación activa...');
|
||||
this.loadMessages(this.currentUserId, false, true);
|
||||
console.log('📨 SSE new_message recibido:', e.data);
|
||||
try {
|
||||
const data = JSON.parse(e.data);
|
||||
console.log('📨 Datos parseados del mensaje:', data);
|
||||
|
||||
// Extraer user_id del mensaje
|
||||
const userId = data.user_id || data.from_user_id || data.sender_id;
|
||||
console.log('👤 User ID del mensaje:', userId, '| Conversación actual:', this.currentUserId);
|
||||
|
||||
// Si la conversación del mensaje es la actualmente abierta
|
||||
if (this.currentUserId && String(this.currentUserId) === String(userId)) {
|
||||
console.log('♻️ Mensaje para conversación ACTIVA, procesando...');
|
||||
|
||||
// Verificar si el usuario está al final del chat (dentro de 150px del final)
|
||||
const container = document.getElementById('chat-conversations');
|
||||
const isAtBottom = container ?
|
||||
(container.scrollHeight - container.scrollTop - container.clientHeight) < 150 : true;
|
||||
|
||||
console.log('📍 Posición scroll:', {
|
||||
scrollHeight: container?.scrollHeight,
|
||||
scrollTop: container?.scrollTop,
|
||||
clientHeight: container?.clientHeight,
|
||||
distanceFromBottom: container ? (container.scrollHeight - container.scrollTop - container.clientHeight) : 0,
|
||||
isAtBottom: isAtBottom
|
||||
});
|
||||
|
||||
if (isAtBottom) {
|
||||
// Usuario está al final: agregar mensaje automáticamente con highlight
|
||||
console.log('✅ Usuario AL FINAL, recargando mensajes...');
|
||||
|
||||
// Marcar que el próximo mensaje nuevo debe tener highlight
|
||||
this._nextMessageUnread = true;
|
||||
|
||||
// Recargar mensajes (esto hará el fetch y renderizará)
|
||||
this.loadMessages(this.currentUserId, true, true).then(() => {
|
||||
// Después de cargar, quitar el highlight después de 3 segundos
|
||||
setTimeout(() => {
|
||||
this._removeUnreadHighlights();
|
||||
}, 3000);
|
||||
});
|
||||
} else {
|
||||
// Usuario está leyendo arriba: mostrar indicador de nuevos mensajes
|
||||
console.log('⬆️ Usuario LEYENDO ARRIBA, mostrando indicador');
|
||||
this.showNewMessagesIndicator(1);
|
||||
|
||||
// Guardar mensaje en staging para agregarlo cuando el usuario baje
|
||||
if (!this._stagedMessages) this._stagedMessages = [];
|
||||
this._stagedMessages.push(data);
|
||||
console.log('💾 Mensaje guardado en staging. Total staged:', this._stagedMessages.length);
|
||||
}
|
||||
} else {
|
||||
console.log('📋 Mensaje para OTRA conversación, solo actualizando lista');
|
||||
}
|
||||
|
||||
// Actualizar la lista de conversaciones para mostrar el nuevo mensaje
|
||||
console.log('📋 Actualizando lista de conversaciones...');
|
||||
this.updateConversationInList(data);
|
||||
|
||||
// Reproducir sonido de notificación solo si no es la conversación activa
|
||||
if (!this.currentUserId || String(this.currentUserId) !== String(userId)) {
|
||||
console.log('🔔 Reproduciendo sonido de notificación');
|
||||
this.playNotificationSound();
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('❌ Error procesando new_message:', err);
|
||||
}
|
||||
|
||||
// Actualizar la lista de conversaciones para mostrar el nuevo mensaje
|
||||
this.updateConversationInList(data);
|
||||
|
||||
// Reproducir sonido de notificación
|
||||
this.playNotificationSound();
|
||||
});
|
||||
|
||||
// Evento: nueva conversación detectada
|
||||
@@ -1136,6 +1271,26 @@
|
||||
console.log('🔔 Nueva notificación (SSE):', e.data);
|
||||
try {
|
||||
const notification = JSON.parse(e.data);
|
||||
|
||||
// Deduplicación: evitar procesar la misma notificación múltiples veces
|
||||
const notificationKey = `notif_${notification.id}_${notification.created_at}`;
|
||||
if (this._processedNotifications.has(notificationKey)) {
|
||||
console.debug('⏭️ Notificación ya procesada, omitiendo:', notification.id);
|
||||
return;
|
||||
}
|
||||
this._processedNotifications.add(notificationKey);
|
||||
|
||||
// Actualizar la lista de conversaciones con los datos de la notificación
|
||||
if (notification.user_id && notification.message) {
|
||||
console.log('📋 Actualizando conversación desde notificación...');
|
||||
this.updateConversationInList({
|
||||
user_id: notification.user_id,
|
||||
message: notification.message,
|
||||
timestamp: notification.created_at
|
||||
});
|
||||
}
|
||||
|
||||
// Mostrar toast de notificación
|
||||
this.showNotificationToast(notification);
|
||||
} catch (err) {
|
||||
console.error('Error procesando notificación SSE:', err);
|
||||
@@ -1225,40 +1380,84 @@
|
||||
*/
|
||||
updateConversationInList(data) {
|
||||
try {
|
||||
console.log('📝 Actualizando conversación en lista:', data);
|
||||
|
||||
// Asegurar que conversations esté inicializado
|
||||
if (!this.conversations || !Array.isArray(this.conversations)) {
|
||||
this.conversations = [];
|
||||
console.warn('⚠️ conversations array vacío, recargando lista completa...');
|
||||
this.loadConversations();
|
||||
return;
|
||||
}
|
||||
|
||||
// Extraer user_id de diferentes formatos posibles
|
||||
const userId = data.user_id || data.from_user_id || data.sender_id;
|
||||
if (!userId) {
|
||||
console.warn('⚠️ No se pudo obtener user_id de los datos:', data);
|
||||
return;
|
||||
}
|
||||
|
||||
// Si no viene el mensaje, recargar la lista completa para obtener datos actualizados
|
||||
if (!data.message && !data.content && !data.text && !data.last_message) {
|
||||
console.log('⚠️ Evento SSE sin contenido de mensaje, recargando lista completa...');
|
||||
// Forzar recarga temporal (sin await, ejecutar en background)
|
||||
const wasLoaded = this._conversationsLoaded;
|
||||
this._conversationsLoaded = false;
|
||||
this.loadConversations().then(() => {
|
||||
this._conversationsLoaded = wasLoaded;
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Extraer mensaje de diferentes formatos
|
||||
const message = data.message || data.content || data.text || data.last_message || 'Nuevo mensaje';
|
||||
const timestamp = data.timestamp || data.created_at || new Date().toISOString();
|
||||
|
||||
// Buscar la conversación en el array
|
||||
const existingIndex = this.conversations.findIndex(c => String(c.user_id) === String(data.user_id));
|
||||
const existingIndex = this.conversations.findIndex(c => String(c.user_id) === String(userId));
|
||||
|
||||
if (existingIndex !== -1) {
|
||||
console.log('✅ Conversación encontrada, actualizando...');
|
||||
// Actualizar conversación existente
|
||||
this.conversations[existingIndex].last_message = data.message;
|
||||
this.conversations[existingIndex].last_message_time = data.timestamp;
|
||||
this.conversations[existingIndex].unread_count = (this.conversations[existingIndex].unread_count || 0) + 1;
|
||||
const conv = this.conversations[existingIndex];
|
||||
conv.last_message = message;
|
||||
conv.last_time = timestamp;
|
||||
|
||||
// Solo incrementar unread_count si no es la conversación activa
|
||||
if (String(this.currentUserId) !== String(userId)) {
|
||||
conv.unread_count = (conv.unread_count || 0) + 1;
|
||||
}
|
||||
|
||||
// Mover al inicio de la lista
|
||||
const conv = this.conversations.splice(existingIndex, 1)[0];
|
||||
this.conversations.splice(existingIndex, 1);
|
||||
this.conversations.unshift(conv);
|
||||
} else {
|
||||
console.log('➕ Conversación no existe, agregando nueva...');
|
||||
// Nueva conversación, agregar al inicio
|
||||
this.conversations.unshift({
|
||||
user_id: data.user_id,
|
||||
name: data.name,
|
||||
phone_number: data.phone_number,
|
||||
last_message: data.message,
|
||||
last_message_time: data.timestamp,
|
||||
unread_count: 1
|
||||
user_id: userId,
|
||||
name: data.name || data.sender_name || data.phone_number || `Usuario ${userId}`,
|
||||
phone_number: data.phone_number || data.phone || '',
|
||||
last_message: message,
|
||||
last_time: timestamp,
|
||||
unread_count: String(this.currentUserId) !== String(userId) ? 1 : 0
|
||||
});
|
||||
}
|
||||
|
||||
// Re-renderizar solo la lista de conversaciones
|
||||
this.renderConversations();
|
||||
console.log('🔄 Re-renderizando lista de conversaciones...');
|
||||
console.log('🔍 Verificando this:', this);
|
||||
console.log('🔍 this.renderConversations existe?', typeof this.renderConversations);
|
||||
console.log('🔍 this.conversations:', this.conversations);
|
||||
|
||||
if (typeof this.renderConversations === 'function') {
|
||||
console.log('✅ Llamando a renderConversations()...');
|
||||
this.renderConversations();
|
||||
} else {
|
||||
console.error('❌ renderConversations no es una función!');
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error actualizando conversación en lista:', error);
|
||||
console.error('❌ Error actualizando conversación en lista:', error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1267,105 +1466,62 @@
|
||||
*/
|
||||
addConversationToList(data) {
|
||||
try {
|
||||
console.log('➕ Agregando nueva conversación:', data);
|
||||
|
||||
// Asegurar que conversations esté inicializado
|
||||
if (!this.conversations || !Array.isArray(this.conversations)) {
|
||||
console.warn('⚠️ conversations array no estaba inicializado, inicializando ahora...');
|
||||
this.conversations = [];
|
||||
console.warn('⚠️ conversations array no estaba inicializado, recargando...');
|
||||
this.loadConversations();
|
||||
return;
|
||||
}
|
||||
|
||||
// Extraer user_id
|
||||
const userId = data.user_id || data.from_user_id || data.sender_id;
|
||||
if (!userId) {
|
||||
console.warn('⚠️ No se pudo obtener user_id');
|
||||
return;
|
||||
}
|
||||
|
||||
// Verificar si ya existe
|
||||
const exists = this.conversations.some(c => String(c.user_id) === String(data.user_id));
|
||||
const exists = this.conversations.some(c => String(c.user_id) === String(userId));
|
||||
if (exists) {
|
||||
console.log('🔄 Conversación ya existe, actualizando...');
|
||||
return this.updateConversationInList(data);
|
||||
}
|
||||
|
||||
// Extraer datos
|
||||
const message = data.message || data.content || data.text || 'Nueva conversación';
|
||||
const timestamp = data.timestamp || data.created_at || new Date().toISOString();
|
||||
const name = data.name || data.sender_name || data.phone_number || `Usuario ${userId}`;
|
||||
|
||||
// Agregar al inicio
|
||||
this.conversations.unshift({
|
||||
user_id: data.user_id,
|
||||
name: data.name,
|
||||
phone_number: data.phone_number,
|
||||
last_message: 'Nueva conversación',
|
||||
last_message_time: data.timestamp,
|
||||
unread_count: data.message_count || 1
|
||||
user_id: userId,
|
||||
name: name,
|
||||
phone_number: data.phone_number || data.phone || '',
|
||||
last_message: message,
|
||||
last_time: timestamp,
|
||||
unread_count: String(this.currentUserId) !== String(userId) ? (data.message_count || 1) : 0
|
||||
});
|
||||
|
||||
console.log('✅ Conversación agregada, re-renderizando...');
|
||||
// Re-renderizar lista
|
||||
this.renderConversations();
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error agregando conversación:', error);
|
||||
console.error('❌ Error agregando conversación:', error);
|
||||
}
|
||||
}
|
||||
|
||||
async loadNotifications() {
|
||||
try {
|
||||
const resp = await fetch('api/get_notifications.php', { credentials: 'same-origin' });
|
||||
|
||||
if (!resp.ok) {
|
||||
// Si falla, no es problema: SSE manejará las notificaciones en tiempo real
|
||||
console.debug(`Notifications fetch: HTTP ${resp.status} (SSE manejará las notificaciones)`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (resp.status === 401) {
|
||||
console.debug('Notifications fetch: unauthorized (SSE manejará las notificaciones)');
|
||||
return;
|
||||
}
|
||||
|
||||
const json = await resp.json();
|
||||
console.debug('loadNotifications response:', json);
|
||||
if (json && json.success && Array.isArray(json.data)) {
|
||||
json.data.forEach(n => this.showNotificationToast(n));
|
||||
} else if (json && json.success === false) {
|
||||
console.debug('get_notifications returned error (no crítico, SSE activo):', json.error || json);
|
||||
}
|
||||
} catch (e) {
|
||||
// Error no crítico: SSE manejará las notificaciones en tiempo real
|
||||
console.debug('loadNotifications failed (SSE manejará las notificaciones):', e.message);
|
||||
}
|
||||
// 🎯 ELIMINADO: Las notificaciones ahora solo llegan por SSE en tiempo real
|
||||
// No se hace fetch a get_notifications.php
|
||||
console.debug('⚡ loadNotifications: SSE maneja todas las notificaciones en tiempo real');
|
||||
return;
|
||||
}
|
||||
|
||||
// Try to mark a notification as read on the server. Returns true on success.
|
||||
async ackNotification(notification) {
|
||||
if (!notification || !notification.id) return true; // nothing to ack on server
|
||||
try {
|
||||
const resp = await fetch('api/mark_notification_read.php', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type':'application/json'},
|
||||
body: JSON.stringify({ id: notification.id })
|
||||
});
|
||||
if (!resp.ok) {
|
||||
console.warn('ackNotification: server responded with', resp.status);
|
||||
return false;
|
||||
}
|
||||
const j = await resp.json().catch(() => null);
|
||||
return !!(j && j.success) || resp.ok;
|
||||
} catch (e) {
|
||||
console.warn('ackNotification failed', e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
setupNotificationAckRetry() {
|
||||
if (this._ackInterval) return;
|
||||
this._ackInterval = setInterval(async () => {
|
||||
if (this._pendingAck.size === 0) return;
|
||||
for (const [nid, notification] of Array.from(this._pendingAck.entries())) {
|
||||
try {
|
||||
const ok = await this.ackNotification(notification);
|
||||
if (ok) {
|
||||
this._pendingAck.delete(nid);
|
||||
this._dismissedNotifications.add(nid);
|
||||
console.debug('Ack retry succeeded for', nid);
|
||||
} else {
|
||||
console.debug('Ack retry still failing for', nid);
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('Ack retry error for', nid, e);
|
||||
}
|
||||
}
|
||||
}, 30000); // every 30s
|
||||
}
|
||||
// SIMPLIFICADO: Las notificaciones ahora son solo en tiempo real
|
||||
// No se guardan en BD ni necesitan marcarse como leídas
|
||||
|
||||
async showNotificationToast(notification) {
|
||||
// Create or reuse container
|
||||
@@ -1390,20 +1546,8 @@
|
||||
return;
|
||||
}
|
||||
|
||||
// mark as shown immediately and try to ack on server so it doesn't reappear
|
||||
// mark as shown immediately (solo en memoria, no en servidor)
|
||||
this._shownNotifications.add(nid);
|
||||
try {
|
||||
const ok = await this.ackNotification(notification);
|
||||
if (ok) {
|
||||
this._dismissedNotifications.add(nid);
|
||||
this._pendingAck.delete(nid);
|
||||
} else {
|
||||
// schedule for retry
|
||||
this._pendingAck.set(nid, notification);
|
||||
}
|
||||
} catch (e) {
|
||||
this._pendingAck.set(nid, notification);
|
||||
}
|
||||
|
||||
const toast = document.createElement('div');
|
||||
// compact by default
|
||||
@@ -1443,16 +1587,8 @@
|
||||
openBtn.className = notification.cool ? 'btn btn-sm btn-light' : (isAttention ? 'btn btn-sm btn-warning' : 'btn btn-sm btn-primary');
|
||||
openBtn.textContent = (notification.open_label && notification.open_label !== 'Ver') ? notification.open_label : 'Abrir';
|
||||
openBtn.onclick = async () => {
|
||||
// Try ack on server; if fails, schedule retry. Always mark as dismissed locally so it won't reappear.
|
||||
try {
|
||||
const ok = await this.ackNotification(notification);
|
||||
if (ok) {
|
||||
this._dismissedNotifications.add(nid);
|
||||
this._pendingAck.delete(nid);
|
||||
} else {
|
||||
this._pendingAck.set(nid, notification);
|
||||
}
|
||||
} catch (e) { this._pendingAck.set(nid, notification); }
|
||||
// Marcar como descartada localmente
|
||||
this._dismissedNotifications.add(nid);
|
||||
removeToastLocal();
|
||||
// navigate to conv or open media if present
|
||||
let data = {};
|
||||
@@ -1462,7 +1598,7 @@
|
||||
if (fileUrl && this.currentUserId && String(this.currentUserId) === String(userId)) {
|
||||
try { openMediaLightbox(fileUrl, data.file_name || ''); } catch (e) { console.warn('openMediaLightbox failed', e); }
|
||||
} else if (userId) {
|
||||
const conv = this.conversationsList.find(c => c.user_id == userId);
|
||||
const conv = this.conversations.find(c => c.user_id == userId);
|
||||
if (conv) {
|
||||
// Prefetch messages then open conversation using preloaded data to avoid double-fetch
|
||||
try {
|
||||
@@ -1493,15 +1629,8 @@
|
||||
dismiss.textContent = '×';
|
||||
dismiss.title = 'Descartar';
|
||||
dismiss.onclick = async () => {
|
||||
try {
|
||||
const ok = await this.ackNotification(notification);
|
||||
if (ok) {
|
||||
this._dismissedNotifications.add(nid);
|
||||
this._pendingAck.delete(nid);
|
||||
} else {
|
||||
this._pendingAck.set(nid, notification);
|
||||
}
|
||||
} catch(e) { this._pendingAck.set(nid, notification); }
|
||||
// Marcar como descartada localmente
|
||||
this._dismissedNotifications.add(nid);
|
||||
removeToastLocal();
|
||||
};
|
||||
|
||||
@@ -1527,19 +1656,9 @@
|
||||
if (!isFinite(timeout) || timeout <= 0) timeout = (notification.cool ? 1000 : 1000);
|
||||
timeout = Math.max(timeout, _minToastDuration);
|
||||
setTimeout(() => {
|
||||
// on automatic timeout, attempt ack and schedule retry if needed
|
||||
(async () => {
|
||||
try {
|
||||
const ok = await this.ackNotification(notification);
|
||||
if (ok) {
|
||||
this._dismissedNotifications.add(nid);
|
||||
this._pendingAck.delete(nid);
|
||||
} else {
|
||||
this._pendingAck.set(nid, notification);
|
||||
}
|
||||
} catch (e) { this._pendingAck.set(nid, notification); }
|
||||
removeToastLocal();
|
||||
})();
|
||||
// Auto-descartar: solo limpiar localmente
|
||||
this._dismissedNotifications.add(nid);
|
||||
removeToastLocal();
|
||||
}, timeout);
|
||||
}
|
||||
|
||||
@@ -2269,6 +2388,20 @@
|
||||
const text = await response.text();
|
||||
|
||||
if (!response.ok) {
|
||||
// Si es 401, intentar parsear el JSON para obtener el mensaje real
|
||||
if (response.status === 401) {
|
||||
try {
|
||||
const errorData = JSON.parse(text);
|
||||
if (errorData.error) {
|
||||
alert('Sesión expirada: ' + errorData.error + '. Recargando página...');
|
||||
}
|
||||
} catch (e) {
|
||||
alert('Sesión expirada. Recargando página...');
|
||||
}
|
||||
// Forzar recarga para restablecer sesión
|
||||
setTimeout(() => window.location.reload(), 1000);
|
||||
return null;
|
||||
}
|
||||
// Incluir el cuerpo de la respuesta (truncado) para diagnóstico
|
||||
const snippet = text && text.length ? (text.length > 2000 ? text.substr(0, 2000) + '... (truncated)' : text) : '<no body>';
|
||||
console.error('apiCall HTTP error', response.status, url, snippet);
|
||||
@@ -2287,6 +2420,13 @@
|
||||
}
|
||||
|
||||
async loadConversations(page = 1, append = false) {
|
||||
// 🎯 OPTIMIZACIÓN: Solo cargar del servidor si es la primera vez O si es paginación
|
||||
// Después SSE se encarga de actualizar automáticamente
|
||||
if (this._conversationsLoaded && !append) {
|
||||
console.log('⚡ Conversaciones ya cargadas, SSE se encarga de actualizaciones');
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.loadingConversations) return;
|
||||
this.loadingConversations = true;
|
||||
const loadMoreContainer = document.getElementById('load-more-container');
|
||||
@@ -2320,13 +2460,20 @@
|
||||
}
|
||||
|
||||
if (append) {
|
||||
this.conversationsList = this.conversationsList.concat(items);
|
||||
this.conversations = this.conversations.concat(items);
|
||||
} else {
|
||||
this.conversationsList = items;
|
||||
this.conversations = items;
|
||||
}
|
||||
|
||||
this.hasMoreConversations = hasMore;
|
||||
this.conversationsPage = page;
|
||||
|
||||
// Marcar como cargadas después de la primera carga exitosa
|
||||
if (!append) {
|
||||
this._conversationsLoaded = true;
|
||||
console.log('✅ Primera carga de conversaciones completada, SSE tomará el control');
|
||||
}
|
||||
|
||||
this.renderConversations();
|
||||
|
||||
if (loadMoreContainer) {
|
||||
@@ -2349,9 +2496,17 @@
|
||||
|
||||
|
||||
renderConversations() {
|
||||
console.log('🎨 Renderizando conversaciones:', this.conversations.length);
|
||||
const container = document.getElementById('conversation-list');
|
||||
|
||||
if (this.conversationsList.length === 0) {
|
||||
if (!container) {
|
||||
console.error('❌ No se encontró el elemento #conversation-list en el DOM');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('✅ Container encontrado:', container);
|
||||
|
||||
if (this.conversations.length === 0) {
|
||||
const emptyMsg = this.conversationFilter === 'unread' ? 'No hay conversaciones no leídas' : 'No hay conversaciones aún';
|
||||
container.innerHTML = `
|
||||
<div class="text-center p-4">
|
||||
@@ -2359,11 +2514,14 @@
|
||||
<p class="text-muted">${emptyMsg}</p>
|
||||
</div>
|
||||
`;
|
||||
console.log('📭 Lista vacía, mostrando mensaje');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('📋 Generando HTML para', this.conversations.length, 'conversaciones');
|
||||
|
||||
// Ahora el backend devuelve por usuario: last_message, last_time, unread_count y avatar_url
|
||||
const html = this.conversationsList.map(conv => {
|
||||
const html = this.conversations.map(conv => {
|
||||
const isActive = conv.user_id == this.currentUserId ? 'active' : '';
|
||||
const time = this.formatTime(conv.last_time);
|
||||
const preview = this.truncateText(conv.last_message || 'Sin mensajes', 50);
|
||||
@@ -2394,10 +2552,12 @@
|
||||
</div>
|
||||
`;
|
||||
}).join('');
|
||||
|
||||
console.log('📄 HTML generado, longitud:', html.length, 'caracteres');
|
||||
|
||||
// Detectar nuevas notificaciones: comparar unread counts previos
|
||||
if (!this._prevConversations) this._prevConversations = {};
|
||||
this.conversationsList.forEach(c => {
|
||||
this.conversations.forEach(c => {
|
||||
const prev = this._prevConversations[c.user_id] || { unread_count: 0 };
|
||||
if (c.unread_count > prev.unread_count && c.user_id != this.currentUserId) {
|
||||
// nueva notificación
|
||||
@@ -2411,8 +2571,11 @@
|
||||
const prevScrollTop = container.scrollTop;
|
||||
const prevScrollHeight = container.scrollHeight;
|
||||
const wasNearTop = prevScrollTop < 60;
|
||||
|
||||
|
||||
console.log('🔄 Actualizando innerHTML del container...');
|
||||
container.innerHTML = html;
|
||||
console.log('✅ DOM actualizado con', this.conversations.length, 'conversaciones');
|
||||
console.log('📊 Elementos en DOM:', container.children.length);
|
||||
|
||||
const newScrollHeight = container.scrollHeight;
|
||||
const scrollDelta = newScrollHeight - prevScrollHeight;
|
||||
@@ -2498,7 +2661,7 @@
|
||||
}
|
||||
|
||||
getConversationPhone(userId) {
|
||||
const conv = this.conversationsList.find(c => c.user_id === userId);
|
||||
const conv = this.conversations.find(c => c.user_id === userId);
|
||||
if (conv) return conv.phone_number || conv.phone || conv.user_phone || null;
|
||||
const el = document.getElementById('chat-phone');
|
||||
return el ? el.textContent.trim() : null;
|
||||
@@ -2544,7 +2707,7 @@
|
||||
document.querySelector(`[data-user-id="${userId}"]`)?.classList.add('active');
|
||||
|
||||
// Actualizar estado del toggle del bot según datos de la conversación
|
||||
const conv = this.conversationsList.find(c => c.user_id === userId);
|
||||
const conv = this.conversations.find(c => c.user_id === userId);
|
||||
if (conv) {
|
||||
const btn = document.getElementById('bot-toggle');
|
||||
const holdIndicator = document.getElementById('hold-indicator');
|
||||
@@ -2572,8 +2735,8 @@
|
||||
}
|
||||
const json = await resp.json();
|
||||
if (json && json.success) {
|
||||
// Recargar lista de conversaciones para reflejar cambios
|
||||
await this.loadConversations();
|
||||
// SSE actualizará las conversaciones automáticamente
|
||||
// await this.loadConversations();
|
||||
alert('Conversación marcada como NO leída.');
|
||||
} else {
|
||||
alert('Error marcando conversación como no leída');
|
||||
@@ -2661,7 +2824,7 @@
|
||||
attendStatus.textContent = '';
|
||||
|
||||
// refresh conv reference
|
||||
const c = this.conversationsList.find(x => x.user_id === userId) || conv;
|
||||
const c = this.conversations.find(x => x.user_id === userId) || conv;
|
||||
|
||||
// Update only the in-chat status message based on server/local state
|
||||
this.getUserState(userId).then(serverState => {
|
||||
@@ -2682,7 +2845,7 @@
|
||||
|
||||
const toggleAttend = async () => {
|
||||
try {
|
||||
const c = this.conversationsList.find(x => x.user_id === userId) || conv;
|
||||
const c = this.conversations.find(x => x.user_id === userId) || conv;
|
||||
|
||||
// Preflight: obtener estado canonico del servidor con cache corta
|
||||
const serverState = await this.getUserState(userId, true);
|
||||
@@ -3521,6 +3684,12 @@
|
||||
div.className = 'message ' + (msg.direction || 'incoming');
|
||||
div.dataset.messageId = mid;
|
||||
div.dataset.createdAt = msg.created_at || '';
|
||||
|
||||
// Marcar mensajes nuevos como no leídos si es el último mensaje y viene de SSE
|
||||
if (this._nextMessageUnread && i === messagesToRender.length - 1 && msg.direction === 'incoming') {
|
||||
div.classList.add('unread');
|
||||
div.dataset.isNewUnread = 'true';
|
||||
}
|
||||
|
||||
let replyHtml = '';
|
||||
if (msg.reply_to_message_id) {
|
||||
@@ -3988,6 +4157,21 @@
|
||||
els.forEach(el => { try { if (el && el.parentNode) el.parentNode.removeChild(el); } catch(e){} });
|
||||
} catch (e) { /* ignore */ }
|
||||
}
|
||||
|
||||
_removeUnreadHighlights() {
|
||||
try {
|
||||
console.log('🎨 Quitando highlights de mensajes no leídos');
|
||||
const unreadMessages = document.querySelectorAll('.message.unread');
|
||||
unreadMessages.forEach(msg => {
|
||||
msg.classList.remove('unread');
|
||||
delete msg.dataset.isNewUnread;
|
||||
});
|
||||
// Reset flag
|
||||
this._nextMessageUnread = false;
|
||||
} catch (e) {
|
||||
console.warn('Error quitando highlights:', e);
|
||||
}
|
||||
}
|
||||
|
||||
applyStagedMessages() {
|
||||
try {
|
||||
@@ -4159,7 +4343,7 @@
|
||||
// If the template expects a named parameter `name`, provide it using user name or phone
|
||||
if (templateName === 'contacto_nuevo') {
|
||||
// Try to find conversation info
|
||||
let conv = this.conversationsList.find(c => c.user_id === this.currentUserId) || {};
|
||||
let conv = this.conversations.find(c => c.user_id === this.currentUserId) || {};
|
||||
let resolvedName = (conv.name || conv.full_name || '').trim();
|
||||
if (!resolvedName) {
|
||||
const nameEl = document.getElementById('chat-name-text') || document.getElementById('chat-name');
|
||||
@@ -4199,7 +4383,8 @@
|
||||
this.addMessageToView(`Plantilla: ${templateName}`, 'outgoing', { reply_to_message_id: replyToTemplate });
|
||||
typeof showAlert !== 'undefined' && showAlert('Mensaje de plantilla enviado correctamente', 'success');
|
||||
await this.loadconversations(this.currentUserId, false);
|
||||
await this.loadConversations();
|
||||
// SSE actualizará las conversaciones automáticamente
|
||||
// await this.loadConversations();
|
||||
} else {
|
||||
throw new Error(resp && resp.error ? resp.error : 'Error enviando plantilla');
|
||||
}
|
||||
@@ -4369,8 +4554,9 @@
|
||||
typeof showAlert !== 'undefined' && showAlert('Mensaje enviado correctamente', 'success');
|
||||
// Recargar mensajes y conversaciones en background
|
||||
this.loadconversations(this.currentUserId, false).catch(e=>console.warn(e));
|
||||
this.loadConversations().catch(e=>console.warn(e));
|
||||
this.loadQuickReplies().catch(e=>console.warn(e));
|
||||
// SSE actualizará las conversaciones automáticamente
|
||||
// this.loadConversations().catch(e=>console.warn(e));
|
||||
// loadQuickReplies ya se cargó al abrir la conversación, no es necesario recargar después de cada mensaje
|
||||
} else {
|
||||
throw new Error(result && result.error ? result.error : 'Error desconocido');
|
||||
}
|
||||
@@ -4598,15 +4784,17 @@
|
||||
const sendContentType = sendResponse.headers.get('content-type') || '';
|
||||
let sendResult = null;
|
||||
|
||||
// Leer el body una sola vez para evitar error "body stream already read"
|
||||
const sendResponseText = await sendResponse.text();
|
||||
|
||||
if (sendContentType.indexOf('application/json') !== -1) {
|
||||
try {
|
||||
sendResult = await sendResponse.json();
|
||||
sendResult = JSON.parse(sendResponseText);
|
||||
} catch (err) {
|
||||
// Content-Type claims JSON but parsing failed: attempt to extract JSON from body
|
||||
const text = await sendResponse.text();
|
||||
console.warn('send_media_message: Content-Type JSON but parse failed. Response body will be inspected for JSON.');
|
||||
console.warn(text);
|
||||
const m = text.match(/(\{[\s\S]*\})/);
|
||||
console.warn(sendResponseText);
|
||||
const m = sendResponseText.match(/(\{[\s\S]*\})/);
|
||||
if (m) {
|
||||
try { sendResult = JSON.parse(m[1]); console.warn('send_media_message: extracted JSON from response.'); } catch (e) { console.warn('send_media_message: extracted JSON parse failed', e); }
|
||||
}
|
||||
@@ -4622,10 +4810,9 @@
|
||||
}
|
||||
} else {
|
||||
// Non-JSON content-type: try to find embedded JSON; if HTTP 200, be forgiving
|
||||
const text = await sendResponse.text();
|
||||
console.warn('send_media_message returned non-JSON response:');
|
||||
console.warn(text.slice(0, 2000));
|
||||
const m = text.match(/(\{[\s\S]*\})/);
|
||||
console.warn(sendResponseText.slice(0, 2000));
|
||||
const m = sendResponseText.match(/(\{[\s\S]*\})/);
|
||||
if (m) {
|
||||
try { sendResult = JSON.parse(m[1]); console.warn('send_media_message: extracted JSON from non-JSON response.'); } catch (e) { console.warn('send_media_message: failed to parse extracted JSON', e); }
|
||||
}
|
||||
@@ -4649,7 +4836,8 @@
|
||||
// Éxito
|
||||
this.cancelMediaUpload();
|
||||
await this.loadconversations(this.currentUserId, false);
|
||||
this.loadConversations();
|
||||
// SSE actualizará las conversaciones automáticamente
|
||||
// this.loadConversations();
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error sending media:', error);
|
||||
|
||||
Reference in New Issue
Block a user