lazy load socket connection

This commit is contained in:
Fran Jurmanović
2025-09-21 22:53:23 +02:00
parent 3ba83ad538
commit 6aeb654abf
18 changed files with 216 additions and 572 deletions

View File

@@ -2,7 +2,7 @@
import { redirect } from 'next/navigation';
import { loginUser, getOpenToken } from '@/lib/api/server/auth';
import { login } from '@/lib/auth/server';
import { login, logout } from '@/lib/auth/server';
export type LoginResult = {
success: boolean;
@@ -43,5 +43,5 @@ export async function loginAction(prevState: LoginResult, formData: FormData) {
}
export async function logoutAction() {
redirect('/logout');
logout();
}

View File

@@ -1,5 +1,7 @@
'use client';
import { SessionData } from '@/lib/session/config';
const BASE_URL = process.env.NEXT_PUBLIC_API_BASE_URL || 'http://localhost:8080';
export type ClientApiResponse<T> = {
@@ -8,6 +10,17 @@ export type ClientApiResponse<T> = {
message?: string;
};
const getSession = async (): Promise<SessionData | null> => {
const response = await fetch('/api/session');
if (response.ok) {
return await response.json();
}
return null;
};
const destroySession = async (): Promise<void> => {
await fetch('/api/session', { method: 'DELETE' });
};
export async function fetchClientAPI<T>(
endpoint: string,
method: string = 'GET',
@@ -15,13 +28,11 @@ export async function fetchClientAPI<T>(
customToken?: string
): Promise<ClientApiResponse<T>> {
let token = customToken;
let session: SessionData | null = null;
if (!token) {
const response = await fetch('/api/session');
if (response.ok) {
const session = await response.json();
token = session.openToken;
}
session = await getSession();
token = session?.openToken;
if (!token) {
throw new Error('No authentication token available');
@@ -41,7 +52,8 @@ export async function fetchClientAPI<T>(
if (!response.ok) {
if (response.status === 401) {
window.location.href = '/logout';
await destroySession();
window.location.href = '/login';
return { error: 'unauthorized' };
}
throw new Error(`API Error: ${response.statusText} - ${method} - ${BASE_URL}${endpoint}`);

View File

@@ -1,5 +1,3 @@
import { redirect } from 'next/navigation';
const BASE_URL = process.env.API_BASE_URL || 'http://localhost:8080';
type ApiResponse<T> = {
@@ -8,6 +6,10 @@ type ApiResponse<T> = {
message?: string;
};
const destroySession = async (): Promise<void> => {
await fetch('/api/session', { method: 'DELETE' });
};
export async function fetchServerAPI<T>(
endpoint: string,
token: string,
@@ -28,7 +30,8 @@ export async function fetchServerAPI<T>(
if (!response.ok) {
if (response.status == 401) {
redirect('/logout');
await destroySession();
window.location.href = '/login';
return { error: 'unauthorized' };
}
throw new Error(

View File

@@ -8,11 +8,12 @@ export async function getSession() {
return session;
}
export async function requireAuth() {
export async function requireAuth(skipRedirect?: boolean) {
const session = await getSession();
if (!session.token || !session.user) {
redirect('/logout');
if (!skipRedirect && (!session.token || !session.user)) {
session.destroy();
redirect('/login');
}
return session;

View File

@@ -0,0 +1,7 @@
export interface WebsocketOptions {
url: string;
}
export const websocketOptions: WebsocketOptions = {
url: process.env.NEXT_PUBLIC_WEBSOCKET_URL || 'ws://localhost:3000/ws'
};

View File

@@ -1,7 +1,16 @@
'use client';
import { createContext, useContext, useEffect, useState, ReactNode, useCallback } from 'react';
import {
createContext,
useContext,
useEffect,
useState,
ReactNode,
useCallback,
useRef
} from 'react';
import { WebSocketClient, MessageHandler, ConnectionStatusHandler } from './client';
import { websocketOptions } from './config';
interface WebSocketContextType {
client: WebSocketClient | null;
@@ -30,10 +39,10 @@ export function useWebSocket() {
interface WebSocketProviderProps {
children: ReactNode;
websocketURL: string;
openToken: string;
}
export function WebSocketProvider({ children, websocketURL }: WebSocketProviderProps) {
export function WebSocketProvider({ children, openToken }: WebSocketProviderProps) {
const [client, setClient] = useState<WebSocketClient | null>(null);
const [isConnected, setIsConnected] = useState(false);
const [connectionStatus, setConnectionStatus] = useState<
@@ -51,7 +60,7 @@ export function WebSocketProvider({ children, websocketURL }: WebSocketProviderP
client.disconnect();
}
const newClient = new WebSocketClient(token, websocketURL);
const newClient = new WebSocketClient(token, websocketOptions.url);
const statusHandler: ConnectionStatusHandler = (status, error) => {
setConnectionStatus(status);
@@ -82,7 +91,7 @@ export function WebSocketProvider({ children, websocketURL }: WebSocketProviderP
}
}, [client]);
const reconnect = async () => {
const reconnect = useCallback(async () => {
if (client) {
try {
await client.reconnect();
@@ -91,37 +100,60 @@ export function WebSocketProvider({ children, websocketURL }: WebSocketProviderP
throw error;
}
}
};
}, [client]);
const hasInitialized = useRef(false);
const associateWithServer = (serverId: string) => {
if (client && isConnected) {
client.associateWithServer(serverId);
}
};
const associateWithServer = useCallback(
(serverId: string) => {
if (openToken && !isConnected && !hasInitialized.current) {
hasInitialized.current = true;
connect(openToken).catch((error) => {
console.error('Failed to connect WebSocket:', error);
hasInitialized.current = false;
});
}
if (client && isConnected) {
client.associateWithServer(serverId);
}
},
[client, isConnected, connect]
);
const addMessageHandler = (handler: MessageHandler) => {
if (client) {
client.addMessageHandler(handler);
}
};
const addMessageHandler = useCallback(
(handler: MessageHandler) => {
if (client) {
client.addMessageHandler(handler);
}
},
[client]
);
const removeMessageHandler = (handler: MessageHandler) => {
if (client) {
client.removeMessageHandler(handler);
}
};
const removeMessageHandler = useCallback(
(handler: MessageHandler) => {
if (client) {
client.removeMessageHandler(handler);
}
},
[client]
);
const addConnectionStatusHandler = (handler: ConnectionStatusHandler) => {
if (client) {
client.addConnectionStatusHandler(handler);
}
};
const addConnectionStatusHandler = useCallback(
(handler: ConnectionStatusHandler) => {
if (client) {
client.addConnectionStatusHandler(handler);
}
},
[client]
);
const removeConnectionStatusHandler = (handler: ConnectionStatusHandler) => {
if (client) {
client.removeConnectionStatusHandler(handler);
}
};
const removeConnectionStatusHandler = useCallback(
(handler: ConnectionStatusHandler) => {
if (client) {
client.removeConnectionStatusHandler(handler);
}
},
[client]
);
useEffect(() => {
return () => {