import type { Connection } from '../protocol/connection.ts'; import { isLegacyFidelity } from '../protocol/fidelity.ts'; import { MESSAGE_TYPE } from '../protocol/messageTypes.ts'; import type { GenesisMessage, MockServerConfig } from '../types.ts'; import type { AuthState } from './auth.ts'; // HEARTBEAT_PONG, as genesis-router answers it // (RouterMessageDelegator.handleHeartbeat, RouterMessageDelegator.kt:158-195 // @v8.15.29): IS_AUTHENTICATED, plus with VERBOSE:true the connection's // USER_NAME, CREATED_AT, LAST_RECEIVED, LAST_SENT and RESOURCES // (heartbeat.json). The router writes it straight to the socket, so it // doesn't count as LAST_SENT. export function handleHeartbeat( message: GenesisMessage, connection: Connection, config: MockServerConfig, auth: AuthState, ): void { // With the auth gate off (the default), this server accepts every data // message — so the pong must report IS_AUTHENTICATED: true even though the // WS connection itself never "logged in". HTTP-login clients (httpAutoLogin // included) send no SESSION_AUTH_TOKEN on WS messages, so // connection.authenticated stays false forever on their connections; report // that raw flag and the client treats the session as dead and tears it // down, defeating httpAutoLogin entirely. Only when a project opts into // auth.requireAuth does the flag reflect real connection auth state. const requireAuth = config.auth?.requireAuth ?? false; const IS_AUTHENTICATED = connection.authenticated || !requireAuth; if (isLegacyFidelity(config) || message.VERBOSE !== true) { connection.reply( message, { MESSAGE_TYPE: MESSAGE_TYPE.HEARTBEAT_PONG, IS_AUTHENTICATED }, false, ); return; } // A ping carrying a token names the session's user ('' for a dead one); // otherwise the connection's own login. const token = message.SESSION_AUTH_TOKEN; const USER_NAME = typeof token === 'string' ? (auth.sessions.get(token)?.username ?? '') : (connection.userName ?? null); connection.reply( message, { IS_AUTHENTICATED, USER_NAME, CREATED_AT: connection.createdAt, LAST_RECEIVED: connection.lastReceived, LAST_SENT: connection.lastSent, RESOURCES: connection.dataserverResources(), MESSAGE_TYPE: MESSAGE_TYPE.HEARTBEAT_PONG, }, false, ); }