Move nickname/position state server-side per account
This commit is contained in:
@@ -19,7 +19,6 @@
|
||||
<label for="authPassword">Password</label>
|
||||
<input id="authPassword" type="password" maxlength="64" autocomplete="current-password" />
|
||||
</div>
|
||||
<button id="showRegisterButton" type="button">Register</button>
|
||||
</section>
|
||||
<section id="registerView" class="auth-panel hidden">
|
||||
<h2>Register</h2>
|
||||
@@ -43,11 +42,12 @@
|
||||
<button id="showLoginButton" type="button">Login</button>
|
||||
</section>
|
||||
<section id="authSessionView" class="auth-panel hidden">
|
||||
<h2>Session</h2>
|
||||
<h2>Logged In</h2>
|
||||
<p id="authSessionText" class="auth-hint"></p>
|
||||
</section>
|
||||
<div class="controls" id="button-container">
|
||||
<button id="connectButton">Connect</button>
|
||||
<button id="showRegisterButton" type="button">Register</button>
|
||||
<button id="logoutButton" class="hidden">Log out</button>
|
||||
<button id="settingsButton">Audio setup</button>
|
||||
<button id="disconnectButton" class="hidden">Disconnect</button>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// Maintainer-controlled web client version.
|
||||
// Format: YYYY.MM.DD Rn (example: 2026.02.20 R2)
|
||||
window.CHGRID_WEB_VERSION = "2026.02.25 R250";
|
||||
window.CHGRID_WEB_VERSION = "2026.02.25 R251";
|
||||
// Optional display timezone for timestamps. Falls back to America/Detroit if unset/invalid.
|
||||
window.CHGRID_TIME_ZONE = "America/Detroit";
|
||||
|
||||
@@ -1130,39 +1130,6 @@ function formatCoordinate(value: number): string {
|
||||
return value.toFixed(2).replace(/\.?0+$/, '');
|
||||
}
|
||||
|
||||
/** Persists current local player coordinates for reconnect/refresh restore. */
|
||||
function persistPlayerPosition(): void {
|
||||
try {
|
||||
const positionKey = getPlayerPositionStorageKey();
|
||||
localStorage.setItem(
|
||||
positionKey,
|
||||
JSON.stringify({ x: state.player.x, y: state.player.y }),
|
||||
);
|
||||
} catch {
|
||||
// Ignore storage failures (private mode/quota/blocked storage).
|
||||
}
|
||||
}
|
||||
|
||||
/** Loads previously persisted local player coordinates, when available and valid. */
|
||||
function getPersistedPlayerPosition(): { x: number; y: number } | null {
|
||||
const raw = localStorage.getItem(getPlayerPositionStorageKey());
|
||||
if (!raw) return null;
|
||||
try {
|
||||
const parsed = JSON.parse(raw) as { x?: unknown; y?: unknown };
|
||||
if (typeof parsed.x !== 'number' || typeof parsed.y !== 'number') return null;
|
||||
if (!Number.isFinite(parsed.x) || !Number.isFinite(parsed.y)) return null;
|
||||
return { x: Math.round(parsed.x), y: Math.round(parsed.y) };
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/** Resolves local storage key for per-account saved player position. */
|
||||
function getPlayerPositionStorageKey(): string {
|
||||
const usernameKey = sanitizeAuthUsername(authUsername);
|
||||
return usernameKey ? `spatialChatPosition:${usernameKey}` : 'spatialChatPosition';
|
||||
}
|
||||
|
||||
/** Picks one random footstep sample URL. */
|
||||
function randomFootstepUrl(): string {
|
||||
return FOOTSTEP_SOUND_URLS[Math.floor(Math.random() * FOOTSTEP_SOUND_URLS.length)];
|
||||
@@ -1248,7 +1215,6 @@ function updateTeleport(): void {
|
||||
signaling.send({ type: 'teleport_complete', x: activeTeleport.targetX, y: activeTeleport.targetY });
|
||||
activeTeleport = null;
|
||||
stopTeleportLoopAudio();
|
||||
persistPlayerPosition();
|
||||
void refreshAudioSubscriptions(true);
|
||||
void audio.playSample(TELEPORT_SOUND_URL, FOOTSTEP_GAIN);
|
||||
updateStatus(completionStatus);
|
||||
@@ -1305,7 +1271,6 @@ function handleMovement(): void {
|
||||
state.player.x = nextX;
|
||||
state.player.y = nextY;
|
||||
lastWallCollisionDirection = null;
|
||||
persistPlayerPosition();
|
||||
state.player.lastMoveTime = now;
|
||||
void refreshAudioSubscriptions(true);
|
||||
void audio.playSample(randomFootstepUrl(), FOOTSTEP_GAIN, movementTickMs);
|
||||
@@ -1557,7 +1522,6 @@ function getConnectionFlowDeps(): ConnectFlowDeps {
|
||||
pushChatMessage(message);
|
||||
},
|
||||
updateConnectAvailability,
|
||||
settingsSaveNickname: (value) => settings.saveNickname(value),
|
||||
mediaIsConnecting: () => mediaSession.isConnecting(),
|
||||
mediaSetConnecting: (value) => mediaSession.setConnecting(value),
|
||||
mediaCheckMicPermission: () => checkMicPermission(),
|
||||
@@ -1570,7 +1534,6 @@ function getConnectionFlowDeps(): ConnectFlowDeps {
|
||||
signalingSendAuth: () => sendAuthRequest(),
|
||||
signalingDisconnect: () => signaling.disconnect(),
|
||||
onMessage: (message) => onSignalingMessage(message as IncomingMessage),
|
||||
persistPlayerPosition,
|
||||
peerManagerCleanupAll: () => peerManager.cleanupAll(),
|
||||
radioCleanupAll: () => radioRuntime.cleanupAll(),
|
||||
emitCleanupAll: () => itemEmitRuntime.cleanupAll(),
|
||||
@@ -1615,7 +1578,6 @@ const onAppMessage = createOnMessageHandler({
|
||||
mediaSession.setConnecting(value);
|
||||
updateConnectAvailability();
|
||||
},
|
||||
getPersistedPlayerPosition,
|
||||
rendererSetGridSize: (size) => renderer.setGridSize(size),
|
||||
applyServerItemUiDefinitions: (defs) => applyServerItemUiDefinitions(defs as Parameters<typeof applyServerItemUiDefinitions>[0]),
|
||||
state,
|
||||
@@ -2670,10 +2632,6 @@ function setupUiHandlers(): void {
|
||||
},
|
||||
updateDeviceSummary,
|
||||
setOutputDevice: (id) => peerManager.setOutputDevice(id),
|
||||
persistOnUnload: () => {
|
||||
if (!state.running) return;
|
||||
persistPlayerPosition();
|
||||
},
|
||||
});
|
||||
dom.showRegisterButton.addEventListener('click', () => {
|
||||
setAuthMode('register');
|
||||
|
||||
@@ -9,7 +9,6 @@ type MessageHandlerDeps = {
|
||||
setWorldGridSize: (size: number) => void;
|
||||
setMovementTickMs: (value: number) => void;
|
||||
setConnecting: (value: boolean) => void;
|
||||
getPersistedPlayerPosition: () => { x: number; y: number } | null;
|
||||
rendererSetGridSize: (size: number) => void;
|
||||
applyServerItemUiDefinitions: (defs: unknown) => boolean;
|
||||
state: {
|
||||
@@ -105,11 +104,8 @@ export function createOnMessageHandler(deps: MessageHandlerDeps): (message: Inco
|
||||
deps.state.player.id = message.id;
|
||||
deps.state.running = true;
|
||||
deps.setConnecting(false);
|
||||
const persistedPosition = deps.getPersistedPlayerPosition();
|
||||
const targetX = persistedPosition?.x ?? message.player.x;
|
||||
const targetY = persistedPosition?.y ?? message.player.y;
|
||||
deps.state.player.x = Math.max(0, Math.min(deps.getWorldGridSize() - 1, targetX));
|
||||
deps.state.player.y = Math.max(0, Math.min(deps.getWorldGridSize() - 1, targetY));
|
||||
deps.state.player.x = Math.max(0, Math.min(deps.getWorldGridSize() - 1, message.player.x));
|
||||
deps.state.player.y = Math.max(0, Math.min(deps.getWorldGridSize() - 1, message.player.y));
|
||||
deps.dom.connectButton.classList.add('hidden');
|
||||
deps.dom.disconnectButton.classList.remove('hidden');
|
||||
deps.dom.focusGridButton.classList.remove('hidden');
|
||||
|
||||
@@ -17,7 +17,6 @@ export type ConnectFlowDeps = {
|
||||
sanitizeName: (value: string) => string;
|
||||
updateStatus: (message: string) => void;
|
||||
updateConnectAvailability: () => void;
|
||||
settingsSaveNickname: (value: string) => void;
|
||||
mediaIsConnecting: () => boolean;
|
||||
mediaSetConnecting: (value: boolean) => void;
|
||||
mediaCheckMicPermission: () => Promise<boolean>;
|
||||
@@ -30,7 +29,6 @@ export type ConnectFlowDeps = {
|
||||
signalingSendAuth: () => void;
|
||||
signalingDisconnect: () => void;
|
||||
onMessage: (message: unknown) => Promise<void>;
|
||||
persistPlayerPosition: () => void;
|
||||
peerManagerCleanupAll: () => void;
|
||||
radioCleanupAll: () => void;
|
||||
emitCleanupAll: () => void;
|
||||
@@ -46,9 +44,6 @@ export async function runConnectFlow(deps: ConnectFlowDeps): Promise<void> {
|
||||
}
|
||||
const nickname = deps.sanitizeName(deps.state.player.nickname);
|
||||
deps.state.player.nickname = nickname || deps.state.player.nickname;
|
||||
if (nickname) {
|
||||
deps.settingsSaveNickname(nickname);
|
||||
}
|
||||
deps.mediaSetConnecting(true);
|
||||
deps.updateConnectAvailability();
|
||||
|
||||
@@ -105,9 +100,6 @@ export async function runConnectFlow(deps: ConnectFlowDeps): Promise<void> {
|
||||
*/
|
||||
export function runDisconnectFlow(deps: ConnectFlowDeps): void {
|
||||
const wasRunning = deps.state.running;
|
||||
if (deps.state.running) {
|
||||
deps.persistPlayerPosition();
|
||||
}
|
||||
|
||||
deps.signalingDisconnect();
|
||||
deps.mediaStopLocalMedia();
|
||||
|
||||
@@ -30,16 +30,12 @@ type UiBindingsDeps = {
|
||||
setPreferredOutput: (id: string, name: string) => void;
|
||||
updateDeviceSummary: () => void;
|
||||
setOutputDevice: (id: string) => Promise<void>;
|
||||
persistOnUnload: () => void;
|
||||
};
|
||||
|
||||
/**
|
||||
* Attaches UI listeners (connect/settings/device changes) and focus traps.
|
||||
*/
|
||||
export function setupUiHandlers(deps: UiBindingsDeps): void {
|
||||
window.addEventListener('pagehide', deps.persistOnUnload);
|
||||
window.addEventListener('beforeunload', deps.persistOnUnload);
|
||||
|
||||
deps.dom.connectButton.addEventListener('click', () => {
|
||||
void deps.connect();
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user