Initial Commit
This commit is contained in:
87
client/src/context/AuthContext.tsx
Normal file
87
client/src/context/AuthContext.tsx
Normal file
@@ -0,0 +1,87 @@
|
||||
import React, { createContext, useContext, useState, useEffect } from 'react';
|
||||
import { LoginModal } from '../components/LoginModal';
|
||||
|
||||
interface User {
|
||||
id: number;
|
||||
github_id: string;
|
||||
username: string;
|
||||
display_name: string;
|
||||
avatar_url: string;
|
||||
email: string;
|
||||
}
|
||||
|
||||
interface AuthContextType {
|
||||
user: User | null;
|
||||
login: () => void;
|
||||
logout: () => Promise<void>;
|
||||
loading: boolean;
|
||||
showLoginPrompt: () => void;
|
||||
}
|
||||
|
||||
const AuthContext = createContext<AuthContextType | undefined>(undefined);
|
||||
|
||||
export const useAuth = () => {
|
||||
const context = useContext(AuthContext);
|
||||
if (!context) {
|
||||
throw new Error('useAuth must be used within an AuthProvider');
|
||||
}
|
||||
return context;
|
||||
};
|
||||
|
||||
export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
||||
const [user, setUser] = useState<User | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [showLoginModal, setShowLoginModal] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
checkAuthStatus();
|
||||
}, []);
|
||||
|
||||
const checkAuthStatus = async () => {
|
||||
try {
|
||||
const response = await fetch('/api/user', {
|
||||
credentials: 'include'
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
const userData = await response.json();
|
||||
setUser(userData);
|
||||
setShowLoginModal(false); // Close modal on auth
|
||||
} else {
|
||||
setUser(null);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Auth check failed:', error);
|
||||
setUser(null);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const login = () => {
|
||||
window.location.href = '/auth/github';
|
||||
};
|
||||
|
||||
const logout = async () => {
|
||||
try {
|
||||
await fetch('/api/logout', {
|
||||
method: 'POST',
|
||||
credentials: 'include'
|
||||
});
|
||||
setUser(null);
|
||||
} catch (error) {
|
||||
console.error('Logout failed:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const showLoginPrompt = () => {
|
||||
setShowLoginModal(true);
|
||||
};
|
||||
|
||||
return (
|
||||
<AuthContext.Provider value={{ user, login, logout, loading, showLoginPrompt }}>
|
||||
{children}
|
||||
<LoginModal isOpen={showLoginModal} />
|
||||
</AuthContext.Provider>
|
||||
);
|
||||
};
|
||||
122
client/src/context/SocketContext.tsx
Normal file
122
client/src/context/SocketContext.tsx
Normal file
@@ -0,0 +1,122 @@
|
||||
import { io, Socket } from 'socket.io-client';
|
||||
import { createContext, useContext, useEffect, useState, ReactNode } from 'react';
|
||||
import { useAuth } from './AuthContext';
|
||||
import { Pixel, Cursor } from '../types';
|
||||
|
||||
interface SocketContextType {
|
||||
socket: Socket | null;
|
||||
isConnected: boolean;
|
||||
cursors: Cursor[];
|
||||
sendCursorMove: (x: number, y: number) => void;
|
||||
sendPaintPixels: (pixels: Pixel[]) => void;
|
||||
}
|
||||
|
||||
const SocketContext = createContext<SocketContextType | undefined>(undefined);
|
||||
|
||||
export function SocketProvider({ children }: { children: ReactNode }) {
|
||||
const [socket, setSocket] = useState<Socket | null>(null);
|
||||
const [isConnected, setIsConnected] = useState(false);
|
||||
const [cursors, setCursors] = useState<Cursor[]>([]);
|
||||
const { user } = useAuth();
|
||||
|
||||
useEffect(() => {
|
||||
// Only connect sock if user is authed
|
||||
if (!user) {
|
||||
setSocket(null);
|
||||
setIsConnected(false);
|
||||
return;
|
||||
}
|
||||
|
||||
// Get socket auth token first
|
||||
fetch('/api/socket-token', {
|
||||
credentials: 'include'
|
||||
})
|
||||
.then(res => {
|
||||
if (!res.ok) {
|
||||
throw new Error('Not authenticated');
|
||||
}
|
||||
return res.json();
|
||||
})
|
||||
.then(data => {
|
||||
if (data.token) {
|
||||
// Conn with token
|
||||
const newSocket = io('', {
|
||||
transports: ['websocket']
|
||||
});
|
||||
|
||||
newSocket.on('connect', () => {
|
||||
setIsConnected(true);
|
||||
// auth when logged in
|
||||
newSocket.emit('join-canvas', data.token);
|
||||
});
|
||||
|
||||
newSocket.on('disconnect', () => {
|
||||
setIsConnected(false);
|
||||
});
|
||||
|
||||
newSocket.on('canvas-state', (pixels: Pixel[]) => {
|
||||
window.dispatchEvent(new CustomEvent('canvas-state', { detail: pixels }));
|
||||
});
|
||||
|
||||
newSocket.on('canvas-update', (update: { pixels: Pixel[] }) => {
|
||||
window.dispatchEvent(new CustomEvent('canvas-update', { detail: update.pixels }));
|
||||
});
|
||||
|
||||
newSocket.on('cursor-update', (updatedCursors: Cursor[]) => {
|
||||
setCursors(updatedCursors);
|
||||
});
|
||||
|
||||
newSocket.on('error', (message: string) => {
|
||||
console.error('Socket error:', message);
|
||||
// If auth fails we disconnect and retry after delay
|
||||
if (message.includes('Authentication required') || message.includes('expired')) {
|
||||
newSocket.disconnect();
|
||||
setTimeout(() => {
|
||||
// Trigger reconn
|
||||
}, 2000);
|
||||
}
|
||||
});
|
||||
|
||||
newSocket.on('auth-success', () => {
|
||||
});
|
||||
|
||||
setSocket(newSocket);
|
||||
|
||||
return () => {
|
||||
newSocket.disconnect();
|
||||
};
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Failed to get socket token:', error);
|
||||
setSocket(null);
|
||||
setIsConnected(false);
|
||||
});
|
||||
}, [user]);
|
||||
|
||||
const sendCursorMove = (x: number, y: number) => {
|
||||
if (socket && isConnected) {
|
||||
socket.emit('cursor-move', { x, y });
|
||||
}
|
||||
};
|
||||
|
||||
const sendPaintPixels = (pixels: Pixel[]) => {
|
||||
if (socket && isConnected) {
|
||||
socket.emit('paint-pixels', pixels);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<SocketContext.Provider value={{ socket, isConnected, cursors, sendCursorMove, sendPaintPixels }}>
|
||||
{children}
|
||||
</SocketContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
export function useSocket() {
|
||||
const context = useContext(SocketContext);
|
||||
if (context === undefined) {
|
||||
throw new Error('useSocket must be used within a SocketProvider');
|
||||
}
|
||||
return context;
|
||||
}
|
||||
Reference in New Issue
Block a user