update: cache-busted version reload with one-time auto-connect; announce list counts; add apache no-cache html headers

This commit is contained in:
Jage9
2026-02-22 18:57:40 -05:00
parent deb235fb9c
commit eb18155132
4 changed files with 51 additions and 5 deletions

View File

@@ -1,5 +1,5 @@
// Maintainer-controlled web client version. // Maintainer-controlled web client version.
// Format: YYYY.MM.DD Rn (example: 2026.02.20 R2) // Format: YYYY.MM.DD Rn (example: 2026.02.20 R2)
window.CHGRID_WEB_VERSION = "2026.02.22 R167"; window.CHGRID_WEB_VERSION = "2026.02.22 R168";
// Optional display timezone for timestamps. Falls back to America/Detroit if unset/invalid. // Optional display timezone for timestamps. Falls back to America/Detroit if unset/invalid.
window.CHGRID_TIME_ZONE = "America/Detroit"; window.CHGRID_TIME_ZONE = "America/Detroit";

View File

@@ -80,6 +80,7 @@ const MIC_INPUT_GAIN_STEP = 0.05;
const HEARTBEAT_INTERVAL_MS = 10_000; const HEARTBEAT_INTERVAL_MS = 10_000;
const RECONNECT_DELAY_MS = 5_000; const RECONNECT_DELAY_MS = 5_000;
const RECONNECT_MAX_ATTEMPTS = 3; const RECONNECT_MAX_ATTEMPTS = 3;
const AUTO_RECONNECT_AFTER_RELOAD_KEY = 'chatGridAutoReconnectAfterReload';
declare global { declare global {
interface Window { interface Window {
@@ -526,6 +527,19 @@ function handleSignalingStatus(message: string): void {
pushChatMessage(message); pushChatMessage(message);
} }
/** Performs cache-busted navigation and marks session for one-time auto-connect. */
function reloadClientForVersion(version: string): void {
try {
sessionStorage.setItem(AUTO_RECONNECT_AFTER_RELOAD_KEY, '1');
} catch {
// Ignore sessionStorage failures.
}
const nextUrl = new URL(window.location.href);
nextUrl.searchParams.set('v', version || 'unknown');
nextUrl.searchParams.set('t', String(Date.now()));
window.location.replace(nextUrl.toString());
}
/** Appends a chat/system line to the bounded status history buffer. */ /** Appends a chat/system line to the bounded status history buffer. */
function pushChatMessage(message: string): void { function pushChatMessage(message: string): void {
messageBuffer.push(message); messageBuffer.push(message);
@@ -1140,6 +1154,10 @@ async function reconnectWithRetry(reason: 'heartbeat' | 'socketClose'): Promise<
for (let attempt = 1; attempt <= RECONNECT_MAX_ATTEMPTS; attempt += 1) { for (let attempt = 1; attempt <= RECONNECT_MAX_ATTEMPTS; attempt += 1) {
await new Promise((resolve) => window.setTimeout(resolve, RECONNECT_DELAY_MS)); await new Promise((resolve) => window.setTimeout(resolve, RECONNECT_DELAY_MS));
await connect(); await connect();
const waitStartedAt = Date.now();
while (!state.running && Date.now() - waitStartedAt < 4_000) {
await new Promise((resolve) => window.setTimeout(resolve, 100));
}
if (state.running) { if (state.running) {
reconnectInFlight = false; reconnectInFlight = false;
return; return;
@@ -1159,7 +1177,12 @@ function getConnectionFlowDeps(): ConnectFlowDeps {
state, state,
dom, dom,
sanitizeName, sanitizeName,
updateStatus: (message) => pushChatMessage(message), updateStatus: (message) => {
if (reconnectInFlight && message === 'Disconnected.') {
return;
}
pushChatMessage(message);
},
updateConnectAvailability, updateConnectAvailability,
settingsSaveNickname: (value) => settings.saveNickname(value), settingsSaveNickname: (value) => settings.saveNickname(value),
mediaIsConnecting: () => mediaSession.isConnecting(), mediaIsConnecting: () => mediaSession.isConnecting(),
@@ -1274,7 +1297,7 @@ async function onSignalingMessage(message: IncomingMessage): Promise<void> {
reloadScheduledForVersionMismatch = true; reloadScheduledForVersionMismatch = true;
pushChatMessage(`Server version ${incomingVersion} detected. Reloading client...`); pushChatMessage(`Server version ${incomingVersion} detected. Reloading client...`);
window.setTimeout(() => { window.setTimeout(() => {
window.location.reload(); reloadClientForVersion(incomingVersion);
}, 50); }, 50);
return; return;
} }
@@ -1454,8 +1477,10 @@ function handleNormalModeInput(code: string, shiftKey: boolean): void {
state.mode = 'listItems'; state.mode = 'listItems';
const first = state.items.get(state.sortedItemIds[0]); const first = state.items.get(state.sortedItemIds[0]);
if (first) { if (first) {
const itemCount = state.sortedItemIds.length;
const itemLabelText = itemCount === 1 ? 'item' : 'items';
updateStatus( updateStatus(
`List: ${itemLabel(first)}, ${distanceDirectionPhrase(state.player.x, state.player.y, first.x, first.y)}, ${first.x}, ${first.y}`, `${itemCount} ${itemLabelText}. List: ${itemLabel(first)}, ${distanceDirectionPhrase(state.player.x, state.player.y, first.x, first.y)}, ${first.x}, ${first.y}`,
); );
} }
audio.sfxUiBlip(); audio.sfxUiBlip();
@@ -1566,8 +1591,10 @@ function handleNormalModeInput(code: string, shiftKey: boolean): void {
state.mode = 'listUsers'; state.mode = 'listUsers';
const first = state.peers.get(state.sortedPeerIds[0]); const first = state.peers.get(state.sortedPeerIds[0]);
if (first) { if (first) {
const userCount = state.sortedPeerIds.length;
const userLabelText = userCount === 1 ? 'user' : 'users';
updateStatus( updateStatus(
`List: ${first.nickname}, ${distanceDirectionPhrase(state.player.x, state.player.y, first.x, first.y)}, ${first.x}, ${first.y}`, `${userCount} ${userLabelText}. List: ${first.nickname}, ${distanceDirectionPhrase(state.player.x, state.player.y, first.x, first.y)}, ${first.x}, ${first.y}`,
); );
} }
audio.sfxUiBlip(); audio.sfxUiBlip();
@@ -2171,3 +2198,13 @@ if (storedNickname) {
updateConnectAvailability(); updateConnectAvailability();
updateDeviceSummary(); updateDeviceSummary();
updateStatus('Welcome to the Chat Grid. Press the Settings button to configure your audio, then Connect to join the grid.'); updateStatus('Welcome to the Chat Grid. Press the Settings button to configure your audio, then Connect to join the grid.');
try {
if (sessionStorage.getItem(AUTO_RECONNECT_AFTER_RELOAD_KEY) === '1') {
sessionStorage.removeItem(AUTO_RECONNECT_AFTER_RELOAD_KEY);
if (storedNickname) {
void connect();
}
}
} catch {
// Ignore sessionStorage failures.
}

View File

@@ -103,6 +103,7 @@ cd /home/bestmidi/chgrid
Notes: Notes:
- Replace `yourdomain.com` with your real domain. - Replace `yourdomain.com` with your real domain.
- Script copies `deploy/apache/chgrid-vhost-snippet.conf`, runs `rebuildhttpdconf`, then restarts Apache via WHM restart command. - Script copies `deploy/apache/chgrid-vhost-snippet.conf`, runs `rebuildhttpdconf`, then restarts Apache via WHM restart command.
- Snippet now includes no-cache headers for `/chgrid/` and `/chgrid/index.html` so client updates are not stuck on stale HTML.
## 7) Optional HTTPS relay for HTTP radio streams ## 7) Optional HTTPS relay for HTTP radio streams

View File

@@ -1,7 +1,15 @@
# Add inside your SSL VirtualHost include for bestmidi.com. # Add inside your SSL VirtualHost include for bestmidi.com.
# Keep your existing main DocumentRoot unchanged when hosting Chat Grid under /chgrid. # Keep your existing main DocumentRoot unchanged when hosting Chat Grid under /chgrid.
# Required modules: proxy, proxy_http, proxy_wstunnel # Required modules: proxy, proxy_http, proxy_wstunnel
# Optional but recommended modules for client update freshness: headers, setenvif
# Proxy websocket signaling endpoint to local Python service. # Proxy websocket signaling endpoint to local Python service.
ProxyPass /ws ws://127.0.0.1:8765 ProxyPass /ws ws://127.0.0.1:8765
ProxyPassReverse /ws ws://127.0.0.1:8765 ProxyPassReverse /ws ws://127.0.0.1:8765
# Ensure HTML entrypoint is never cached so version updates are picked up quickly.
<LocationMatch "^/chgrid/?$|^/chgrid/index\\.html$">
Header set Cache-Control "no-store, no-cache, must-revalidate"
Header set Pragma "no-cache"
Header set Expires "0"
</LocationMatch>