import { create } from 'zustand'; import { useInventoryStore } from './useInventoryStore'; import { mapStoreToSpooferConfig, mapSpooferConfigToStore } from './wsProtocol'; interface WebsocketState { socket: WebSocket | null; isConnected: boolean; connect: () => void; } let lastTogglesJson = ''; let lastInventoryJson = ''; export const useWebsocketStore = create((set, get) => ({ socket: null, isConnected: false, connect: () => { if (get().socket) return; const ws = new WebSocket('ws://localhost:4444'); ws.onopen = () => { console.log('Connected to Spoofer Engine'); set({ socket: ws, isConnected: true }); }; ws.onclose = () => { console.log('Disconnected. Retrying in 2s...'); set({ socket: null, isConnected: false }); setTimeout(() => get().connect(), 2000); }; ws.onerror = (err) => { console.error('WS Error:', err); ws.close(); }; ws.onmessage = async (msg) => { try { const payload = JSON.parse(msg.data); // C++ sends action=0 (INIT_CONFIG) on WebSocket open if (payload.action === 0) { const mapped = await mapSpooferConfigToStore(payload.profile); // Snapshot current state strings to prevent echo lastInventoryJson = JSON.stringify({ unlockedCharacters: mapped.unlockedCharacters, unlockedCustomizations: mapped.unlockedCustomizations, unlockedDLCs: mapped.unlockedDLCs, unlockedPerks: mapped.unlockedPerks, items: mapped.items, offerings: mapped.offerings, addons: mapped.addons }); lastTogglesJson = JSON.stringify({ spoofItems: payload.toggles?.spoofItems ?? false, spoofPerks: payload.toggles?.spoofPerks ?? false, spoofCatalog: payload.toggles?.spoofCatalog ?? false, spoofDLCs: payload.toggles?.spoofDLCs ?? false }); useInventoryStore.getState().importProfile(mapped); useInventoryStore.getState().importToggles(payload.toggles); } } catch (e) { console.error('Failed to parse WS message', e); } }; } })); // Subscribe to store changes and sync to C++ client useInventoryStore.subscribe((state) => { const { socket, isConnected } = useWebsocketStore.getState(); if (!isConnected || !socket || socket.readyState !== WebSocket.OPEN) return; // --- Check toggles --- const toggles = { spoofItems: state.spoofItems, spoofPerks: state.spoofPerks, spoofCatalog: state.spoofCatalog, spoofDLCs: state.spoofDLCs }; const togglesJson = JSON.stringify(toggles); if (togglesJson !== lastTogglesJson) { lastTogglesJson = togglesJson; socket.send(JSON.stringify({ action: 2, toggles })); } // --- Check inventory --- const inventory = { unlockedCharacters: state.unlockedCharacters, unlockedCustomizations: state.unlockedCustomizations, unlockedDLCs: state.unlockedDLCs, unlockedPerks: state.unlockedPerks, items: state.items, offerings: state.offerings, addons: state.addons }; const inventoryJson = JSON.stringify(inventory); if (inventoryJson !== lastInventoryJson) { lastInventoryJson = inventoryJson; mapStoreToSpooferConfig(state).then((profile) => { // Re-check connection in case it dropped during async mapping const ws = useWebsocketStore.getState(); if ( ws.isConnected && ws.socket && ws.socket.readyState === WebSocket.OPEN ) { ws.socket.send(JSON.stringify({ action: 1, profile })); } }); } });