-
Fixed polling after voice/text sends. Changes include: Stalled polls abort and retry automatically. Polling now schedules after each completed request. Local sends no longer advance the server cursor and skip interleaved messages. Sends trigger immediate reconciliation. API requests bypass browser caches.
This commit is contained in:
+52
-12
@@ -6,7 +6,8 @@
|
||||
const state = {
|
||||
config: {}, user: null, groups: [], billing: null,
|
||||
view: ['chat', 'groups', 'archive', 'account'].includes(initialView) ? initialView : 'chat',
|
||||
groupId: null, lastId: 0, pollTimer: null, polling: false, pollGeneration: 0, groupsAccess: null,
|
||||
groupId: null, lastId: 0, pollTimer: null, pollController: null,
|
||||
polling: false, pollRequested: false, pollGeneration: 0, groupsAccess: null,
|
||||
groupSeen: {}, groupUnread: {},
|
||||
loginChallenge: initialChallenge,
|
||||
verifyToken: new URLSearchParams(location.search).get('verify') || '',
|
||||
@@ -37,7 +38,11 @@
|
||||
return { method: 'POST', body };
|
||||
}
|
||||
async function api(file, options, query = '') {
|
||||
const response = await fetch(apiUrl(file, query), { credentials: 'include', ...(options || {}) });
|
||||
const response = await fetch(apiUrl(file, query), {
|
||||
credentials: 'include',
|
||||
cache: 'no-store',
|
||||
...(options || {}),
|
||||
});
|
||||
const data = await response.json().catch(() => ({ error: 'The server returned an invalid response' }));
|
||||
if (!response.ok && !data.error) data.error = 'Request failed';
|
||||
return data;
|
||||
@@ -383,35 +388,63 @@
|
||||
const message = input.value.trim();
|
||||
if (!message) return;
|
||||
input.value = '';
|
||||
const result = await api('messages.php', form({ action: 'send', message, group_id: state.groupId || '' }));
|
||||
const result = await api('messages.php', form({
|
||||
action: 'send', message, group_id: state.groupId || '',
|
||||
})).catch(() => ({ error: 'Message send failed. Polling will continue.' }));
|
||||
if (result.groups_enabled === false) {
|
||||
input.value = message;
|
||||
return disableGroups(result.error, result.groups_platform_enabled !== false);
|
||||
}
|
||||
if (result.error) { input.value = message; return toast(result.error); }
|
||||
appendMessage(result.message);
|
||||
state.lastId = Math.max(state.lastId, result.message.id);
|
||||
requestPoll();
|
||||
}
|
||||
function startPoll() {
|
||||
stopPoll();
|
||||
const generation = state.pollGeneration;
|
||||
poll(generation);
|
||||
state.pollTimer = setInterval(() => poll(generation), state.config.chat?.poll_interval_ms || 2000);
|
||||
schedulePoll(generation, 0);
|
||||
}
|
||||
function schedulePoll(generation, delay = state.config.chat?.poll_interval_ms || 2000) {
|
||||
if (generation !== state.pollGeneration || state.view !== 'chat') return;
|
||||
if (state.pollTimer) clearTimeout(state.pollTimer);
|
||||
state.pollTimer = setTimeout(async () => {
|
||||
state.pollTimer = null;
|
||||
await poll(generation);
|
||||
if (generation !== state.pollGeneration || state.view !== 'chat') return;
|
||||
const delay = state.pollRequested ? 0 : state.config.chat?.poll_interval_ms || 2000;
|
||||
state.pollRequested = false;
|
||||
schedulePoll(generation, delay);
|
||||
}, Math.max(0, delay));
|
||||
}
|
||||
function requestPoll() {
|
||||
if (state.view !== 'chat') return;
|
||||
if (state.polling) {
|
||||
state.pollRequested = true;
|
||||
return;
|
||||
}
|
||||
schedulePoll(state.pollGeneration, 0);
|
||||
}
|
||||
function stopPoll() {
|
||||
if (state.pollTimer) clearInterval(state.pollTimer);
|
||||
if (state.pollTimer) clearTimeout(state.pollTimer);
|
||||
state.pollController?.abort();
|
||||
state.pollTimer = null;
|
||||
state.pollController = null;
|
||||
state.polling = false;
|
||||
state.pollRequested = false;
|
||||
state.pollGeneration++;
|
||||
}
|
||||
async function poll(generation = state.pollGeneration) {
|
||||
if (state.polling || state.view !== 'chat' || generation !== state.pollGeneration) return;
|
||||
const requestedGroup = state.groupId;
|
||||
const requestedSince = state.lastId;
|
||||
const controller = new AbortController();
|
||||
const timeoutMs = Math.max(1000, Number(state.config.chat?.poll_timeout_ms) || 12000);
|
||||
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
||||
state.pollController = controller;
|
||||
state.polling = true;
|
||||
try {
|
||||
const query = new URLSearchParams({ action: 'poll', since: requestedSince, group_id: requestedGroup || '' });
|
||||
const result = await api('messages.php', null, query.toString()).catch(() => null);
|
||||
const result = await api('messages.php', { signal: controller.signal }, query.toString()).catch(() => null);
|
||||
if (generation !== state.pollGeneration || Number(state.groupId || 0) !== Number(requestedGroup || 0)) return;
|
||||
if (result && !result.error && Number(result.group_id || 0) === Number(requestedGroup || 0)) {
|
||||
const availabilityChanged = applyGroupsAvailability(
|
||||
@@ -429,7 +462,9 @@
|
||||
if (groupsAvailable()) syncGroups(result.groups, true);
|
||||
if (requestedSince === 0) renderMessages(result.messages || []);
|
||||
else (result.messages || []).forEach(message => appendMessage(message, true));
|
||||
if (result.messages?.length) state.lastId = result.messages[result.messages.length - 1].id;
|
||||
if (result.messages?.length) {
|
||||
state.lastId = Math.max(state.lastId, ...result.messages.map(message => Number(message.id) || 0));
|
||||
}
|
||||
if (requestedGroup !== null) {
|
||||
state.groupUnread[requestedGroup] = false;
|
||||
state.groupSeen[requestedGroup] = Math.max(Number(state.groupSeen[requestedGroup] || 0), Number(state.lastId || 0));
|
||||
@@ -445,6 +480,8 @@
|
||||
if (result?.error && requestedGroup !== null) await recoverGroupAccess(requestedGroup, generation);
|
||||
}
|
||||
} finally {
|
||||
clearTimeout(timeout);
|
||||
if (state.pollController === controller) state.pollController = null;
|
||||
if (generation === state.pollGeneration) state.polling = false;
|
||||
}
|
||||
}
|
||||
@@ -470,7 +507,9 @@
|
||||
const list = $('#messages');
|
||||
list.innerHTML = messages.length ? '' : '<div class="cc-empty-day">NO MESSAGES IN THIS CHANNEL TODAY</div>';
|
||||
messages.forEach(appendMessage);
|
||||
if (messages.length) state.lastId = messages[messages.length - 1].id;
|
||||
if (messages.length) {
|
||||
state.lastId = Math.max(state.lastId, ...messages.map(message => Number(message.id) || 0));
|
||||
}
|
||||
list.scrollTop = list.scrollHeight;
|
||||
}
|
||||
function appendMessage(message, incoming = false) {
|
||||
@@ -608,13 +647,14 @@
|
||||
body.append('group_id', state.groupId || '');
|
||||
body.append('duration', state.voice.duration.toFixed(2));
|
||||
body.append('voice', state.voice.blob, 'voice.webm');
|
||||
const result = await api('messages.php', { method: 'POST', body });
|
||||
const result = await api('messages.php', { method: 'POST', body })
|
||||
.catch(() => ({ error: 'Voice send failed. Polling will continue.' }));
|
||||
state.voice.sending = false;
|
||||
if (result.groups_enabled === false) return disableGroups(result.error, result.groups_platform_enabled !== false);
|
||||
if (result.error) return toast(result.error);
|
||||
appendMessage(result.message);
|
||||
state.lastId = Math.max(state.lastId, result.message.id);
|
||||
discardVoice();
|
||||
requestPoll();
|
||||
}
|
||||
|
||||
function groupsView() {
|
||||
|
||||
Reference in New Issue
Block a user