fix websocket connection stuff

This commit is contained in:
Arkaprabha Chakraborty
2025-11-01 22:16:15 +05:30
parent d392e1684b
commit 6849d6d266
5 changed files with 148 additions and 21 deletions

View File

@@ -1,3 +1,4 @@
/* eslint-disable @next/next/no-page-custom-font */
import "./globals.css"; import "./globals.css";
import type { Metadata } from "next"; import type { Metadata } from "next";

View File

@@ -39,6 +39,7 @@ import { JetBrains_Mono } from "next/font/google";
import { ThemeProvider } from "next-themes"; import { ThemeProvider } from "next-themes";
import { ContentWarningModal } from "@/components/ContentWarningModal"; import { ContentWarningModal } from "@/components/ContentWarningModal";
import { BetterHoverCard, HoverCardProvider } from "@/components/ui/BetterHoverCard"; import { BetterHoverCard, HoverCardProvider } from "@/components/ui/BetterHoverCard";
import { getCookie, setCookie } from "@/lib/cookies";
dotenv.config(); dotenv.config();
@@ -200,6 +201,33 @@ const generateUserColor = () => {
return colors[Math.floor(Math.random() * colors.length)]; return colors[Math.floor(Math.random() * colors.length)];
}; };
// User persistence helpers using cookies
const restoreUser = (): User | null => {
if (typeof window === 'undefined') return null;
try {
const stored = getCookie("osborne-user");
if (stored) {
const parsed = JSON.parse(stored);
return {
...parsed,
lastSeen: new Date(parsed.lastSeen),
};
}
} catch (error) {
console.error('Error loading user from storage:', error);
}
return null;
};
const saveUser = (user: User) => {
if (typeof window === 'undefined') return;
try {
setCookie("osborne-user", JSON.stringify(user), 30); // 30 days
} catch (error) {
console.error('Error saving user to storage:', error);
}
};
const Room = () => { const Room = () => {
const router = useRouter(); const router = useRouter();
const searchParams = useSearchParams(); const searchParams = useSearchParams();
@@ -232,6 +260,8 @@ const Room = () => {
const [purgeError, setPurgeError] = useState<string | null>(null); const [purgeError, setPurgeError] = useState<string | null>(null);
const [uploadProgress, setUploadProgress] = useState<Array<{fileName: string; progress: number; status: 'uploading' | 'completed' | 'error'}>>([]); const [uploadProgress, setUploadProgress] = useState<Array<{fileName: string; progress: number; status: 'uploading' | 'completed' | 'error'}>>([]);
const [isRecordingOpen, setIsRecordingOpen] = useState(false); const [isRecordingOpen, setIsRecordingOpen] = useState(false);
const [reconnectAttempts, setReconnectAttempts] = useState(0);
const [isReconnecting, setIsReconnecting] = useState(false);
// Detect mobile screen size // Detect mobile screen size
useEffect(() => { useEffect(() => {
@@ -325,6 +355,14 @@ const Room = () => {
useEffect(() => { useEffect(() => {
setIsClient(true); setIsClient(true);
// Load user from storage if available
if (roomCode) {
const storedUser = restoreUser();
if (storedUser) {
setCurrentUser(storedUser);
}
}
// Set initial window width // Set initial window width
const handleResize = () => { const handleResize = () => {
const newWidth = window.innerWidth; const newWidth = window.innerWidth;
@@ -347,7 +385,7 @@ const Room = () => {
return () => { return () => {
window.removeEventListener('resize', handleResize); window.removeEventListener('resize', handleResize);
}; };
}, []); }, [roomCode]);
// Calculate panel visibility based on window width // Calculate panel visibility based on window width
// Minimum width needed: 320px (left) + 640px (main content) + 320px (right) = 1280px // Minimum width needed: 320px (left) + 640px (main content) + 320px (right) = 1280px
@@ -440,17 +478,32 @@ const Room = () => {
ws.onopen = () => { ws.onopen = () => {
setStatus("Connected"); setStatus("Connected");
setError(""); setError("");
setIsReconnecting(false);
setReconnectAttempts(0); // Reset on successful connection
// Create user if not exists // Use existing user, check storage, or create new one
const user: User = { let user = currentUserRef.current;
if (!user && roomCode) {
user = restoreUser();
}
if (!user) {
user = {
id: generateUserId(), id: generateUserId(),
name: generateUserName(), name: generateUserName(),
color: generateUserColor(), color: generateUserColor(),
lastSeen: new Date(), lastSeen: new Date(),
isTyping: false, isTyping: false,
}; };
}
// Update lastSeen for reconnection
user.lastSeen = new Date();
setCurrentUser(user); setCurrentUser(user);
// Save to storage for persistence across sessions
saveUser(user);
const message: JoinRoom = { const message: JoinRoom = {
type: "join-room", type: "join-room",
code: roomCode, code: roomCode,
@@ -585,6 +638,7 @@ const Room = () => {
ws.onclose = () => { ws.onclose = () => {
setStatus("Disconnected"); setStatus("Disconnected");
setIsReconnecting(false);
setTimeout(() => { setTimeout(() => {
if (socketRef.current === ws) { if (socketRef.current === ws) {
@@ -594,13 +648,17 @@ const Room = () => {
}; };
ws.onerror = () => { ws.onerror = () => {
setIsReconnecting(true);
const backoffDelay = Math.min(1000 * Math.pow(2, reconnectAttempts), 10000); // Max 10 seconds
setTimeout(() => { setTimeout(() => {
if (socketRef.current === ws) { if (socketRef.current === ws) {
setReconnectAttempts(prev => prev + 1);
connectSocket(); connectSocket();
} }
}, 1000); }, backoffDelay);
}; };
}, [roomCode]); }, [roomCode, reconnectAttempts]);
useEffect(() => { useEffect(() => {
if (!isClient || !roomCode) return; if (!isClient || !roomCode) return;
@@ -850,7 +908,7 @@ const Room = () => {
return ( return (
<HoverCardProvider> <HoverCardProvider>
<div className="relative min-h-screen bg-background dark:bg-background ui-font"> <div className="relative min-h-dvh bg-background dark:bg-background ui-font">
<div <div
className="absolute inset-0 transition-all duration-300" className="absolute inset-0 transition-all duration-300"
style={{ style={{
@@ -1230,16 +1288,33 @@ const Room = () => {
</CardHeader> </CardHeader>
<CardContent className="space-y-4 text-sm"> <CardContent className="space-y-4 text-sm">
<p className="text-muted-foreground"> <p className="text-muted-foreground">
The connection to the server was lost. Please check your {isReconnecting
internet connection and try to reconnect. ? `Attempting to reconnect... (Attempt ${reconnectAttempts + 1})`
: "The connection to the server was lost. Please check your internet connection and try to reconnect."
}
</p> </p>
<Button
onClick={() => {
setShowReconnectOverlay(false);
setReconnectAttempts(0);
setIsReconnecting(false);
connectSocket();
}}
className="w-full"
disabled={isReconnecting}
>
<RefreshCw className={`mr-2 h-4 w-4 ${isReconnecting ? 'animate-spin' : ''}`} />
{isReconnecting ? 'Reconnecting...' : 'Reconnect'}
</Button>
{reconnectAttempts > 3 && (
<Button <Button
onClick={() => window.location.reload()} onClick={() => window.location.reload()}
variant="outline"
className="w-full" className="w-full"
> >
<RefreshCw className="mr-2 h-4 w-4" /> Force Reload
Reconnect
</Button> </Button>
)}
</CardContent> </CardContent>
</Card> </Card>
</div> </div>

View File

@@ -4,20 +4,21 @@ import { useState, useEffect } from "react";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { TriangleAlert } from "lucide-react"; import { TriangleAlert } from "lucide-react";
import { getCookie, setCookie } from "@/lib/cookies";
export const ContentWarningModal = () => { export const ContentWarningModal = () => {
const [showWarning, setShowWarning] = useState(false); const [showWarning, setShowWarning] = useState(false);
useEffect(() => { useEffect(() => {
// Check if user has already acknowledged the warning // Check if user has already acknowledged the warning
const hasAcknowledged = localStorage.getItem('content-warning-acknowledged'); const hasAcknowledged = getCookie('osborne-cwa') === 'true';
if (!hasAcknowledged) { if (!hasAcknowledged) {
setShowWarning(true); setShowWarning(true);
} }
}, []); }, []);
const handleAcknowledge = () => { const handleAcknowledge = () => {
localStorage.setItem('content-warning-acknowledged', 'true'); setCookie('osborne-cwa', 'true', 365); // 1 year
setShowWarning(false); setShowWarning(false);
}; };

50
client/lib/cookies.ts Normal file
View File

@@ -0,0 +1,50 @@
/**
* Cookie utility functions for browser-based cookie management
*/
/**
* Get a cookie value by name
* @param name - The name of the cookie
* @returns The cookie value or null if not found
*/
export const getCookie = (name: string): string | null => {
if (typeof window === 'undefined') return null;
const value = `; ${document.cookie}`;
const parts = value.split(`; ${name}=`);
if (parts.length === 2) {
const cookieValue = parts.pop()?.split(';').shift();
return cookieValue ? decodeURIComponent(cookieValue) : null;
}
return null;
};
/**
* Set a cookie with an expiration date
* @param name - The name of the cookie
* @param value - The value to store
* @param days - Number of days until expiration (default: 30)
*/
export const setCookie = (name: string, value: string, days: number = 30) => {
if (typeof window === 'undefined') return;
const expires = new Date();
expires.setTime(expires.getTime() + (days * 24 * 60 * 60 * 1000));
document.cookie = `${name}=${encodeURIComponent(value)};expires=${expires.toUTCString()};path=/;SameSite=Lax`;
};
/**
* Delete a cookie by setting its expiration to the past
* @param name - The name of the cookie to delete
*/
export const deleteCookie = (name: string) => {
if (typeof window === 'undefined') return;
document.cookie = `${name}=;expires=Thu, 01 Jan 1970 00:00:00 UTC;path=/;SameSite=Lax`;
};
/**
* Check if a cookie exists
* @param name - The name of the cookie
* @returns True if the cookie exists, false otherwise
*/
export const cookieExists = (name: string): boolean => {
return getCookie(name) !== null;
};

View File

@@ -377,11 +377,11 @@ export const getNextTheme = (currentThemeId: string): ThemeConfig => {
// Cookie utilities // Cookie utilities
export const saveThemeToCookie = (themeId: string): void => { export const saveThemeToCookie = (themeId: string): void => {
document.cookie = `theme=${themeId}; path=/; max-age=${60 * 60 * 24 * 365}`; // 1 year document.cookie = `osborne-theme=${themeId}; path=/; max-age=${60 * 60 * 24 * 365}`; // 1 year
}; };
export const getThemeFromCookie = (): string | null => { export const getThemeFromCookie = (): string | null => {
const match = document.cookie.match(/(?:^|; )theme=([^;]*)/); const match = document.cookie.match(/(?:^|; )osborne-theme=([^;]*)/);
return match ? decodeURIComponent(match[1]) : null; return match ? decodeURIComponent(match[1]) : null;
}; };