275 lines
6.9 KiB
JavaScript
275 lines
6.9 KiB
JavaScript
/**
|
|
* AeThex Provider
|
|
* Main context provider for AeThex Connect - handles auth, chat, and real-time features
|
|
*/
|
|
|
|
import React, { createContext, useContext, useState, useEffect, useCallback } from 'react';
|
|
import { io } from 'socket.io-client';
|
|
|
|
const API_URL = import.meta.env?.VITE_API_URL || 'http://localhost:5000';
|
|
const API_BASE = `${API_URL}/api`;
|
|
|
|
const AeThexContext = createContext(null);
|
|
|
|
export function useAeThex() {
|
|
return useContext(AeThexContext);
|
|
}
|
|
|
|
export function AeThexProvider({ children }) {
|
|
// Auth state
|
|
const [user, setUser] = useState(null);
|
|
const [token, setTokenState] = useState(null);
|
|
const [loading, setLoading] = useState(true);
|
|
const [error, setError] = useState(null);
|
|
|
|
// Socket state
|
|
const [socket, setSocket] = useState(null);
|
|
const [connected, setConnected] = useState(false);
|
|
|
|
// Chat state
|
|
const [servers, setServers] = useState([]);
|
|
const [channels, setChannels] = useState([]);
|
|
const [messages, setMessages] = useState([]);
|
|
const [currentServer, setCurrentServer] = useState(null);
|
|
const [currentChannel, setCurrentChannel] = useState(null);
|
|
const [onlineUsers, setOnlineUsers] = useState([]);
|
|
|
|
// Token management
|
|
const setToken = useCallback((newToken) => {
|
|
setTokenState(newToken);
|
|
if (newToken) {
|
|
localStorage.setItem('aethex_token', newToken);
|
|
} else {
|
|
localStorage.removeItem('aethex_token');
|
|
}
|
|
}, []);
|
|
|
|
// API request helper
|
|
const apiRequest = useCallback(async (endpoint, options = {}) => {
|
|
const headers = {
|
|
'Content-Type': 'application/json',
|
|
...options.headers,
|
|
};
|
|
|
|
const currentToken = token || localStorage.getItem('aethex_token');
|
|
if (currentToken) {
|
|
headers['Authorization'] = `Bearer ${currentToken}`;
|
|
}
|
|
|
|
const response = await fetch(`${API_BASE}${endpoint}`, {
|
|
...options,
|
|
headers,
|
|
});
|
|
|
|
const data = await response.json();
|
|
if (!response.ok) {
|
|
throw new Error(data.error || 'Request failed');
|
|
}
|
|
return data;
|
|
}, [token]);
|
|
|
|
// Auth functions
|
|
const login = useCallback(async (email, password) => {
|
|
setError(null);
|
|
setLoading(true);
|
|
try {
|
|
const response = await apiRequest('/auth/login', {
|
|
method: 'POST',
|
|
body: JSON.stringify({ email, password }),
|
|
});
|
|
if (response.success) {
|
|
setToken(response.data.token);
|
|
setUser(response.data.user);
|
|
return { success: true };
|
|
}
|
|
throw new Error(response.error || 'Login failed');
|
|
} catch (err) {
|
|
setError(err.message);
|
|
return { success: false, error: err.message };
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
}, [apiRequest, setToken]);
|
|
|
|
const register = useCallback(async (email, password, username, displayName) => {
|
|
setError(null);
|
|
setLoading(true);
|
|
try {
|
|
const response = await apiRequest('/auth/register', {
|
|
method: 'POST',
|
|
body: JSON.stringify({ email, password, username, displayName }),
|
|
});
|
|
if (response.success) {
|
|
setToken(response.data.token);
|
|
setUser(response.data.user);
|
|
return { success: true };
|
|
}
|
|
throw new Error(response.error || 'Registration failed');
|
|
} catch (err) {
|
|
setError(err.message);
|
|
return { success: false, error: err.message };
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
}, [apiRequest, setToken]);
|
|
|
|
const demoLogin = useCallback(async () => {
|
|
setError(null);
|
|
setLoading(true);
|
|
try {
|
|
const response = await apiRequest('/auth/demo', {
|
|
method: 'POST',
|
|
});
|
|
if (response.success) {
|
|
setToken(response.data.token);
|
|
setUser(response.data.user);
|
|
return { success: true };
|
|
}
|
|
throw new Error(response.error || 'Demo login failed');
|
|
} catch (err) {
|
|
setError(err.message);
|
|
return { success: false, error: err.message };
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
}, [apiRequest, setToken]);
|
|
|
|
const logout = useCallback(() => {
|
|
setToken(null);
|
|
setUser(null);
|
|
if (socket) {
|
|
socket.disconnect();
|
|
setSocket(null);
|
|
}
|
|
setConnected(false);
|
|
}, [socket, setToken]);
|
|
|
|
// Socket connection
|
|
const connectSocket = useCallback((authToken) => {
|
|
if (socket) {
|
|
socket.disconnect();
|
|
}
|
|
|
|
const newSocket = io(API_URL, {
|
|
auth: { token: authToken },
|
|
reconnection: true,
|
|
reconnectionDelay: 1000,
|
|
reconnectionAttempts: 10,
|
|
transports: ['websocket', 'polling']
|
|
});
|
|
|
|
newSocket.on('connect', () => {
|
|
console.log('✓ Connected to AeThex Connect');
|
|
setConnected(true);
|
|
});
|
|
|
|
newSocket.on('disconnect', () => {
|
|
console.log('✗ Disconnected from AeThex Connect');
|
|
setConnected(false);
|
|
});
|
|
|
|
newSocket.on('message:new', (message) => {
|
|
setMessages(prev => [...prev, message]);
|
|
});
|
|
|
|
newSocket.on('presence:online', (users) => {
|
|
setOnlineUsers(users);
|
|
});
|
|
|
|
setSocket(newSocket);
|
|
return newSocket;
|
|
}, [socket]);
|
|
|
|
// Chat functions
|
|
const sendMessage = useCallback((content) => {
|
|
if (!socket || !connected || !currentChannel) return;
|
|
socket.emit('channel:message', {
|
|
channelId: currentChannel.id,
|
|
content
|
|
});
|
|
}, [socket, connected, currentChannel]);
|
|
|
|
const joinChannel = useCallback((channelId) => {
|
|
if (!socket || !connected) return;
|
|
socket.emit('channel:join', { channelId });
|
|
}, [socket, connected]);
|
|
|
|
// Check auth on mount
|
|
useEffect(() => {
|
|
const checkAuth = async () => {
|
|
const storedToken = localStorage.getItem('aethex_token');
|
|
if (storedToken) {
|
|
setTokenState(storedToken);
|
|
try {
|
|
const response = await fetch(`${API_BASE}/auth/me`, {
|
|
headers: { 'Authorization': `Bearer ${storedToken}` }
|
|
});
|
|
const data = await response.json();
|
|
if (data.success) {
|
|
setUser(data.data);
|
|
connectSocket(storedToken);
|
|
} else {
|
|
localStorage.removeItem('aethex_token');
|
|
}
|
|
} catch (err) {
|
|
console.error('Auth check failed:', err);
|
|
localStorage.removeItem('aethex_token');
|
|
}
|
|
}
|
|
setLoading(false);
|
|
};
|
|
|
|
checkAuth();
|
|
|
|
return () => {
|
|
if (socket) {
|
|
socket.disconnect();
|
|
}
|
|
};
|
|
}, []);
|
|
|
|
// Connect socket when user logs in
|
|
useEffect(() => {
|
|
if (user && token && !socket) {
|
|
connectSocket(token);
|
|
}
|
|
}, [user, token]);
|
|
|
|
const value = {
|
|
// Auth
|
|
user,
|
|
loading,
|
|
error,
|
|
isAuthenticated: !!user,
|
|
login,
|
|
register,
|
|
demoLogin,
|
|
logout,
|
|
|
|
// Socket
|
|
socket,
|
|
connected,
|
|
|
|
// Chat
|
|
servers,
|
|
channels,
|
|
messages,
|
|
currentServer,
|
|
currentChannel,
|
|
onlineUsers,
|
|
setCurrentServer,
|
|
setCurrentChannel,
|
|
setMessages,
|
|
sendMessage,
|
|
joinChannel,
|
|
|
|
// API helper
|
|
apiRequest
|
|
};
|
|
|
|
return (
|
|
<AeThexContext.Provider value={value}>
|
|
{children}
|
|
</AeThexContext.Provider>
|
|
);
|
|
}
|