From fdbe84e87797ec90bc469f8d3ec5358261e9b102 Mon Sep 17 00:00:00 2001 From: Ivan Date: Sat, 2 May 2026 22:49:21 -0500 Subject: [PATCH] fix(App.vue): implement polling guard to prevent overlapping updates and ensure proper state management --- meshchatx/src/frontend/components/App.vue | 63 ++++++++++++++--------- tests/frontend/AppPropagationSync.test.js | 1 + 2 files changed, 39 insertions(+), 25 deletions(-) diff --git a/meshchatx/src/frontend/components/App.vue b/meshchatx/src/frontend/components/App.vue index be5c9c9..e0a07ca 100644 --- a/meshchatx/src/frontend/components/App.vue +++ b/meshchatx/src/frontend/components/App.vue @@ -811,6 +811,8 @@ export default { clearInterval(this._propagationSyncPollTimer); this._propagationSyncPollTimer = null; } + // Clear polling guard flag on unmount + this._isPropagationSyncPolling = false; this.stopShell(); this.clearWsShellUiTimers(); if (this.endedTimeout) clearTimeout(this.endedTimeout); @@ -1498,32 +1500,41 @@ export default { await this.updatePropagationNodeStatus(); + // Guard to prevent overlapping poll calls + this._isPropagationSyncPolling = false; + const poll = async () => { - await this.updatePropagationNodeStatus(); - if (this.isSyncingPropagationNode) { - ToastUtils.loading(this.propagationSyncLiveToastMessage(), 0, propagationSyncToastKey); - return; - } - if (this._propagationSyncPollTimer != null) { - clearInterval(this._propagationSyncPollTimer); - this._propagationSyncPollTimer = null; - } - ToastUtils.dismiss(propagationSyncToastKey); - const status = this.propagationNodeStatus?.state; - const messagesReceived = this.propagationNodeStatus?.messages_received ?? 0; - const messagesStored = this.propagationNodeStatus?.messages_stored ?? 0; - const deliveryConfirmations = this.propagationNodeStatus?.delivery_confirmations ?? 0; - const messagesHidden = this.propagationNodeStatus?.messages_hidden ?? 0; - if (status === "complete" || status === "idle") { - const base = this.$t("app.sync_complete", { count: messagesReceived }); - const details = `${messagesStored} stored, ${deliveryConfirmations} confirmations, ${messagesHidden} hidden`; - ToastUtils.success(`${base} (${details})`); - } else { - ToastUtils.error( - this.$t("app.sync_error", { - status: this.propagationSyncStatusLabel(status), - }) - ); + if (this._isPropagationSyncPolling) return; + this._isPropagationSyncPolling = true; + try { + await this.updatePropagationNodeStatus(); + if (this.isSyncingPropagationNode) { + ToastUtils.loading(this.propagationSyncLiveToastMessage(), 0, propagationSyncToastKey); + return; + } + if (this._propagationSyncPollTimer != null) { + clearInterval(this._propagationSyncPollTimer); + this._propagationSyncPollTimer = null; + } + ToastUtils.dismiss(propagationSyncToastKey); + const status = this.propagationNodeStatus?.state; + const messagesReceived = this.propagationNodeStatus?.messages_received ?? 0; + const messagesStored = this.propagationNodeStatus?.messages_stored ?? 0; + const deliveryConfirmations = this.propagationNodeStatus?.delivery_confirmations ?? 0; + const messagesHidden = this.propagationNodeStatus?.messages_hidden ?? 0; + if (status === "complete" || status === "idle") { + const base = this.$t("app.sync_complete", { count: messagesReceived }); + const details = `${messagesStored} stored, ${deliveryConfirmations} confirmations, ${messagesHidden} hidden`; + ToastUtils.success(`${base} (${details})`); + } else { + ToastUtils.error( + this.$t("app.sync_error", { + status: this.propagationSyncStatusLabel(status), + }) + ); + } + } finally { + this._isPropagationSyncPolling = false; } }; @@ -1560,6 +1571,8 @@ export default { clearInterval(this._propagationSyncPollTimer); this._propagationSyncPollTimer = null; } + // Clear the polling guard flag + this._isPropagationSyncPolling = false; ToastUtils.dismiss(propagationSyncToastKey); await this.updatePropagationNodeStatus(); }, diff --git a/tests/frontend/AppPropagationSync.test.js b/tests/frontend/AppPropagationSync.test.js index 4e9b993..2030b3a 100644 --- a/tests/frontend/AppPropagationSync.test.js +++ b/tests/frontend/AppPropagationSync.test.js @@ -27,6 +27,7 @@ function makeSyncContext(axiosMock, tOverrides = {}) { }, propagationNodeStatus: null, _propagationSyncPollTimer: null, + _isPropagationSyncPolling: false, propagationSyncLiveToastMessage: App.methods.propagationSyncLiveToastMessage, propagationSyncStatusLabel: App.methods.propagationSyncStatusLabel, get isSyncingPropagationNode() {