diff --git a/src/client/Transport.ts b/src/client/Transport.ts index 58307e113..c107d0834 100644 --- a/src/client/Transport.ts +++ b/src/client/Transport.ts @@ -379,8 +379,32 @@ export class Transport { `WebSocket closed. Code: ${event.code}, Reason: ${event.reason}`, ); if (event.code === 1002) { - // TODO: make this a modal - alert(`connection refused: ${event.reason}`); + // Connection refused - typically auth/turnstile issues + if (event.reason.includes("Turnstile")) { + console.error( + "Turnstile verification failed. This can happen if you joined too quickly. Please try again.", + ); + window.dispatchEvent( + new CustomEvent("show-message", { + detail: { + message: "Security verification expired. Please try again.", + color: "red", + duration: 5000, + }, + }), + ); + } else { + console.error(`Connection refused: ${event.reason}`); + window.dispatchEvent( + new CustomEvent("show-message", { + detail: { + message: `Connection refused: ${event.reason}`, + color: "red", + duration: 5000, + }, + }), + ); + } } else if (event.code !== 1000) { console.log(`received error code ${event.code}, reconnecting`); this.reconnect(); diff --git a/src/client/TurnstileManager.ts b/src/client/TurnstileManager.ts index 2f6713c12..52f8e9ad5 100644 --- a/src/client/TurnstileManager.ts +++ b/src/client/TurnstileManager.ts @@ -92,18 +92,26 @@ class TurnstileManager { } private async acquireToken(): Promise { - // If we have a valid cached token, return it + // If we have a valid cached token, consume it immediately + // (clear from cache so it can never be reused - tokens are single-use) if (this.currentToken && this.isTokenValid(this.currentToken)) { + const token = this.currentToken; + this.currentToken = null; // Immediately clear to prevent reuse console.log( - `TurnstileManager using cached valid token: ${this.currentToken.token.substring(0, 10)}...`, + `TurnstileManager consuming cached token: ${token.token.substring(0, 10)}...`, ); - return this.currentToken; + return token; } // If a fetch is already in progress, wait for it if (this.state === "fetching" && this.pendingPromise) { console.log("TurnstileManager waiting for pending token request"); - return this.pendingPromise; + const token = await this.pendingPromise; + // Clear if this is the current token (another waiter might have already cleared it) + if (token && this.currentToken === token) { + this.currentToken = null; + } + return token; } // Need to fetch a new token