feat: Implement initial multiplayer lobby and game room functionality with server-side room management.
This commit is contained in:
@@ -1,10 +1,13 @@
|
||||
import React, { useState } from 'react';
|
||||
import { Layers, Box, Trophy } from 'lucide-react';
|
||||
import { Layers, Box, Trophy, Users } from 'lucide-react';
|
||||
import { CubeManager } from './modules/cube/CubeManager';
|
||||
import { TournamentManager } from './modules/tournament/TournamentManager';
|
||||
import { LobbyManager } from './modules/lobby/LobbyManager';
|
||||
import { Pack } from './services/PackGeneratorService';
|
||||
|
||||
export const App: React.FC = () => {
|
||||
const [activeTab, setActiveTab] = useState<'draft' | 'bracket'>('draft');
|
||||
const [activeTab, setActiveTab] = useState<'draft' | 'bracket' | 'lobby'>('draft');
|
||||
const [generatedPacks, setGeneratedPacks] = useState<Pack[]>([]);
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-slate-900 text-slate-100 font-sans pb-20">
|
||||
@@ -21,22 +24,35 @@ export const App: React.FC = () => {
|
||||
<div className="flex bg-slate-900 rounded-lg p-1 border border-slate-700">
|
||||
<button
|
||||
onClick={() => setActiveTab('draft')}
|
||||
className={`px-4 py-2 rounded-md text-sm font-bold flex items-center gap-2 transition-all ${activeTab === 'draft' ? 'bg-purple-600 text-white' : 'text-slate-400 hover:text-white'}`}
|
||||
className={`px-3 md:px-4 py-2 rounded-md text-sm font-bold flex items-center gap-2 transition-all ${activeTab === 'draft' ? 'bg-purple-600 text-white' : 'text-slate-400 hover:text-white'}`}
|
||||
>
|
||||
<Box className="w-4 h-4" /> Draft Management
|
||||
<Box className="w-4 h-4" /> <span className="hidden md:inline">Draft Management</span>
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setActiveTab('lobby')}
|
||||
className={`px-3 md:px-4 py-2 rounded-md text-sm font-bold flex items-center gap-2 transition-all ${activeTab === 'lobby' ? 'bg-emerald-600 text-white' : 'text-slate-400 hover:text-white'}`}
|
||||
>
|
||||
<Users className="w-4 h-4" /> <span className="hidden md:inline">Online Lobby</span>
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setActiveTab('bracket')}
|
||||
className={`px-4 py-2 rounded-md text-sm font-bold flex items-center gap-2 transition-all ${activeTab === 'bracket' ? 'bg-blue-600 text-white' : 'text-slate-400 hover:text-white'}`}
|
||||
className={`px-3 md:px-4 py-2 rounded-md text-sm font-bold flex items-center gap-2 transition-all ${activeTab === 'bracket' ? 'bg-blue-600 text-white' : 'text-slate-400 hover:text-white'}`}
|
||||
>
|
||||
<Trophy className="w-4 h-4" /> Tournament / Bracket
|
||||
<Trophy className="w-4 h-4" /> <span className="hidden md:inline">Tournament</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
{activeTab === 'draft' && <CubeManager />}
|
||||
{activeTab === 'draft' && (
|
||||
<CubeManager
|
||||
packs={generatedPacks}
|
||||
setPacks={setGeneratedPacks}
|
||||
onGoToLobby={() => setActiveTab('lobby')}
|
||||
/>
|
||||
)}
|
||||
{activeTab === 'lobby' && <LobbyManager generatedPacks={generatedPacks} />}
|
||||
{activeTab === 'bracket' && <TournamentManager />}
|
||||
</main>
|
||||
</div>
|
||||
|
||||
@@ -1,11 +1,17 @@
|
||||
import React, { useState, useRef, useEffect } from 'react';
|
||||
import { Layers, RotateCcw, Box, Check, Loader2, Upload, LayoutGrid, List, Sliders, Settings } from 'lucide-react';
|
||||
import { Layers, RotateCcw, Box, Check, Loader2, Upload, LayoutGrid, List, Sliders, Settings, Users } from 'lucide-react';
|
||||
import { CardParserService } from '../../services/CardParserService';
|
||||
import { ScryfallService, ScryfallCard, ScryfallSet } from '../../services/ScryfallService';
|
||||
import { PackGeneratorService, ProcessedPools, SetsMap, Pack, PackGenerationSettings } from '../../services/PackGeneratorService';
|
||||
import { PackCard } from '../../components/PackCard';
|
||||
|
||||
export const CubeManager: React.FC = () => {
|
||||
interface CubeManagerProps {
|
||||
packs: Pack[];
|
||||
setPacks: React.Dispatch<React.SetStateAction<Pack[]>>;
|
||||
onGoToLobby: () => void;
|
||||
}
|
||||
|
||||
export const CubeManager: React.FC<CubeManagerProps> = ({ packs, setPacks, onGoToLobby }) => {
|
||||
// --- Services ---
|
||||
// --- Services ---
|
||||
// Memoize services to persist cache across renders
|
||||
@@ -27,8 +33,6 @@ export const CubeManager: React.FC = () => {
|
||||
ignoreTokens: true
|
||||
});
|
||||
|
||||
const [packs, setPacks] = useState<Pack[]>([]);
|
||||
|
||||
// UI State
|
||||
const [viewMode, setViewMode] = useState<'list' | 'grid' | 'stack'>('list');
|
||||
|
||||
@@ -378,10 +382,22 @@ export const CubeManager: React.FC = () => {
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<div className="flex bg-slate-800 rounded-lg p-1 border border-slate-700">
|
||||
<button onClick={() => setViewMode('list')} className={`p-2 rounded ${viewMode === 'list' ? 'bg-slate-600 text-white' : 'text-slate-400'}`}><List className="w-4 h-4" /></button>
|
||||
<button onClick={() => setViewMode('grid')} className={`p-2 rounded ${viewMode === 'grid' ? 'bg-slate-600 text-white' : 'text-slate-400'}`}><LayoutGrid className="w-4 h-4" /></button>
|
||||
<button onClick={() => setViewMode('stack')} className={`p-2 rounded ${viewMode === 'stack' ? 'bg-slate-600 text-white' : 'text-slate-400'}`}><Layers className="w-4 h-4" /></button>
|
||||
<div className="flex gap-2">
|
||||
{/* Play Button */}
|
||||
{packs.length > 0 && (
|
||||
<button
|
||||
onClick={onGoToLobby}
|
||||
className="px-4 py-2 bg-gradient-to-r from-purple-600 to-pink-600 hover:from-purple-500 hover:to-pink-500 text-white font-bold rounded-lg shadow-lg flex items-center gap-2 animate-in fade-in zoom-in"
|
||||
>
|
||||
<Users className="w-4 h-4" /> <span className="hidden sm:inline">Play Online</span>
|
||||
</button>
|
||||
)}
|
||||
|
||||
<div className="flex bg-slate-800 rounded-lg p-1 border border-slate-700">
|
||||
<button onClick={() => setViewMode('list')} className={`p-2 rounded ${viewMode === 'list' ? 'bg-slate-600 text-white' : 'text-slate-400'}`}><List className="w-4 h-4" /></button>
|
||||
<button onClick={() => setViewMode('grid')} className={`p-2 rounded ${viewMode === 'grid' ? 'bg-slate-600 text-white' : 'text-slate-400'}`}><LayoutGrid className="w-4 h-4" /></button>
|
||||
<button onClick={() => setViewMode('stack')} className={`p-2 rounded ${viewMode === 'stack' ? 'bg-slate-600 text-white' : 'text-slate-400'}`}><Layers className="w-4 h-4" /></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
179
src/client/src/modules/lobby/GameRoom.tsx
Normal file
179
src/client/src/modules/lobby/GameRoom.tsx
Normal file
@@ -0,0 +1,179 @@
|
||||
|
||||
import React, { useState, useEffect, useRef } from 'react';
|
||||
import { socketService } from '../../services/SocketService';
|
||||
import { Users, MessageSquare, Send, Play, Copy, Check } from 'lucide-react';
|
||||
|
||||
interface Player {
|
||||
id: string;
|
||||
name: string;
|
||||
isHost: boolean;
|
||||
role: 'player' | 'spectator';
|
||||
}
|
||||
|
||||
interface ChatMessage {
|
||||
id: string;
|
||||
sender: string;
|
||||
text: string;
|
||||
timestamp: string;
|
||||
}
|
||||
|
||||
interface Room {
|
||||
id: string;
|
||||
hostId: string;
|
||||
players: Player[];
|
||||
status: string;
|
||||
messages: ChatMessage[];
|
||||
}
|
||||
|
||||
interface GameRoomProps {
|
||||
room: Room;
|
||||
currentPlayerId: string;
|
||||
}
|
||||
|
||||
export const GameRoom: React.FC<GameRoomProps> = ({ room: initialRoom, currentPlayerId }) => {
|
||||
const [room, setRoom] = useState<Room>(initialRoom);
|
||||
const [message, setMessage] = useState('');
|
||||
const [messages, setMessages] = useState<ChatMessage[]>(initialRoom.messages || []);
|
||||
const messagesEndRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
setRoom(initialRoom);
|
||||
setMessages(initialRoom.messages || []);
|
||||
}, [initialRoom]);
|
||||
|
||||
useEffect(() => {
|
||||
const socket = socketService.socket;
|
||||
|
||||
const handleRoomUpdate = (updatedRoom: Room) => {
|
||||
console.log('Room updated:', updatedRoom);
|
||||
setRoom(updatedRoom);
|
||||
};
|
||||
|
||||
const handleNewMessage = (msg: ChatMessage) => {
|
||||
setMessages(prev => [...prev, msg]);
|
||||
};
|
||||
|
||||
socket.on('room_update', handleRoomUpdate);
|
||||
socket.on('new_message', handleNewMessage);
|
||||
|
||||
return () => {
|
||||
socket.off('room_update', handleRoomUpdate);
|
||||
socket.off('new_message', handleNewMessage);
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
|
||||
}, [messages]);
|
||||
|
||||
const sendMessage = (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
if (!message.trim()) return;
|
||||
|
||||
const me = room.players.find(p => p.id === currentPlayerId);
|
||||
socketService.socket.emit('send_message', {
|
||||
roomId: room.id,
|
||||
sender: me?.name || 'Unknown',
|
||||
text: message
|
||||
});
|
||||
setMessage('');
|
||||
};
|
||||
|
||||
const copyRoomId = () => {
|
||||
navigator.clipboard.writeText(room.id);
|
||||
// Could show a toast here
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex h-[calc(100vh-100px)] gap-4">
|
||||
{/* Main Game Area (Placeholder for now) */}
|
||||
<div className="flex-1 bg-slate-800 rounded-xl p-6 border border-slate-700 shadow-xl flex flex-col items-center justify-center">
|
||||
<h2 className="text-3xl font-bold text-white mb-4">Waiting for Players...</h2>
|
||||
<div className="flex items-center gap-4 bg-slate-900 px-6 py-3 rounded-xl border border-slate-700">
|
||||
<span className="text-slate-400 uppercase text-xs font-bold tracking-wider">Room Code</span>
|
||||
<code className="text-2xl font-mono text-emerald-400 font-bold tracking-widest">{room.id}</code>
|
||||
<button onClick={copyRoomId} className="p-2 text-slate-400 hover:text-white transition-colors" title="Copy Code">
|
||||
<Copy className="w-5 h-5" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="mt-8 text-center text-slate-400">
|
||||
<p>Share the code with your friends to join.</p>
|
||||
<p className="text-sm mt-2">
|
||||
<span className="text-emerald-400 font-bold">{room.players.filter(p => p.role === 'player').length}</span> / 8 Players Joined
|
||||
</p>
|
||||
<p className="text-xs mt-1 text-slate-500">
|
||||
{room.players.length} total connected (including spectators)
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{room.players.find(p => p.id === currentPlayerId)?.isHost && (
|
||||
<button
|
||||
onClick={() => socketService.socket.emit('start_game', { roomId: room.id })}
|
||||
disabled={room.status !== 'waiting'}
|
||||
className="mt-8 px-8 py-3 bg-emerald-600 hover:bg-emerald-500 text-white font-bold rounded-lg flex items-center gap-2 shadow-lg shadow-emerald-900/20 transition-all disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
>
|
||||
<Play className="w-5 h-5" /> {room.status === 'waiting' ? 'Start Draft' : 'Draft in Progress'}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Sidebar: Players & Chat */}
|
||||
<div className="w-80 flex flex-col gap-4">
|
||||
{/* Players List */}
|
||||
<div className="flex-1 bg-slate-800 rounded-xl p-4 border border-slate-700 shadow-xl overflow-hidden flex flex-col">
|
||||
<h3 className="text-sm font-bold text-slate-400 uppercase mb-3 flex items-center gap-2">
|
||||
<Users className="w-4 h-4" /> Lobby
|
||||
</h3>
|
||||
<div className="flex-1 overflow-y-auto space-y-2 pr-1">
|
||||
{room.players.map(p => (
|
||||
<div key={p.id} className="flex items-center justify-between bg-slate-900/50 p-2 rounded-lg border border-slate-700/50">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className={`w-8 h-8 rounded-full flex items-center justify-center font-bold text-xs ${p.role === 'spectator' ? 'bg-slate-700 text-slate-300' : 'bg-gradient-to-br from-purple-500 to-blue-500 text-white'}`}>
|
||||
{p.name.substring(0, 2).toUpperCase()}
|
||||
</div>
|
||||
<div className="flex flex-col">
|
||||
<span className={`text-sm font-medium ${p.id === currentPlayerId ? 'text-white' : 'text-slate-300'}`}>
|
||||
{p.name}
|
||||
</span>
|
||||
<span className="text-[10px] uppercase font-bold tracking-wider text-slate-500">
|
||||
{p.role} {p.isHost && <span className="text-amber-500 ml-1">• Host</span>}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Chat */}
|
||||
<div className="h-1/2 bg-slate-800 rounded-xl p-4 border border-slate-700 shadow-xl flex flex-col">
|
||||
<h3 className="text-sm font-bold text-slate-400 uppercase mb-3 flex items-center gap-2">
|
||||
<MessageSquare className="w-4 h-4" /> Chat
|
||||
</h3>
|
||||
<div className="flex-1 overflow-y-auto space-y-2 mb-3 pr-1 custom-scrollbar">
|
||||
{messages.map(msg => (
|
||||
<div key={msg.id} className="text-sm">
|
||||
<span className="font-bold text-purple-400 text-xs">{msg.sender}: </span>
|
||||
<span className="text-slate-300">{msg.text}</span>
|
||||
</div>
|
||||
))}
|
||||
<div ref={messagesEndRef} />
|
||||
</div>
|
||||
<form onSubmit={sendMessage} className="flex gap-2">
|
||||
<input
|
||||
type="text"
|
||||
value={message}
|
||||
onChange={e => setMessage(e.target.value)}
|
||||
className="flex-1 bg-slate-900 border border-slate-700 rounded-lg px-3 py-2 text-sm text-white focus:outline-none focus:ring-2 focus:ring-purple-500"
|
||||
placeholder="Type..."
|
||||
/>
|
||||
<button type="submit" className="p-2 bg-purple-600 hover:bg-purple-500 rounded-lg text-white transition-colors">
|
||||
<Send className="w-4 h-4" />
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
166
src/client/src/modules/lobby/LobbyManager.tsx
Normal file
166
src/client/src/modules/lobby/LobbyManager.tsx
Normal file
@@ -0,0 +1,166 @@
|
||||
|
||||
import React, { useState } from 'react';
|
||||
import { socketService } from '../../services/SocketService';
|
||||
import { GameRoom } from './GameRoom';
|
||||
import { Pack } from '../../services/PackGeneratorService';
|
||||
import { Users, PlusCircle, LogIn, AlertCircle } from 'lucide-react';
|
||||
|
||||
interface LobbyManagerProps {
|
||||
generatedPacks: Pack[];
|
||||
}
|
||||
|
||||
export const LobbyManager: React.FC<LobbyManagerProps> = ({ generatedPacks }) => {
|
||||
const [activeRoom, setActiveRoom] = useState<any>(null);
|
||||
const [playerName, setPlayerName] = useState('');
|
||||
const [joinRoomId, setJoinRoomId] = useState('');
|
||||
const [error, setError] = useState('');
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [playerId] = useState(() => Math.random().toString(36).substring(2) + Date.now().toString(36)); // Simple persistent ID
|
||||
|
||||
const connect = () => {
|
||||
if (!socketService.socket.connected) {
|
||||
socketService.connect();
|
||||
}
|
||||
};
|
||||
|
||||
const handleCreateRoom = async () => {
|
||||
if (!playerName) {
|
||||
setError('Please enter your name');
|
||||
return;
|
||||
}
|
||||
if (generatedPacks.length === 0) {
|
||||
setError('No packs generated! Please go to Draft Management and generate packs first.');
|
||||
return;
|
||||
}
|
||||
|
||||
setLoading(true);
|
||||
setError('');
|
||||
connect();
|
||||
|
||||
try {
|
||||
const response = await socketService.emitPromise('create_room', {
|
||||
hostId: playerId,
|
||||
hostName: playerName,
|
||||
packs: generatedPacks
|
||||
});
|
||||
|
||||
if (response.success) {
|
||||
setActiveRoom(response.room);
|
||||
} else {
|
||||
setError(response.message || 'Failed to create room');
|
||||
}
|
||||
} catch (err: any) {
|
||||
setError(err.message || 'Connection error');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleJoinRoom = async () => {
|
||||
if (!playerName) {
|
||||
setError('Please enter your name');
|
||||
return;
|
||||
}
|
||||
if (!joinRoomId) {
|
||||
setError('Please enter a Room ID');
|
||||
return;
|
||||
}
|
||||
|
||||
setLoading(true);
|
||||
setError('');
|
||||
connect();
|
||||
|
||||
try {
|
||||
const response = await socketService.emitPromise('join_room', {
|
||||
roomId: joinRoomId.toUpperCase(),
|
||||
playerId,
|
||||
playerName
|
||||
});
|
||||
|
||||
if (response.success) {
|
||||
setActiveRoom(response.room);
|
||||
} else {
|
||||
setError(response.message || 'Failed to join room');
|
||||
}
|
||||
} catch (err: any) {
|
||||
setError(err.message || 'Connection error');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
if (activeRoom) {
|
||||
return <GameRoom room={activeRoom} currentPlayerId={playerId} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="max-w-4xl mx-auto p-4 md:p-10">
|
||||
<div className="bg-slate-800 rounded-2xl p-8 border border-slate-700 shadow-2xl">
|
||||
<h2 className="text-3xl font-bold text-white mb-2 flex items-center gap-3">
|
||||
<Users className="w-8 h-8 text-purple-500" /> Multiplayer Lobby
|
||||
</h2>
|
||||
<p className="text-slate-400 mb-8">Create a private room for your draft or join an existing one.</p>
|
||||
|
||||
{error && (
|
||||
<div className="bg-red-900/50 border border-red-500 text-red-200 p-4 rounded-xl mb-6 flex items-center gap-3">
|
||||
<AlertCircle className="w-5 h-5" />
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="space-y-6">
|
||||
<div>
|
||||
<label className="block text-sm font-bold text-slate-300 mb-2">Your Name</label>
|
||||
<input
|
||||
type="text"
|
||||
value={playerName}
|
||||
onChange={(e) => setPlayerName(e.target.value)}
|
||||
placeholder="Enter your nickname..."
|
||||
className="w-full bg-slate-900 border border-slate-700 rounded-xl p-4 text-white focus:ring-2 focus:ring-purple-500 outline-none text-lg"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-8 pt-4 border-t border-slate-700">
|
||||
{/* Create Room */}
|
||||
<div className={`space-y-4 ${generatedPacks.length === 0 ? 'opacity-50' : ''}`}>
|
||||
<h3 className="text-xl font-bold text-white">Create Room</h3>
|
||||
<p className="text-sm text-slate-400">Start a new draft with your {generatedPacks.length} generated packs.</p>
|
||||
<button
|
||||
onClick={handleCreateRoom}
|
||||
disabled={loading || generatedPacks.length === 0}
|
||||
className="w-full py-4 bg-gradient-to-r from-purple-600 to-pink-600 hover:from-purple-500 hover:to-pink-500 text-white font-bold rounded-xl shadow-lg transform transition hover:scale-[1.02] flex justify-center items-center gap-2 disabled:cursor-not-allowed disabled:grayscale"
|
||||
>
|
||||
<PlusCircle className="w-5 h-5" /> {loading ? 'Creating...' : 'Create Private Room'}
|
||||
</button>
|
||||
{generatedPacks.length === 0 && (
|
||||
<p className="text-xs text-amber-500 text-center font-bold">Requires packs from Draft Management tab.</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Join Room */}
|
||||
<div className="space-y-4">
|
||||
<h3 className="text-xl font-bold text-white">Join Room</h3>
|
||||
<p className="text-sm text-slate-400">Enter a code shared by your friend.</p>
|
||||
<div className="flex gap-2">
|
||||
<input
|
||||
type="text"
|
||||
value={joinRoomId}
|
||||
onChange={(e) => setJoinRoomId(e.target.value)}
|
||||
placeholder="ROOM CODE"
|
||||
className="flex-1 bg-slate-900 border border-slate-700 rounded-xl p-4 text-white font-mono uppercase text-lg text-center tracking-widest focus:ring-2 focus:ring-blue-500 outline-none"
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
onClick={handleJoinRoom}
|
||||
disabled={loading}
|
||||
className="w-full py-4 bg-blue-600 hover:bg-blue-500 text-white font-bold rounded-xl shadow-lg transform transition hover:scale-[1.02] flex justify-center items-center gap-2"
|
||||
>
|
||||
<LogIn className="w-5 h-5" /> {loading ? 'Joining...' : 'Join Room'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
37
src/client/src/services/SocketService.ts
Normal file
37
src/client/src/services/SocketService.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
|
||||
import { io, Socket } from 'socket.io-client';
|
||||
|
||||
const URL = `http://${window.location.hostname}:3000`;
|
||||
|
||||
class SocketService {
|
||||
public socket: Socket;
|
||||
|
||||
constructor() {
|
||||
this.socket = io(URL, {
|
||||
autoConnect: false
|
||||
});
|
||||
}
|
||||
|
||||
connect() {
|
||||
this.socket.connect();
|
||||
}
|
||||
|
||||
disconnect() {
|
||||
this.socket.disconnect();
|
||||
}
|
||||
|
||||
// Helper method to make requests with acknowledgements
|
||||
emitPromise(event: string, data: any): Promise<any> {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.socket.emit(event, data, (response: any) => {
|
||||
if (response?.error) {
|
||||
reject(response.error);
|
||||
} else {
|
||||
resolve(response);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export const socketService = new SocketService();
|
||||
Reference in New Issue
Block a user