Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions src/apps/golf/components/GolfGame.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,13 @@
gap: 1rem;
}

.permalinkError {
color: #ff6b6b;
text-align: center;
font-size: 1.1rem;
margin: 0;
}

.primaryButton,
.secondaryButton {
padding: 0.75rem 1.5rem;
Expand Down
18 changes: 17 additions & 1 deletion src/apps/golf/components/GolfGame.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,8 @@ const GolfGame = ({ onGameIdChange, onPlayerIdChange, onPlayerNameChange, onConn
setRoomCode,
leaveGame,
dismissNewGameNotification,
joinNewGame
joinNewGame,
permalinkJoinAttempt
} = useGolfGame({ onGameIdChange, onPlayerIdChange, onPlayerNameChange, onConnectionChange, permalinkParams })

const confirmLeave = useCallback(() => {
Expand Down Expand Up @@ -355,6 +356,21 @@ const GolfGame = ({ onGameIdChange, onPlayerIdChange, onPlayerNameChange, onConn
}

if (!gameState) {
if (permalinkJoinAttempt.error) {
return (
<div className={styles.lobby}>
<h1 className={styles.title}>Golf Card Game</h1>
<div className={styles.lobbyContent}>
<div className={styles.lobbyActions}>
<p className={styles.permalinkError}>{permalinkJoinAttempt.error}</p>
<button onClick={createRoom} className={styles.primaryButton}>
Create New Room
</button>
</div>
</div>
</div>
)
}
return <div className={styles.loading}>Loading...</div>
}

Expand Down
60 changes: 37 additions & 23 deletions src/hooks/useGolfGame.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ export const useGolfGame = ({
error: null as string | null,
gameJoinAttempted: false
})
const [isReconnecting, setIsReconnecting] = useState(false)
const [isManualNavigation, setIsManualNavigation] = useState(false)
const [, setIsCreatingNewGame] = useState(false)
const [newGameNotifications, setNewGameNotifications] = useState<Array<{
Expand All @@ -116,6 +117,7 @@ export const useGolfGame = ({
const networkAdapterRef = useRef<GolfNetworkAdapter | null>(null)
const permalinkTimeoutRef = useRef<number | null>(null)
const notificationTimeoutRef = useRef<number | null>(null)
const reconnectTimeoutRef = useRef<number | null>(null)
const countdownIntervalRef = useRef<number | null>(null)
const navigate = useNavigate()

Expand Down Expand Up @@ -407,7 +409,19 @@ export const useGolfGame = ({
useEffect(() => {
// Create network adapter with callbacks
const adapter = new GolfNetworkAdapter({
onReconnecting: () => {
setIsReconnecting(true)
// Safety: clear after 2s in case server has no state to restore
reconnectTimeoutRef.current = window.setTimeout(() => {
setIsReconnecting(false)
}, 2000)
},
onRoomJoined: (newPlayerId, newRoomState) => {
setIsReconnecting(false)
if (reconnectTimeoutRef.current) {
clearTimeout(reconnectTimeoutRef.current)
reconnectTimeoutRef.current = null
}
setPlayerId(newPlayerId)
setRoomState(newRoomState)
setIsInRoom(true)
Expand Down Expand Up @@ -480,27 +494,7 @@ export const useGolfGame = ({
},
onNotification: (message) => {
showNotification(message)

// Handle permalink join errors
if (permalinkJoinAttempt.isAttempting) {
// Check for common error messages that indicate join failure
if (message.includes('not found') || message.includes('does not exist') ||
message.includes('failed to join') || message.includes('error')) {
// Clear timeout and set error
if (permalinkTimeoutRef.current) {
clearTimeout(permalinkTimeoutRef.current)
permalinkTimeoutRef.current = null
}
setPermalinkJoinAttempt({
isAttempting: false,
roomId: null,
gameId: null,
error: message,
gameJoinAttempted: false
})
}
}


// Parse game end notifications
if (message.includes('Winner:')) {
const winnerMatch = message.match(/Winner: (.+)/)
Expand All @@ -509,6 +503,22 @@ export const useGolfGame = ({
}
}
},
onGameError: (errorMessage) => {
if (permalinkJoinAttempt.isAttempting &&
(errorMessage.includes('not found') || errorMessage.includes('does not exist'))) {
if (permalinkTimeoutRef.current) {
clearTimeout(permalinkTimeoutRef.current)
permalinkTimeoutRef.current = null
}
setPermalinkJoinAttempt({
isAttempting: false,
roomId: null,
gameId: null,
error: 'This room no longer exists.',
gameJoinAttempted: false
})
}
},
onConnectionChange: (connected) => {
setIsConnected(connected)
onConnectionChange?.(connected)
Expand Down Expand Up @@ -562,6 +572,9 @@ export const useGolfGame = ({
if (permalinkTimeoutRef.current) {
clearTimeout(permalinkTimeoutRef.current)
}
if (reconnectTimeoutRef.current) {
clearTimeout(reconnectTimeoutRef.current)
}
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [onGameIdChange, onPlayerIdChange, onPlayerNameChange, onConnectionChange, showNotification])
Expand All @@ -570,7 +583,8 @@ export const useGolfGame = ({
// Handle permalink-based automatic joining
useEffect(() => {
// Only attempt permalink joining if we have valid params and are connected
if (!permalinkParams || !permalinkParams.isValid || !isConnected || !networkAdapterRef.current) {
// Skip while reconnection state restore is in progress to avoid racing
if (!permalinkParams || !permalinkParams.isValid || !isConnected || isReconnecting || !networkAdapterRef.current) {
return
}

Expand Down Expand Up @@ -627,7 +641,7 @@ export const useGolfGame = ({
// Join the room first
networkAdapterRef.current.joinRoom(permalinkParams.roomId)
}
}, [permalinkParams, isConnected, roomState?.id, gameState?.id, permalinkJoinAttempt.isAttempting, permalinkJoinAttempt.gameJoinAttempted, showNotification])
}, [permalinkParams, isConnected, isReconnecting, roomState?.id, gameState?.id, permalinkJoinAttempt.isAttempting, permalinkJoinAttempt.gameJoinAttempted, showNotification])

// Handle successful room join for permalink flow
useEffect(() => {
Expand Down
8 changes: 8 additions & 0 deletions src/plugins/golfNetworkPlugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ export class GolfNetworkPlugin implements GameNetworkPlugin {
private onNotification?: (message: string) => void
private onGameEnded?: (winner: string, finalScores: FinalScore[]) => void
private onNewGameStarted?: (gameId: string, previousGameId?: string) => void
private onReconnecting?: () => void
private onGameError?: (message: string) => void

constructor(callbacks?: {
onRoomJoined?: (playerId: string, roomState: Room) => void
Expand All @@ -58,6 +60,8 @@ export class GolfNetworkPlugin implements GameNetworkPlugin {
onNotification?: (message: string) => void
onGameEnded?: (winner: string, finalScores: FinalScore[]) => void
onNewGameStarted?: (gameId: string, previousGameId?: string) => void
onReconnecting?: () => void
onGameError?: (message: string) => void
}) {
if (callbacks) {
this.onRoomJoined = callbacks.onRoomJoined
Expand All @@ -67,6 +71,8 @@ export class GolfNetworkPlugin implements GameNetworkPlugin {
this.onNotification = callbacks.onNotification
this.onGameEnded = callbacks.onGameEnded
this.onNewGameStarted = callbacks.onNewGameStarted
this.onReconnecting = callbacks.onReconnecting
this.onGameError = callbacks.onGameError
}
}

Expand Down Expand Up @@ -213,6 +219,7 @@ export class GolfNetworkPlugin implements GameNetworkPlugin {
this.clearSessionToken()
}

this.onGameError?.(errorMessage)
this.notify(errorMessage, context)
}

Expand Down Expand Up @@ -275,6 +282,7 @@ export class GolfNetworkPlugin implements GameNetworkPlugin {

if (isReconnect) {
console.log('♻️ Session restored - you should be back in your previous room/game')
this.onReconnecting?.()
}
}

Expand Down
6 changes: 5 additions & 1 deletion src/utils/networkAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,8 @@ export class GolfNetworkAdapter {
onConnectionChange?: (connected: boolean) => void
onGameEnded?: (winner: string, finalScores: FinalScore[]) => void
onNewGameStarted?: (gameId: string, previousGameId?: string) => void
onReconnecting?: () => void
onGameError?: (message: string) => void
}) {
// Create manager with connection state callback
this.manager = new NetworkManager({
Expand Down Expand Up @@ -145,7 +147,9 @@ export class GolfNetworkAdapter {
},
onNotification: callbacks?.onNotification,
onGameEnded: callbacks?.onGameEnded,
onNewGameStarted: callbacks?.onNewGameStarted
onNewGameStarted: callbacks?.onNewGameStarted,
onReconnecting: callbacks?.onReconnecting,
onGameError: callbacks?.onGameError
})

// Register the plugin
Expand Down
Loading