()
| 238 | } |
| 239 | |
| 240 | export function connect() { |
| 241 | let ws; |
| 242 | let connectErrorShown = false; |
| 243 | const isCurrentSocket = () => S.ws === ws; |
| 244 | const failConnect = (message) => { |
| 245 | if (connectErrorShown) return; |
| 246 | connectErrorShown = true; |
| 247 | hideHubConnectOverlay(); |
| 248 | renderHubCards(); |
| 249 | showToast(message); |
| 250 | }; |
| 251 | try { |
| 252 | ws = new WebSocket(serverWsUrl); |
| 253 | } catch (e) { |
| 254 | failConnect('Invalid server address'); |
| 255 | return; |
| 256 | } |
| 257 | S.ws = ws; |
| 258 | S.authenticated = false; |
| 259 | S.resumeRequestedFor = ''; |
| 260 | S.replaying = true; |
| 261 | debugLog('ws_connect_start', { |
| 262 | serverWsUrl, |
| 263 | sessionId: S.sessionId || null, |
| 264 | hidden: typeof document !== 'undefined' ? !!document.hidden : null, |
| 265 | online: typeof navigator !== 'undefined' && 'onLine' in navigator ? !!navigator.onLine : null, |
| 266 | }); |
| 267 | |
| 268 | const connectTimeout = setTimeout(() => { |
| 269 | if (!isCurrentSocket()) return; |
| 270 | if (ws.readyState !== WebSocket.OPEN) { |
| 271 | ws.close(); |
| 272 | failConnect('Connection timed out'); |
| 273 | } |
| 274 | }, 8000); |
| 275 | |
| 276 | ws.onopen = () => { |
| 277 | clearTimeout(connectTimeout); |
| 278 | if (!isCurrentSocket()) { |
| 279 | try { ws.close(); } catch {} |
| 280 | return; |
| 281 | } |
| 282 | clearForegroundProbe('ws_open'); |
| 283 | S.lastMessageAt = Date.now(); |
| 284 | S.intentionalDisconnect = false; |
| 285 | S.skipNextCloseHandling = false; |
| 286 | setStatus('starting'); |
| 287 | ws.send(JSON.stringify({ |
| 288 | type: 'hello', |
| 289 | clientInstanceId: CLIENT_INSTANCE_ID, |
| 290 | token: serverToken || '', |
| 291 | page: location.pathname || '/', |
| 292 | userAgent: navigator.userAgent || '', |
| 293 | })); |
| 294 | debugLog('ws_open', { |
| 295 | sessionId: S.sessionId || null, |
| 296 | waiting: S.waiting, |
| 297 | replaying: S.replaying, |
no test coverage detected