mirror of
https://github.com/arkorty/Osborne.git
synced 2026-03-17 16:51:44 +00:00
fix websocket connection stuff
This commit is contained in:
@@ -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";
|
||||||
|
|
||||||
|
|||||||
@@ -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,16 +478,31 @@ 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;
|
||||||
id: generateUserId(),
|
if (!user && roomCode) {
|
||||||
name: generateUserName(),
|
user = restoreUser();
|
||||||
color: generateUserColor(),
|
}
|
||||||
lastSeen: new Date(),
|
if (!user) {
|
||||||
isTyping: false,
|
user = {
|
||||||
};
|
id: generateUserId(),
|
||||||
|
name: generateUserName(),
|
||||||
|
color: generateUserColor(),
|
||||||
|
lastSeen: new Date(),
|
||||||
|
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",
|
||||||
@@ -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
|
<Button
|
||||||
onClick={() => window.location.reload()}
|
onClick={() => {
|
||||||
|
setShowReconnectOverlay(false);
|
||||||
|
setReconnectAttempts(0);
|
||||||
|
setIsReconnecting(false);
|
||||||
|
connectSocket();
|
||||||
|
}}
|
||||||
className="w-full"
|
className="w-full"
|
||||||
|
disabled={isReconnecting}
|
||||||
>
|
>
|
||||||
<RefreshCw className="mr-2 h-4 w-4" />
|
<RefreshCw className={`mr-2 h-4 w-4 ${isReconnecting ? 'animate-spin' : ''}`} />
|
||||||
Reconnect
|
{isReconnecting ? 'Reconnecting...' : 'Reconnect'}
|
||||||
</Button>
|
</Button>
|
||||||
|
{reconnectAttempts > 3 && (
|
||||||
|
<Button
|
||||||
|
onClick={() => window.location.reload()}
|
||||||
|
variant="outline"
|
||||||
|
className="w-full"
|
||||||
|
>
|
||||||
|
Force Reload
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -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
50
client/lib/cookies.ts
Normal 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;
|
||||||
|
};
|
||||||
@@ -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;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user