Fix WebSocket reconnect: refused upgrades are 1006 not 1008, back off, prompt once

This commit is contained in:
Chris Amow 2026-08-10 04:53:41 +00:00
parent 1f1544bab1
commit 821f0a0a8f

View file

@ -5,9 +5,17 @@ const { createApp, ref, computed, watch, onMounted, onUnmounted } = Vue;
const TOKEN_KEY = 'chart-token'; const TOKEN_KEY = 'chart-token';
let authToken = localStorage.getItem(TOKEN_KEY) || ''; let authToken = localStorage.getItem(TOKEN_KEY) || '';
// Reconnects and the status poll both hit 401s, so without this a visitor
// who cancels gets asked again every couple of seconds.
let promptDeclined = false;
function promptForToken() { function promptForToken() {
if (promptDeclined) return false;
const entered = window.prompt('Access token for this chart', ''); const entered = window.prompt('Access token for this chart', '');
if (entered === null) return false; if (entered === null) {
promptDeclined = true;
return false;
}
authToken = entered.trim(); authToken = entered.trim();
localStorage.setItem(TOKEN_KEY, authToken); localStorage.setItem(TOKEN_KEY, authToken);
return true; return true;
@ -55,6 +63,7 @@ createApp({
const now = ref(Date.now()); const now = ref(Date.now());
let chartApi = null; let chartApi = null;
let socket = null; let socket = null;
let retryDelay = 2000;
let timer = null; let timer = null;
const barAge = computed(() => { const barAge = computed(() => {
@ -76,6 +85,7 @@ createApp({
const query = authToken ? `?token=${encodeURIComponent(authToken)}` : ''; const query = authToken ? `?token=${encodeURIComponent(authToken)}` : '';
socket = new WebSocket(`${protocol}://${location.host}/ws${query}`); socket = new WebSocket(`${protocol}://${location.host}/ws${query}`);
socket.onopen = () => { socket.onopen = () => {
retryDelay = 2000;
socket.send(JSON.stringify({ type: 'subscribe', tf: timeframe.value })); socket.send(JSON.stringify({ type: 'subscribe', tf: timeframe.value }));
sendPrefs(); sendPrefs();
}; };
@ -102,11 +112,17 @@ createApp({
playAlert(); playAlert();
} }
}; };
socket.onclose = event => { socket.onclose = async () => {
status.value.stream = 'disconnected'; status.value.stream = 'disconnected';
// 1008 is the server rejecting our token; ask once, then reconnect. // A refused upgrade reaches the browser as 1006, not 1008: the server
if (event.code === 1008 && !promptForToken()) return; // rejects the handshake with HTTP 403 before any close frame exists.
setTimeout(connect, 2000); // So the close code cannot distinguish "bad token" from "server
// restarting". Re-check over HTTP instead — apiFetch prompts for a
// token when that is what is actually wrong — and back off so a
// visitor without one is not reconnecting twice a second forever.
await refreshStatus();
retryDelay = Math.min(retryDelay * 2, 30000);
setTimeout(connect, retryDelay);
}; };
} }