1// Custom Blazor Reconnection Handler 2// Provides automatic reconnection with custom UI matching the application theme 3 4(function () { 5 const maxRetries = 8; 6 const retryIntervalMilliseconds = 3000; 7 let currentReconnectionAttempt = 0; 8 let reconnectDisplay = null; 9 let reconnectModal = null; 10 let statusText = null; 11 let progressBar = null; 12 let reconnectButton = null; 13 let closeButton = null; 14 let reconnectionStartTime = null; 15 16 function createReconnectUI() { 17 if (reconnectDisplay) return; 18 19 reconnectDisplay = document.createElement('div'); 20 reconnectDisplay.id = 'blazor-reconnect-ui'; 21 reconnectDisplay.innerHTML = ` 22 <div class="blazor-reconnect-overlay"></div> 23 <div class="blazor-reconnect-modal"> 24 <div class="blazor-reconnect-header"> 25 <i class="fas fa-wifi"></i> 26 <h3>Connection Status</h3> 27 </div> 28 <div class="blazor-reconnect-body"> 29 <div class="blazor-reconnect-icon"> 30 <i class="fas fa-circle-notch fa-spin"></i> 31 </div> 32 <div class="blazor-reconnect-status">Attempting to reconnect...</div> 33 <div class="blazor-reconnect-progress"> 34 <div class="blazor-reconnect-progress-bar"></div> 35 </div> 36 <div class="blazor-reconnect-attempt">Attempt <span class="attempt-number">1</span> of ${maxRetries}</div> 37 </div> 38 <div class="blazor-reconnect-actions"> 39 <button class="blazor-reconnect-btn blazor-reconnect-btn-primary" id="blazor-reconnect-manual"> 40 <i class="fas fa-redo"></i> 41 Reconnect Now 42 </button> 43 <button class="blazor-reconnect-btn blazor-reconnect-btn-secondary" id="blazor-reconnect-reload"> 44 <i class="fas fa-sync-alt"></i> 45 Reload Page 46 </button> 47 </div> 48 </div> 49 `; 50 51 document.body.appendChild(reconnectDisplay); 52 53 reconnectModal = reconnectDisplay.querySelector('.blazor-reconnect-modal'); 54 statusText = reconnectDisplay.querySelector('.blazor-reconnect-status'); 55 progressBar = reconnectDisplay.querySelector('.blazor-reconnect-progress-bar'); 56 reconnectButton = document.getElementById('blazor-reconnect-manual'); 57 closeButton = document.getElementById('blazor-reconnect-reload'); 58 59 reconnectButton.addEventListener('click', () => { 60 reportReconnectionMetric('manual', true, 0); 61 window.location.reload(); 62 }); 63 64 closeButton.addEventListener('click', () => { 65 window.location.reload(); 66 }); 67 } 68 69 function showReconnectUI(show) { 70 if (!reconnectDisplay) { 71 createReconnectUI(); 72 } 73 74 if (show) { 75 reconnectDisplay.style.display = 'flex'; 76 setTimeout(() => { 77 reconnectDisplay.classList.add('show'); 78 }, 10); 79 } else { 80 reconnectDisplay.classList.remove('show'); 81 setTimeout(() => { 82 reconnectDisplay.style.display = 'none'; 83 }, 300); 84 } 85 } 86 87 function updateReconnectUI(attempt, status) { 88 if (!reconnectDisplay) return; 89 90 const attemptNumber = reconnectDisplay.querySelector('.attempt-number'); 91 const icon = reconnectDisplay.querySelector('.blazor-reconnect-icon i'); 92 93 if (attemptNumber) { 94 attemptNumber.textContent = attempt; 95 } 96 97 if (progressBar) { 98 const progress = (attempt / maxRetries) * 100; 99 progressBar.style.width = progress + '%'; 100 } 101 102 if (status === 'failed') { 103 statusText.textContent = 'Connection failed. Please try again.'; 104 icon.className = 'fas fa-exclamation-triangle'; 105 icon.style.color = '#e74c3c';
106 reconnectModal.classList.add('failed'); 107 } else if (status === 'reconnecting') { 108 statusText.textContent = `Attempting to reconnect... (${attempt}/${maxRetries})`; 109 icon.className = 'fas fa-circle-notch fa-spin'; 110 icon.style.color = '#3498db'; 111 reconnectModal.classList.remove('failed'); 112 } else if (status === 'success') { 113 statusText.textContent = 'Reconnected successfully!'; 114 icon.className = 'fas fa-check-circle'; 115 icon.style.color = '#27ae60'; 116 reconnectModal.classList.remove('failed'); 117 } 118 } 119 120 function reportReconnectionMetric(eventType, success, duration) { 121 // Only report if we're connected (to avoid errors during reconnection) 122 if (success) { 123 fetch('/api/metrics/reconnection', { 124 method: 'POST', 125 headers: { 126 'Content-Type': 'application/json' 127 }, 128 body: JSON.stringify({ 129 eventType: eventType, 130 success: success, 131 duration: duration 132 }) 133 }).catch(err => { 134 console.error('Failed to report reconnection metric:', err); 135 }); 136 } 137 } 138 139 // Custom reconnection handler 140 class CustomReconnectionHandler { 141 constructor(maxRetries, retryInterval) { 142 this._maxRetries = maxRetries; 143 this._retryInterval = retryInterval; 144 // Create an array of retry delays - all the same interval 145 this._retryDelays = []; 146 for (let i = 0; i < maxRetries; i++) { 147 this._retryDelays.push(retryInterval); 148 } 149 } 150 151 onConnectionDown(options, error) { 152 console.log('Blazor: Connection down', error); 153 currentReconnectionAttempt = 0; 154 reconnectionStartTime = Date.now(); 155 showReconnectUI(true); 156 updateReconnectUI(1, 'reconnecting'); 157 } 158 159 onConnectionUp() { 160 console.log('Blazor: Connection up'); 161 const duration = reconnectionStartTime ? Date.now() - reconnectionStartTime : 0; 162 updateReconnectUI(currentReconnectionAttempt, 'success'); 163 reportReconnectionMetric('automatic', true, duration); 164 setTimeout(() => { 165 showReconnectUI(false); 166 currentReconnectionAttempt = 0; 167 reconnectionStartTime = null; 168 }, 2000); 169 } 170 171 onReconnecting(attempt) { 172 console.log('Blazor: onReconnecting called with attempt', attempt, 'of', this._maxRetries); 173 currentReconnectionAttempt = attempt; 174 updateReconnectUI(attempt, 'reconnecting'); 175 176 // attempt is 1-based, array is 0-based 177 const delayIndex = attempt - 1; 178 179 if (delayIndex >= this._retryDelays.length) { 180 console.log('Blazor: Max retries exceeded, stopping'); 181 return null; // Stop retrying 182 } 183 184 const delay = this._retryDelays[delayIndex]; 185 console.log('Blazor: Will retry in', delay, 'ms (attempt', attempt, 'of', this._maxRetries, ')'); 186 return delay; 187 } 188 189 onReconnectFailed() { 190 console.log('Blazor: Reconnection failed after all attempts'); 191 updateReconnectUI(currentReconnectionAttempt, 'failed'); 192 reconnectionStartTime = null; 193 } 194 } 195 196 // .NET 8+ uses reconnectionHandler with onConnectionDown/onConnectionUp callbacks 197 console.log('Blazor reconnect script loaded'); 198 199 let currentReconnectionProcess = null; 200 201 const startReconnectionProcess = () => { 202 if (currentReconnectionProcess) { 203 return currentReconnectionProcess; 204 } 205 206 console.log('Starting reconnection process...'); 207 reconnectionStartTime = Date.now(); 208 currentReconnectionAttempt = 0; 209 210 createReconnectUI(); 211 showReconnectUI(); 212 213 let isCanceled = false; 214 215 const reconnectionPromise = (async () => { 216 for (let i = 0; i < maxRetries; i++) { 217 currentReconnectionAttempt = i + 1; 218 updateReconnectUI(); 219 220 console.log(`Reconnection attempt ${currentReconnectionAttempt} of ${maxRetries}`); 221 222 await new Promise(resolve => setTimeout(resolve, retryIntervalMilliseconds)); 223 224 if (isCanceled) { 225 console.log('Reconnection canceled'); 226 return; 227 } 228 229 try { 230 const result = await Blazor.reconnect(); 231 if (!result) { 232 // Server reached but connection rejected - reload page 233 console.log('Connection rejected by server, reloading page...'); 234 location.reload(); 235 return; 236 } 237 238 // Successfully reconnected 239 console.log('Successfully reconnected!'); 240 return; 241 } catch (error) { 242 console.error(`Reconnection attempt ${currentReconnectionAttempt} failed:`, error); 243 } 244 } 245 246 // Max retries reached - show failed state 247 console.error('Max reconnection attempts reached'); 248 showFailedState(); 249 })(); 250 251 currentReconnectionProcess = { 252 promise: reconnectionPromise, 253 cancel: () => { 254 isCanceled = true; 255 hideReconnectUI(); 256 currentReconnectionProcess = null; 257 } 258 }; 259 260 return currentReconnectionProcess; 261 }; 262 263 // Start Blazor with .NET 8+ reconnection handler 264 console.log('Starting Blazor with .NET 8+ reconnection handler...'); 265 Blazor.start({ 266 circuit: { 267 reconnectionHandler: { 268 onConnectionDown: (options, error) => { 269 console.log('Connection down:', error); 270 startReconnectionProcess(); 271 }, 272 onConnectionUp: () => { 273 console.log('Connection up!'); 274 if (currentReconnectionProcess) { 275 currentReconnectionProcess.cancel(); 276 currentReconnectionProcess = null; 277 } 278 } 279 } 280 } 281 }).then(() => { 282 console.log('Blazor started successfully'); 283 }).catch(err => { 284 console.error('Error starting Blazor:', err); 285 });
286 287 // Test function to simulate a disconnection 288 window.testReconnection = function() { 289 console.log('Testing reconnection by simulating disconnect...'); 290 if (window.Blazor && window.Blazor._internal && window.Blazor._internal.forceCloseConnection) { 291 window.Blazor._internal.forceCloseConnection(); 292 } else { 293 console.log('Cannot force disconnect, reloading page instead...'); 294 location.reload(); 295 } 296 }; 297})(); 298
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.