diff --git a/package.json b/package.json index 8a91a2095..66e38b799 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "tari-universe", "private": true, - "version": "0.8.2", + "version": "0.8.3", "type": "module", "scripts": { "dev": "vite dev --mode development", @@ -68,4 +68,4 @@ "typescript-eslint": "^8.16.0", "vite": "^5.4.11" } -} +} \ No newline at end of file diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 5ff3338d0..e07043845 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -6773,7 +6773,7 @@ dependencies = [ [[package]] name = "tari-universe" -version = "0.8.2" +version = "0.8.3" dependencies = [ "anyhow", "async-trait", diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index d02e8c138..550717357 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -4,7 +4,7 @@ description = "Tari Universe" edition = "2021" name = "tari-universe" repository = "https://github.com/tari-project/universe" -version = "0.8.2" +version = "0.8.3" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -79,7 +79,7 @@ tauri = { version = "2", features = [ "image-png", "image-ico", "tray-icon", - "unstable", + ] } tauri-plugin-os = "2" tauri-plugin-sentry = "=0.2.0" diff --git a/src-tauri/binaries_versions_esmeralda.json b/src-tauri/binaries_versions_esmeralda.json index 68ea52f69..60e7c3f06 100644 --- a/src-tauri/binaries_versions_esmeralda.json +++ b/src-tauri/binaries_versions_esmeralda.json @@ -4,7 +4,7 @@ "mmproxy": "=1.9.0-pre.0", "minotari_node": "=1.9.0-pre.0", "wallet": "=1.9.0-pre.0", - "sha-p2pool": "=0.12.0", + "sha-p2pool": "=0.13.0", "xtrgpuminer": "=0.2.9", "tor": "=13.5.7" } diff --git a/src-tauri/binaries_versions_nextnet.json b/src-tauri/binaries_versions_nextnet.json index f5af9e9a7..839f99b17 100644 --- a/src-tauri/binaries_versions_nextnet.json +++ b/src-tauri/binaries_versions_nextnet.json @@ -4,7 +4,7 @@ "mmproxy": "=1.9.0-rc.0", "minotari_node": "=1.9.0-rc.0", "wallet": "=1.9.0-rc.0", - "sha-p2pool": "=0.12.0", + "sha-p2pool": "=0.13.0", "xtrgpuminer": "=0.2.9", "tor": "=13.5.7" } diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index da7168f98..ca918d8d5 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -2,7 +2,6 @@ #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] use auto_launcher::AutoLauncher; - use hardware::hardware_status_monitor::HardwareStatusMonitor; use log::trace; use log::{debug, error, info, warn}; diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index fedbd4ffb..dcfeb3feb 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -1,5 +1,5 @@ { - "version": "0.8.2", + "version": "0.8.3", "productName": "Tari Universe (Alpha)", "mainBinaryName": "Tari Universe (Alpha)", "identifier": "com.tari.universe.alpha", diff --git a/src/hooks/airdrop/stateHelpers/useGetSosReferrals.ts b/src/hooks/airdrop/stateHelpers/useGetSosReferrals.ts new file mode 100644 index 000000000..1bcc4f84e --- /dev/null +++ b/src/hooks/airdrop/stateHelpers/useGetSosReferrals.ts @@ -0,0 +1,88 @@ +import { useCallback, useEffect, useState } from 'react'; +import { useAirdropRequest } from '../utils/useHandleRequest'; +import { ReferralsResponse, useShellOfSecretsStore } from '@app/store/useShellOfSecretsStore'; +import { CrewMember } from '@app/types/ws'; + +const MAX_REFERRALS = 100; + +export const useGetSosReferrals = () => { + const [intervalSeconds, setIntervalSeconds] = useState(0); + const handleRequest = useAirdropRequest(); + const setReferrals = useShellOfSecretsStore((state) => state.setReferrals); + const referrals = useShellOfSecretsStore((state) => state.referrals); + + const fetchUserReferrals = useCallback(async () => { + const data = await handleRequest({ + path: '/sos/referrals/', + method: 'GET', + }); + if (!data?.toleranceMs) return; + setReferrals(data); + }, [handleRequest, setReferrals]); + + const fetchCrewMemberDetails = useCallback( + async (userId: string) => { + const existingReferral = referrals?.activeReferrals?.find((x) => x.id === userId); + let updatedReferrals = referrals?.activeReferrals || []; + let totalActiveReferrals = referrals?.totalActiveReferrals || 0; + + let shouldUpdate = false; + if (!referrals) return; + + if (!existingReferral) { + shouldUpdate = true; + totalActiveReferrals += 1; + + const data = await handleRequest({ + path: `/sos/crew-member-data/${userId}/`, + method: 'GET', + }); + + if (data && data.id) { + updatedReferrals.push(data); + + // Remove referrals if there are too many saved crew members + if (updatedReferrals.length > MAX_REFERRALS) { + const indexToRemove = updatedReferrals.findIndex((member) => member.active === false); + if (indexToRemove > -1) { + updatedReferrals.splice(indexToRemove, 1); + } else { + updatedReferrals.splice(15, 1); + } + } + } + } else if (existingReferral.active) { + shouldUpdate = true; + updatedReferrals = updatedReferrals.map((x) => { + if (x.id === userId) { + return { ...x, active: true }; + } + return x; + }); + } + + if (shouldUpdate) { + setReferrals({ + ...referrals, + totalActiveReferrals, + activeReferrals: updatedReferrals, + }); + } + }, + [handleRequest, referrals, setReferrals] + ); + + useEffect(() => { + if (intervalSeconds) { + const intervalId = setInterval(fetchUserReferrals, intervalSeconds * 1000); + return () => clearInterval(intervalId); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [intervalSeconds]); + + return { + fetchUserReferrals, + setRefetchIntervalSeconds: setIntervalSeconds, + fetchCrewMemberDetails, + }; +}; diff --git a/src/hooks/airdrop/useWebsocket.ts b/src/hooks/airdrop/useWebsocket.ts index d5f4aa989..a21882c75 100644 --- a/src/hooks/airdrop/useWebsocket.ts +++ b/src/hooks/airdrop/useWebsocket.ts @@ -1,10 +1,9 @@ import { io } from 'socket.io-client'; import { useAirdropStore } from '@app/store/useAirdropStore.ts'; -import { WebsocketEventNames, WebsocketUserEvent } from '@app/types/ws'; import { useCallback, useEffect, useMemo, useState } from 'react'; import { useMiningStore } from '@app/store/useMiningStore'; import { useAppConfigStore } from '@app/store/useAppConfigStore'; -import { useShellOfSecretsStore } from '@app/store/useShellOfSecretsStore'; +import { useHandleWsUserIdEvent } from './ws/useHandleWsUserIdEvent'; let socket: ReturnType | null; @@ -14,14 +13,13 @@ const MINING_EVENT_NAME = 'mining-status'; export const useWebsocket = () => { const airdropToken = useAirdropStore((state) => state.airdropTokens?.token); const userId = useAirdropStore((state) => state.userDetails?.user?.id); - const setUserGems = useAirdropStore((state) => state.setUserGems); - const setTotalBonusTimeMs = useShellOfSecretsStore((state) => state.setTotalBonusTimeMs); const baseUrl = useAirdropStore((state) => state.backendInMemoryConfig?.airdropApiUrl); const cpu = useMiningStore((state) => state.cpu); const gpu = useMiningStore((state) => state.gpu); const network = useMiningStore((state) => state.network); const appId = useAppConfigStore((state) => state.anon_id); const base_node = useMiningStore((state) => state.base_node); + const handleWsUserIdEvent = useHandleWsUserIdEvent(); const [connectedSocket, setConnectedSocket] = useState(false); const isMining = useMemo(() => { @@ -70,19 +68,7 @@ export const useWebsocket = () => { if (!socket) return; setConnectedSocket(true); socket.emit('auth', airdropToken); - socket.on(userId as string, (msg: string) => { - const msgParsed = JSON.parse(msg) as WebsocketUserEvent; - if (msgParsed.name === WebsocketEventNames.COMPLETED_QUEST && msgParsed?.data?.userPoints?.gems) { - setUserGems(msgParsed.data.userPoints?.gems); - } - if ( - (msgParsed.name === WebsocketEventNames.MINING_STATUS_CREW_UPDATE || - msgParsed.name === WebsocketEventNames.MINING_STATUS_USER_UPDATE) && - msgParsed?.data?.totalTimeBonusMs - ) { - setTotalBonusTimeMs(msgParsed.data.totalTimeBonusMs); - } - }); + socket.on(userId as string, handleWsUserIdEvent); }); } catch (e) { console.error(e); diff --git a/src/hooks/airdrop/ws/useHandleWsUserIdEvent.ts b/src/hooks/airdrop/ws/useHandleWsUserIdEvent.ts new file mode 100644 index 000000000..416e0a2f5 --- /dev/null +++ b/src/hooks/airdrop/ws/useHandleWsUserIdEvent.ts @@ -0,0 +1,52 @@ +import { useAirdropStore } from '@app/store/useAirdropStore'; +import { useShellOfSecretsStore } from '@app/store/useShellOfSecretsStore'; +import { WebsocketEventNames, WebsocketUserEvent } from '@app/types/ws'; +import { useGetSosReferrals } from '../stateHelpers/useGetSosReferrals'; + +export const useHandleWsUserIdEvent = () => { + const setTotalBonusTimeMs = useShellOfSecretsStore((state) => state.setTotalBonusTimeMs); + const referrals = useShellOfSecretsStore((state) => state.referrals); + const setReferrals = useShellOfSecretsStore((state) => state.setReferrals); + const setUserGems = useAirdropStore((state) => state.setUserGems); + const { fetchCrewMemberDetails } = useGetSosReferrals(); + + return (event: string) => { + const eventParsed = JSON.parse(event) as WebsocketUserEvent; + switch (eventParsed.name) { + case WebsocketEventNames.COMPLETED_QUEST: + if (eventParsed.data.userPoints?.gems) { + setUserGems(eventParsed.data.userPoints?.gems); + } + break; + case WebsocketEventNames.MINING_STATUS_CREW_UPDATE: { + fetchCrewMemberDetails(eventParsed.data.crewMember.id); + setTotalBonusTimeMs(eventParsed.data.totalTimeBonusMs); + break; + } + + case WebsocketEventNames.MINING_STATUS_CREW_DISCONNECTED: + if (referrals?.activeReferrals) { + const totalActiveReferrals = (referrals?.totalActiveReferrals || 1) - 1; + const referralsUpdated = referrals?.activeReferrals.map((x) => { + if (x.id === eventParsed.data.crewMemberId) { + return { ...x, active: false }; + } + return x; + }); + + setReferrals({ + ...referrals, + totalActiveReferrals, + activeReferrals: referralsUpdated, + }); + } + break; + case WebsocketEventNames.MINING_STATUS_USER_UPDATE: + setTotalBonusTimeMs(eventParsed.data.totalTimeBonusMs); + break; + default: + // eslint-disable-next-line no-console + console.log('Unknown event', eventParsed); + } + }; +}; diff --git a/src/store/useShellOfSecretsStore.ts b/src/store/useShellOfSecretsStore.ts index b4f8cfdef..188ba66a2 100644 --- a/src/store/useShellOfSecretsStore.ts +++ b/src/store/useShellOfSecretsStore.ts @@ -1,20 +1,32 @@ +import { CrewMember } from '@app/types/ws.ts'; import { create } from './create.ts'; const SOS_GAME_ENDING_DATE = new Date('2025-01-30'); +// Type for the response structure +export interface ReferralsResponse { + activeReferrals: CrewMember[]; + totalActiveReferrals: number; + totalReferrals: number; + toleranceMs: number; +} + interface State { + referrals?: ReferralsResponse; showWidget: boolean; totalBonusTimeMs: number; revealDate: Date; } interface Actions { + setReferrals: (referrals: ReferralsResponse) => void; setShowWidget: (showWidget: boolean) => void; setTotalBonusTimeMs: (totalTimeBonusUpdate: number) => void; getTimeRemaining: () => { days: number; hours: number; totalRemainingMs: number }; } const initialState: State = { + referrals: undefined, showWidget: false, totalBonusTimeMs: 0, revealDate: SOS_GAME_ENDING_DATE, @@ -22,6 +34,7 @@ const initialState: State = { export const useShellOfSecretsStore = create()((set, get) => ({ ...initialState, + setReferrals: (referrals) => set({ referrals }), setShowWidget: (showWidget) => set({ showWidget }), setTotalBonusTimeMs: (totalTimeBonusUpdate: number) => set({ totalBonusTimeMs: totalTimeBonusUpdate }), getTimeRemaining: () => { diff --git a/src/types/ws.ts b/src/types/ws.ts index f5fe0b2db..b76306d0e 100644 --- a/src/types/ws.ts +++ b/src/types/ws.ts @@ -1,6 +1,7 @@ export enum WebsocketEventNames { COMPLETED_QUEST = 'completed_quest', MINING_STATUS_CREW_UPDATE = 'mining_status_crew_update', + MINING_STATUS_CREW_DISCONNECTED = 'mining_status_crew_disconnected', MINING_STATUS_USER_UPDATE = 'mining_status_user_update', } @@ -19,10 +20,20 @@ export interface QuestCompletedEvent { }; } +export interface CrewMember { + imageUrl: string | null; + id: string; + name: string; + profileImageUrl: string | null; + lastHandshakeAt: Date | null; + active?: boolean; +} + export interface MiningStatusCrewUpdateEvent { name: WebsocketEventNames.MINING_STATUS_CREW_UPDATE; data: { totalTimeBonusMs: number; + crewMember: { id: string }; }; } @@ -33,4 +44,15 @@ export interface MiningStatusUserUpdateEvent { }; } -export type WebsocketUserEvent = QuestCompletedEvent | MiningStatusCrewUpdateEvent | MiningStatusUserUpdateEvent; +export interface MiningStatusCrewDisconnectedEvent { + name: WebsocketEventNames.MINING_STATUS_CREW_DISCONNECTED; + data: { + crewMemberId: string; + }; +} + +export type WebsocketUserEvent = + | QuestCompletedEvent + | MiningStatusCrewUpdateEvent + | MiningStatusUserUpdateEvent + | MiningStatusCrewDisconnectedEvent;