mirror of
https://github.com/legop3/MultiRoombaRover.git
synced 2026-09-15 17:12:59 -04:00
102 lines
4.2 KiB
JavaScript
102 lines
4.2 KiB
JavaScript
// Hook: useUserIdentitySync
|
|
// Purpose: Keeps local identity state synchronized with server session/auth updates. Scope: Handles identity hydration, change propagation, and persistence touch points.
|
|
import { useCallback, useEffect } from 'react';
|
|
import { useSessionActions, useSessionSelector } from '../context/SessionContext.jsx';
|
|
import { useSocket } from '../context/SocketContext.jsx';
|
|
import { getBrowserFingerprintId } from '../lib/browserFingerprint.js';
|
|
import { useSettingsNamespace } from '../settings/index.js';
|
|
|
|
export default function useUserIdentitySync({ identitySurface = 'passive' } = {}) {
|
|
const socket = useSocket();
|
|
const connected = useSessionSelector((state) => state.connected);
|
|
const serverCookieUserId = useSessionSelector((state) => state.session?.identity?.cookieUserId || '');
|
|
const { identifySession } = useSessionActions();
|
|
const { value: identity, status: identityStatus, save: saveIdentity } = useSettingsNamespace('identity', {
|
|
cookieUserId: '',
|
|
});
|
|
const { value: profile, status: profileStatus } = useSettingsNamespace('profile', { nickname: '' });
|
|
const { value: overseerPreference, status: overseerPreferenceStatus } = useSettingsNamespace(
|
|
'overseerPreference',
|
|
{ enabled: false },
|
|
);
|
|
const { value: audioAdjustments, status: audioAdjustmentsStatus } = useSettingsNamespace('audioAdjustments', {
|
|
hornPercent: 0,
|
|
ttsPercent: 0,
|
|
forwardPercent: 0,
|
|
});
|
|
|
|
const ready =
|
|
identityStatus === 'ready'
|
|
&& profileStatus === 'ready'
|
|
&& overseerPreferenceStatus === 'ready'
|
|
&& audioAdjustmentsStatus === 'ready';
|
|
const cookieUserId = (identity?.cookieUserId || '').trim();
|
|
const nickname = (profile?.nickname || '').trim();
|
|
const overseerEnabled = Boolean(overseerPreference?.enabled);
|
|
const normalizedIdentitySurface = identitySurface === 'driver' ? 'driver' : 'passive';
|
|
|
|
const sendIdentify = useCallback(async () => {
|
|
if (!ready || !connected || !socket?.id) return;
|
|
try {
|
|
/*
|
|
Handshake auth establishes identity before connection handlers run.
|
|
This event remains the live-update path for settings that change while
|
|
the current transport stays connected, so existing callers and server
|
|
behavior do not need a second update contract.
|
|
*/
|
|
const resp = await identifySession({
|
|
cookieUserId,
|
|
fingerprintId: await getBrowserFingerprintId(),
|
|
nickname,
|
|
audioAdjustments,
|
|
overseerEnabled,
|
|
identitySurface: normalizedIdentitySurface,
|
|
});
|
|
const nextKey = (resp?.cookieUserId || '').trim();
|
|
if (nextKey && nextKey !== cookieUserId) {
|
|
saveIdentity((current) => ({ ...(current || {}), cookieUserId: nextKey }));
|
|
}
|
|
} catch {
|
|
/*
|
|
A failed live update must not replace Socket.IO's connection lifecycle
|
|
with custom retry state. The next reconnect reads the latest persisted
|
|
settings through handshake auth and re-establishes the complete identity.
|
|
*/
|
|
}
|
|
}, [
|
|
audioAdjustments,
|
|
connected,
|
|
cookieUserId,
|
|
identifySession,
|
|
normalizedIdentitySurface,
|
|
nickname,
|
|
overseerEnabled,
|
|
ready,
|
|
saveIdentity,
|
|
socket,
|
|
]);
|
|
|
|
useEffect(() => {
|
|
/*
|
|
This runs once when the route's settings become ready and again only when
|
|
an identity value changes. Reconnects receive the same data from the auth
|
|
callback, so there is intentionally no timer, visibility retry, or online
|
|
retry here.
|
|
*/
|
|
if (!ready || !connected || !socket?.id) return;
|
|
sendIdentify();
|
|
}, [ready, connected, socket?.id, cookieUserId, nickname, audioAdjustments, overseerEnabled, normalizedIdentitySurface, sendIdentify]);
|
|
|
|
useEffect(() => {
|
|
/*
|
|
A first-time browser has no portable key to send in its handshake, so the
|
|
canonical identity service creates one. Session sync is authoritative for
|
|
that generated value; persisting it here makes every later handshake carry
|
|
the same user key without depending on a session:identify acknowledgement.
|
|
*/
|
|
const nextKey = String(serverCookieUserId || '').trim();
|
|
if (!nextKey || nextKey === cookieUserId) return;
|
|
saveIdentity((current) => ({ ...(current || {}), cookieUserId: nextKey }));
|
|
}, [cookieUserId, saveIdentity, serverCookieUserId]);
|
|
}
|