update: cache-busted version reload with one-time auto-connect; announce list counts; add apache no-cache html headers
This commit is contained in:
@@ -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";
|
||||||
|
|||||||
@@ -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.
|
||||||
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user