chg: dev: massive refactor on fetches - create centralized dataProvider #7
This commit is contained in:
@@ -8,10 +8,10 @@
|
||||
*/
|
||||
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
import { useMutation, useQuery } from '@tanstack/react-query';
|
||||
import { useGame } from '@mine-contexts';
|
||||
import { DESC, IMAGES } from '@mine-utils';
|
||||
import useStepTimer from './useStepTimer';
|
||||
import useGameDataProvider from './useGameDataProvider';
|
||||
import { ChallengeCountdown, WaitingOverlayContent } from '@mine-components';
|
||||
|
||||
const useServerCommunication = (gameAssoc, gameInherited, opponentName, isEnvDev) => {
|
||||
@@ -28,6 +28,17 @@ const useServerCommunication = (gameAssoc, gameInherited, opponentName, isEnvDev
|
||||
cells,
|
||||
} = useGame();
|
||||
|
||||
/** Get all API queries and mutations from data provider */
|
||||
const {
|
||||
connectQuery,
|
||||
startMutation,
|
||||
joinMutation,
|
||||
stepMutation,
|
||||
heartbeatMutation,
|
||||
challengeRespondMutation,
|
||||
leaveMutation,
|
||||
} = useGameDataProvider(gameAssoc);
|
||||
|
||||
const eventSourceRef = useRef(null);
|
||||
const rpcUsersRef = useRef(null);
|
||||
const stepCacheRef = useRef([]);
|
||||
@@ -42,40 +53,6 @@ const useServerCommunication = (gameAssoc, gameInherited, opponentName, isEnvDev
|
||||
|
||||
const HEARTBEAT_INTERVAL_MS = 1500;
|
||||
|
||||
/** REST mutations / queries */
|
||||
|
||||
const connectQuery = useQuery({
|
||||
queryKey: ['game-connect', gameAssoc],
|
||||
queryFn: () => fetch('/api/game/connect/' + gameAssoc)
|
||||
.then(r => r.text())
|
||||
.then(b64 => JSON.parse(window.atob(b64))),
|
||||
enabled: false,
|
||||
retry: false,
|
||||
});
|
||||
|
||||
const startMutation = useMutation({
|
||||
mutationFn: () => fetch('/api/game/start', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ gameAssoc }),
|
||||
}),
|
||||
});
|
||||
|
||||
const joinMutation = useMutation({
|
||||
mutationFn: () => fetch('/api/game/join/' + gameAssoc, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
}).catch(e => isEnvDev && console.error('Join error', e)),
|
||||
});
|
||||
|
||||
const stepMutation = useMutation({
|
||||
mutationFn: dataPack => fetch('/api/game/step/' + gameAssoc, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(dataPack),
|
||||
}).then(r => r.json()),
|
||||
});
|
||||
|
||||
/** Game-start helpers (triggered by server events) */
|
||||
|
||||
const wInit = (revealedCells = [], lastStep = {}, gameState = {}, isGameFinished = false) => {
|
||||
@@ -176,7 +153,7 @@ const useServerCommunication = (gameAssoc, gameInherited, opponentName, isEnvDev
|
||||
overlayTitle = 'Choose an opponent!';
|
||||
overlaySubtitle = gameAssoc ? (
|
||||
<WaitingOverlayContent
|
||||
shareUrl={`${window.location.origin}/battle/${gameAssoc}`}
|
||||
shareUrl={`${window.location.origin}/play/${gameAssoc}`}
|
||||
currentGameAssoc={gameAssoc}
|
||||
/>
|
||||
) : '';
|
||||
@@ -188,7 +165,7 @@ const useServerCommunication = (gameAssoc, gameInherited, opponentName, isEnvDev
|
||||
Promise.resolve().then(() => setGridReady(true));
|
||||
};
|
||||
|
||||
const makeGameStart = (payload, lastStep = {}) => {
|
||||
const makeGameStart = payload => {
|
||||
/** Don't start a finished game */
|
||||
if (isGameFinishedRef.current) {
|
||||
return;
|
||||
@@ -242,11 +219,7 @@ const useServerCommunication = (gameAssoc, gameInherited, opponentName, isEnvDev
|
||||
const publishHeartbeat = () => {
|
||||
const me = webPlayerRef.current;
|
||||
if (!me || endRef.current) return;
|
||||
fetch('/api/game/heartbeat/' + gameAssoc, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ color: me }),
|
||||
}).catch(e => isEnvDev && console.warn('Heartbeat publish failed', e));
|
||||
heartbeatMutation.mutate(me);
|
||||
};
|
||||
|
||||
const startHeartbeat = () => {
|
||||
@@ -264,7 +237,7 @@ const useServerCommunication = (gameAssoc, gameInherited, opponentName, isEnvDev
|
||||
|
||||
/** Mercure / SSE message handlers */
|
||||
|
||||
const wSubscribe = (payload, rpcUsers = null, lastStep = null) => {
|
||||
const wSubscribe = (payload, rpcUsers = null) => {
|
||||
isEnvDev && console.info((payload.user ?? 'user') + ' subscribed');
|
||||
const firstUser = !rpcUsers;
|
||||
|
||||
@@ -279,7 +252,7 @@ const useServerCommunication = (gameAssoc, gameInherited, opponentName, isEnvDev
|
||||
&& (!connectionLostRef.current
|
||||
|| (connectionLostRef.current && false === activePlayerRef.current && !endRef.current))
|
||||
) {
|
||||
makeGameStart(payload, lastStep);
|
||||
makeGameStart(payload);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -315,31 +288,31 @@ const useServerCommunication = (gameAssoc, gameInherited, opponentName, isEnvDev
|
||||
|
||||
const handleAccept = () => {
|
||||
clearTimeout(declineTimeout);
|
||||
fetch('/api/game/challenge/respond/' + challengerGameAssoc, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ accepted: true, targetGameAssoc: gameAssoc }),
|
||||
}).then(() => {
|
||||
showOverlay('Challenge accepted!', 'Waiting for the challenger to join...');
|
||||
}).catch(() => {
|
||||
});
|
||||
challengeRespondMutation.mutate(
|
||||
{ challengerGameAssoc, accepted: true, targetGameAssoc: gameAssoc },
|
||||
{
|
||||
onSuccess: () => {
|
||||
showOverlay('Challenge accepted!', 'Waiting for the challenger to join...');
|
||||
},
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
const handleDecline = () => {
|
||||
clearTimeout(declineTimeout);
|
||||
fetch('/api/game/challenge/respond/' + challengerGameAssoc, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ accepted: false, targetGameAssoc: gameAssoc }),
|
||||
}).then(() => {
|
||||
showOverlay('We are waiting for your opponent...', gameAssoc ? (
|
||||
<WaitingOverlayContent
|
||||
shareUrl={window.location.origin + '/play/' + gameAssoc}
|
||||
currentGameAssoc={gameAssoc}
|
||||
/>
|
||||
) : '');
|
||||
}).catch(() => {
|
||||
});
|
||||
challengeRespondMutation.mutate(
|
||||
{ challengerGameAssoc, accepted: false, targetGameAssoc: gameAssoc },
|
||||
{
|
||||
onSuccess: () => {
|
||||
showOverlay('We are waiting for your opponent...', gameAssoc ? (
|
||||
<WaitingOverlayContent
|
||||
shareUrl={`${window.location.origin}/play/${gameAssoc}`}
|
||||
currentGameAssoc={gameAssoc}
|
||||
/>
|
||||
) : '');
|
||||
},
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
declineTimeout = setTimeout(handleDecline, 30000);
|
||||
@@ -404,7 +377,7 @@ const useServerCommunication = (gameAssoc, gameInherited, opponentName, isEnvDev
|
||||
if (undefined !== payload.data) {
|
||||
wTopic(payload);
|
||||
} else if (undefined === payload.msg) {
|
||||
wSubscribe(payload, rpcUsersRef.current, lastStepRef.current);
|
||||
wSubscribe(payload, rpcUsersRef.current);
|
||||
} else {
|
||||
wUnsubscribe(payload);
|
||||
}
|
||||
@@ -423,7 +396,7 @@ const useServerCommunication = (gameAssoc, gameInherited, opponentName, isEnvDev
|
||||
const subscriberJwt = wrapper.dataset.mercureSubscriberJwt;
|
||||
const url = new URL(hubUrl, window.location.origin);
|
||||
|
||||
url.searchParams.append('topic', 'mineseeker/channel/' + gameAssoc);
|
||||
url.searchParams.append('topic', `mineseeker/channel/${gameAssoc}`);
|
||||
|
||||
if (subscriberJwt) url.searchParams.append('authorization', subscriberJwt);
|
||||
if (eventSourceRef.current) eventSourceRef.current.close();
|
||||
@@ -452,6 +425,7 @@ const useServerCommunication = (gameAssoc, gameInherited, opponentName, isEnvDev
|
||||
openEventSource();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
if (gameInherited) {
|
||||
const serverData = await connectQuery.refetch().then(r => {
|
||||
@@ -497,7 +471,9 @@ const useServerCommunication = (gameAssoc, gameInherited, opponentName, isEnvDev
|
||||
}
|
||||
})();
|
||||
|
||||
window.addEventListener('pagehide', () => navigator.sendBeacon('/api/game/leave/' + gameAssoc));
|
||||
window.addEventListener('pagehide', () => {
|
||||
leaveMutation.mutate();
|
||||
});
|
||||
|
||||
return () => {
|
||||
stopHeartbeat();
|
||||
@@ -525,9 +501,11 @@ const useServerCommunication = (gameAssoc, gameInherited, opponentName, isEnvDev
|
||||
try {
|
||||
const result = await stepMutation.mutateAsync(dataPack);
|
||||
applyStep(result);
|
||||
|
||||
if (result.uuid && !endRef.current) {
|
||||
setGameUuid(result.uuid);
|
||||
}
|
||||
|
||||
makeGameEndIfItEnds(result.bluePoints, result.redPoints, false, result.leftMines);
|
||||
} catch (e) {
|
||||
isEnvDev && console.error('Step error', e);
|
||||
@@ -537,6 +515,7 @@ const useServerCommunication = (gameAssoc, gameInherited, opponentName, isEnvDev
|
||||
const clickResign = () => {
|
||||
const color = activePlayerRef.current ? 'blue' : 'red';
|
||||
const stepElapsed = getStepElapsed(activePlayerRef.current, isGameRunningRef.current);
|
||||
|
||||
stepMutation.mutate(
|
||||
{ resign: color, stepElapsed },
|
||||
{
|
||||
@@ -551,7 +530,9 @@ const useServerCommunication = (gameAssoc, gameInherited, opponentName, isEnvDev
|
||||
|
||||
const resign = () => {
|
||||
const activeColor = activePlayerRef.current ? 'blue' : 'red';
|
||||
|
||||
if (webPlayerRef.current !== activeColor) return;
|
||||
|
||||
showOverlay('Are u sure u want to resign?!', (
|
||||
<div className="resign">
|
||||
<a onClick={clickResign}>Yes</a>
|
||||
|
||||
Reference in New Issue
Block a user