Update conversations.php
This commit is contained in:
+100
-5
@@ -772,6 +772,10 @@
|
|||||||
this.conversations = [];
|
this.conversations = [];
|
||||||
// Track shown notifications to avoid duplicates from polling
|
// Track shown notifications to avoid duplicates from polling
|
||||||
this._shownNotifications = new Set();
|
this._shownNotifications = new Set();
|
||||||
|
// Track notifications the user dismissed/read locally so they don't reappear
|
||||||
|
this._dismissedNotifications = new Set();
|
||||||
|
// Map of pending ack notifications to retry marking as read (nid => notification)
|
||||||
|
this._pendingAck = new Map();
|
||||||
// Message pagination / loading state
|
// Message pagination / loading state
|
||||||
this.messageLimit = 50;
|
this.messageLimit = 50;
|
||||||
this.loadingMessages = false;
|
this.loadingMessages = false;
|
||||||
@@ -793,6 +797,8 @@
|
|||||||
this.setupEventListeners();
|
this.setupEventListeners();
|
||||||
this.setupAutoRefresh();
|
this.setupAutoRefresh();
|
||||||
this.setupNotificationPolling();
|
this.setupNotificationPolling();
|
||||||
|
// start background retry loop to ack dismissed notifications on server
|
||||||
|
this.setupNotificationAckRetry && this.setupNotificationAckRetry();
|
||||||
}
|
}
|
||||||
|
|
||||||
setupNotificationPolling() {
|
setupNotificationPolling() {
|
||||||
@@ -820,6 +826,48 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
async showNotificationToast(notification) {
|
async showNotificationToast(notification) {
|
||||||
// Create or reuse container
|
// Create or reuse container
|
||||||
const containerId = 'notification-toasts';
|
const containerId = 'notification-toasts';
|
||||||
@@ -860,7 +908,7 @@
|
|||||||
const actions = document.createElement('div');
|
const actions = document.createElement('div');
|
||||||
actions.className = 'nt-actions';
|
actions.className = 'nt-actions';
|
||||||
|
|
||||||
const removeToast = () => {
|
const removeToastLocal = () => {
|
||||||
if (toast.parentNode) toast.remove();
|
if (toast.parentNode) toast.remove();
|
||||||
if (this._shownNotifications.has(nid)) this._shownNotifications.delete(nid);
|
if (this._shownNotifications.has(nid)) this._shownNotifications.delete(nid);
|
||||||
};
|
};
|
||||||
@@ -868,6 +916,33 @@
|
|||||||
const openBtn = document.createElement('button');
|
const openBtn = document.createElement('button');
|
||||||
openBtn.className = notification.cool ? 'btn btn-sm btn-light' : 'btn btn-sm btn-primary';
|
openBtn.className = notification.cool ? 'btn btn-sm btn-light' : 'btn btn-sm btn-primary';
|
||||||
openBtn.textContent = 'Abrir';
|
openBtn.textContent = '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); }
|
||||||
|
removeToastLocal();
|
||||||
|
// navigate to conv if present
|
||||||
|
let data = {};
|
||||||
|
try { data = notification.data ? JSON.parse(notification.data) : {}; } catch(e) {}
|
||||||
|
const userId = data.user_id || notification.user_id;
|
||||||
|
if (userId) {
|
||||||
|
const conv = this.conversations.find(c => c.user_id == userId);
|
||||||
|
if (conv) {
|
||||||
|
this.openConversation(conv.user_id, conv.name, conv.phone_number);
|
||||||
|
} else {
|
||||||
|
this.openConversation(userId, 'Usuario', '');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
openBtn.className = notification.cool ? 'btn btn-sm btn-light' : 'btn btn-sm btn-primary';
|
||||||
|
openBtn.textContent = 'Abrir';
|
||||||
openBtn.onclick = async () => {
|
openBtn.onclick = async () => {
|
||||||
try {
|
try {
|
||||||
await fetch('api/mark_notification_read.php', {method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify({id: notification.id})});
|
await fetch('api/mark_notification_read.php', {method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify({id: notification.id})});
|
||||||
@@ -892,9 +967,15 @@
|
|||||||
dismiss.title = 'Descartar';
|
dismiss.title = 'Descartar';
|
||||||
dismiss.onclick = async () => {
|
dismiss.onclick = async () => {
|
||||||
try {
|
try {
|
||||||
await fetch('api/mark_notification_read.php', {method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify({id: notification.id})});
|
const ok = await this.ackNotification(notification);
|
||||||
} catch(e) { console.warn('mark_notification_read failed', e); }
|
if (ok) {
|
||||||
removeToast();
|
this._dismissedNotifications.add(nid);
|
||||||
|
this._pendingAck.delete(nid);
|
||||||
|
} else {
|
||||||
|
this._pendingAck.set(nid, notification);
|
||||||
|
}
|
||||||
|
} catch(e) { this._pendingAck.set(nid, notification); }
|
||||||
|
removeToastLocal();
|
||||||
};
|
};
|
||||||
|
|
||||||
actions.appendChild(openBtn);
|
actions.appendChild(openBtn);
|
||||||
@@ -908,7 +989,21 @@
|
|||||||
let timeout = notification.duration ? Number(notification.duration) : (notification.cool ? 15000 : 8000);
|
let timeout = notification.duration ? Number(notification.duration) : (notification.cool ? 15000 : 8000);
|
||||||
if (!isFinite(timeout) || timeout <= 0) timeout = (notification.cool ? 15000 : 8000);
|
if (!isFinite(timeout) || timeout <= 0) timeout = (notification.cool ? 15000 : 8000);
|
||||||
timeout = Math.max(timeout, _minToastDuration);
|
timeout = Math.max(timeout, _minToastDuration);
|
||||||
setTimeout(removeToast, timeout);
|
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();
|
||||||
|
})();
|
||||||
|
}, timeout);
|
||||||
}
|
}
|
||||||
|
|
||||||
setupEventListeners() {
|
setupEventListeners() {
|
||||||
|
|||||||
Reference in New Issue
Block a user