From a2f8c7bced2be981630aec0768ba12b2de9d37e1 Mon Sep 17 00:00:00 2001 From: Jacob Cody Wimer Date: Thu, 29 Jan 2026 17:28:14 -0500 Subject: [PATCH] Stats page should auto push stats when it reconnects to the websocket. Spectate page should auto pull when it reconnects to the websocket. --- .../controllers/match_data_controller.js | 46 +++++++++++++++++-- .../controllers/match_spectate_controller.js | 7 ++- app/channels/match_channel.rb | 25 ++++++++++ 3 files changed, 72 insertions(+), 6 deletions(-) diff --git a/app/assets/javascripts/controllers/match_data_controller.js b/app/assets/javascripts/controllers/match_data_controller.js index 62232ba..7c43b49 100644 --- a/app/assets/javascripts/controllers/match_data_controller.js +++ b/app/assets/javascripts/controllers/match_data_controller.js @@ -13,6 +13,8 @@ export default class extends Controller { connect() { console.log("Match data controller connected") + this.isConnected = false + this.pendingLocalSync = { w1: false, w2: false } this.w1 = { name: "w1", @@ -69,6 +71,7 @@ export default class extends Controller { wrestler.updated_at = new Date().toISOString() this.updateHtmlValues() this.saveToLocalStorage(wrestler) + if (!this.isConnected) this.pendingLocalSync[wrestler.name] = true // Send the update via Action Cable if subscribed if (this.matchSubscription) { @@ -109,6 +112,7 @@ export default class extends Controller { // Update the internal JS object wrestler.stats = newValue wrestler.updated_at = new Date().toISOString() + if (!this.isConnected) this.pendingLocalSync[wrestler.name] = true // Save to localStorage this.saveToLocalStorage(wrestler) @@ -334,15 +338,18 @@ export default class extends Controller { { connected: () => { console.log(`[Stats AC] Connected to MatchStatsChannel for match ID: ${matchId}`) + this.isConnected = true if (this.statusIndicatorTarget) { this.statusIndicatorTarget.innerText = "Connected: Stats will update in real-time." this.statusIndicatorTarget.classList.remove('alert-info', 'alert-warning', 'alert-danger') this.statusIndicatorTarget.classList.add('alert-success') } + this.sendCurrentStatsOnReconnect() }, disconnected: () => { console.log(`[Stats AC] Disconnected from MatchStatsChannel`) + this.isConnected = false if (this.statusIndicatorTarget) { this.statusIndicatorTarget.innerText = "Disconnected: Stats updates paused." this.statusIndicatorTarget.classList.remove('alert-info', 'alert-success', 'alert-danger') @@ -356,15 +363,25 @@ export default class extends Controller { // Update w1 stats if (data.w1_stat !== undefined && this.w1StatTarget) { console.log(`[Stats AC] Updating w1_stat: ${data.w1_stat.substring(0, 30)}...`) - this.w1.stats = data.w1_stat - this.w1StatTarget.value = data.w1_stat + if (!this.pendingLocalSync.w1 || data.w1_stat === this.w1.stats) { + this.w1.stats = data.w1_stat + this.w1StatTarget.value = data.w1_stat + this.pendingLocalSync.w1 = false + } else { + console.log('[Stats AC] Skipping w1_stat overwrite due to pending local changes.') + } } // Update w2 stats if (data.w2_stat !== undefined && this.w2StatTarget) { console.log(`[Stats AC] Updating w2_stat: ${data.w2_stat.substring(0, 30)}...`) - this.w2.stats = data.w2_stat - this.w2StatTarget.value = data.w2_stat + if (!this.pendingLocalSync.w2 || data.w2_stat === this.w2.stats) { + this.w2.stats = data.w2_stat + this.w2StatTarget.value = data.w2_stat + this.pendingLocalSync.w2 = false + } else { + console.log('[Stats AC] Skipping w2_stat overwrite due to pending local changes.') + } } }, @@ -381,4 +398,23 @@ export default class extends Controller { } ) } -} \ No newline at end of file + + sendCurrentStatsOnReconnect() { + if (!this.matchSubscription) return + const payload = {} + if (typeof this.w1?.stats === 'string' && this.w1.stats.length > 0) { + payload.new_w1_stat = this.w1.stats + this.pendingLocalSync.w1 = true + } + if (typeof this.w2?.stats === 'string' && this.w2.stats.length > 0) { + payload.new_w2_stat = this.w2.stats + this.pendingLocalSync.w2 = true + } + if (Object.keys(payload).length > 0) { + console.log('[ActionCable] Reconnect sync: sending current stats payload:', payload) + this.matchSubscription.perform('send_stat', payload) + } else { + console.log('[ActionCable] Reconnect sync: no local stats to send.') + } + } +} diff --git a/app/assets/javascripts/controllers/match_spectate_controller.js b/app/assets/javascripts/controllers/match_spectate_controller.js index 32f3671..229d4df 100644 --- a/app/assets/javascripts/controllers/match_spectate_controller.js +++ b/app/assets/javascripts/controllers/match_spectate_controller.js @@ -76,6 +76,11 @@ export default class extends Controller { this.statusIndicatorTarget.classList.remove('alert-danger', 'alert-secondary', 'text-danger', 'text-dark') this.statusIndicatorTarget.classList.add('alert-success') } + try { + this.matchSubscription.perform('request_sync') + } catch (e) { + console.error('[Spectator AC] request_sync perform failed:', e) + } }, disconnected: () => { console.log(`[Spectator AC Callback] Disconnected: ${matchId}`) @@ -131,4 +136,4 @@ export default class extends Controller { this.finishedTarget.textContent = data.finished ? 'Yes' : 'No' } } -} \ No newline at end of file +} diff --git a/app/channels/match_channel.rb b/app/channels/match_channel.rb index 0ae50b3..db5c596 100644 --- a/app/channels/match_channel.rb +++ b/app/channels/match_channel.rb @@ -60,4 +60,29 @@ class MatchChannel < ApplicationCable::Channel Rails.logger.info "[MatchChannel] No new stat data provided in send_stat for match #{@match.id}, not updating DB or broadcasting." end end + + # Called when client wants the latest stats immediately after reconnect + def request_sync + unless @match + Rails.logger.error "[MatchChannel] Error: request_sync called but @match is nil. Client params on sub: #{params[:match_id]}" + return + end + + payload = { + w1_stat: @match.w1_stat, + w2_stat: @match.w2_stat, + score: @match.score, + win_type: @match.win_type, + winner_name: @match.winner&.name, + winner_id: @match.winner_id, + finished: @match.finished + }.compact + + if payload.present? + Rails.logger.info "[MatchChannel] request_sync transmit for match #{@match.id} with payload: #{payload.inspect}" + transmit(payload) + else + Rails.logger.info "[MatchChannel] request_sync payload empty for match #{@match.id}, not transmitting." + end + end end