Added back Remote Cursors
This commit is contained in:
@@ -1,14 +1,14 @@
|
||||
import { io, Socket } from 'socket.io-client';
|
||||
import { createContext, useContext, useEffect, useState, ReactNode } from 'react';
|
||||
import { createContext, useContext, useEffect, useState, ReactNode, useCallback, useRef } from 'react';
|
||||
import { useAuth } from './AuthContext';
|
||||
import { Pixel, Cursor } from '../types';
|
||||
import { Pixel, Cursor, PaintAck, PendingPixel } from '../types';
|
||||
|
||||
interface SocketContextType {
|
||||
socket: Socket | null;
|
||||
isConnected: boolean;
|
||||
cursors: Cursor[];
|
||||
sendCursorMove: (x: number, y: number) => void;
|
||||
sendPaintPixels: (pixels: Pixel[]) => void;
|
||||
sendPaintPixels: (pixels: Pixel[], onAck?: (ack: PaintAck) => void) => void;
|
||||
}
|
||||
|
||||
const SocketContext = createContext<SocketContextType | undefined>(undefined);
|
||||
@@ -18,16 +18,15 @@ export function SocketProvider({ children }: { children: ReactNode }) {
|
||||
const [isConnected, setIsConnected] = useState(false);
|
||||
const [cursors, setCursors] = useState<Cursor[]>([]);
|
||||
const { user } = useAuth();
|
||||
const pendingPixelsRef = useRef<Map<string, PendingPixel>>(new Map());
|
||||
|
||||
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'
|
||||
})
|
||||
@@ -39,14 +38,12 @@ export function SocketProvider({ children }: { children: ReactNode }) {
|
||||
})
|
||||
.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);
|
||||
});
|
||||
|
||||
@@ -68,11 +65,9 @@ export function SocketProvider({ children }: { children: ReactNode }) {
|
||||
|
||||
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);
|
||||
}
|
||||
});
|
||||
@@ -94,15 +89,74 @@ export function SocketProvider({ children }: { children: ReactNode }) {
|
||||
});
|
||||
}, [user]);
|
||||
|
||||
const resendPendingPixels = useCallback((socket: Socket) => {
|
||||
const now = Date.now();
|
||||
const maxRetries = 3;
|
||||
const retryDelay = 1000;
|
||||
|
||||
for (const [key, pending] of pendingPixelsRef.current) {
|
||||
if (pending.retries >= maxRetries) {
|
||||
pendingPixelsRef.current.delete(key);
|
||||
window.dispatchEvent(new CustomEvent('pixel-sync-failed', { detail: pending.pixel }));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (now - pending.timestamp > retryDelay * (pending.retries + 1)) {
|
||||
pending.retries++;
|
||||
pending.timestamp = now;
|
||||
socket.emit('paint-pixels', {
|
||||
pixels: [pending.pixel],
|
||||
timestamp: pending.timestamp
|
||||
}, (ack: PaintAck) => {
|
||||
if (ack.success) {
|
||||
pendingPixelsRef.current.delete(key);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!socket || !isConnected) return;
|
||||
|
||||
const interval = setInterval(() => {
|
||||
if (pendingPixelsRef.current.size > 0) {
|
||||
resendPendingPixels(socket);
|
||||
}
|
||||
}, 500);
|
||||
|
||||
return () => clearInterval(interval);
|
||||
}, [socket, isConnected, resendPendingPixels]);
|
||||
|
||||
const sendCursorMove = (x: number, y: number) => {
|
||||
if (socket && isConnected) {
|
||||
socket.emit('cursor-move', { x, y });
|
||||
}
|
||||
};
|
||||
|
||||
const sendPaintPixels = (pixels: Pixel[]) => {
|
||||
const sendPaintPixels = (pixels: Pixel[], onAck?: (ack: PaintAck) => void) => {
|
||||
if (socket && isConnected) {
|
||||
socket.emit('paint-pixels', pixels);
|
||||
const timestamp = Date.now();
|
||||
const key = `${pixels[0]?.x},${pixels[0]?.y}`;
|
||||
|
||||
socket.emit('paint-pixels', { pixels, timestamp }, (ack: PaintAck) => {
|
||||
if (onAck) {
|
||||
onAck(ack);
|
||||
}
|
||||
|
||||
if (ack.success) {
|
||||
pendingPixelsRef.current.delete(key);
|
||||
} else if (ack.conflicts > 0 && ack.pixels) {
|
||||
for (const pixel of ack.pixels) {
|
||||
const pixelKey = `${pixel.x},${pixel.y}`;
|
||||
pendingPixelsRef.current.set(pixelKey, {
|
||||
pixel,
|
||||
timestamp: Date.now(),
|
||||
retries: 0
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user