/** * Social Features Service * Handles social interactions, messaging, friend system, and sharing */ import { PlayerData, GameResult, Achievement, AnalyticsEvent, GameLibraryError, GameErrorCode } from '../../core/types'; export interface SocialProfile { playerId: string; displayName: string; avatar?: string; status: 'online' | 'offline' | 'away' | 'busy'; lastSeen: Date; bio?: string; level: number; totalScore: number; favoriteGames: string[]; achievements: Achievement[]; stats: PlayerStats; privacy: PrivacySettings; } export interface PlayerStats { totalGamesPlayed: number; totalTimeSpent: number; averageScore: number; winRate: number; longestStreak: number; friendsCount: number; rank?: number; } export interface PrivacySettings { showOnlineStatus: boolean; allowFriendRequests: boolean; showGameActivity: boolean; allowDirectMessages: boolean; showInLeaderboards: boolean; } export interface Friendship { id: string; senderId: string; receiverId: string; status: 'pending' | 'accepted' | 'blocked'; createdAt: Date; acceptedAt?: Date; } export interface DirectMessage { id: string; fromPlayerId: string; toPlayerId: string; content: string; type: 'text' | 'game_invitation' | 'achievement_share' | 'game_result_share'; metadata?: any; sentAt: Date; readAt?: Date; status: 'sent' | 'delivered' | 'read'; } export interface ShareableContent { type: 'achievement' | 'game_result' | 'challenge' | 'competition_win'; title: string; description: string; imageUrl?: string; data: any; shareUrl: string; } /** * Social Features Service */ export class SocialService { private static instance: SocialService; private profiles = new Map(); private friendships = new Map(); private messages = new Map(); private eventListeners = new Map(); private currentPlayerId?: string; private constructor() {} static getInstance(): SocialService { if (!SocialService.instance) { SocialService.instance = new SocialService(); } return SocialService.instance; } /** * Initialize social service */ async initialize(playerId: string, playerData: PlayerData): Promise { this.currentPlayerId = playerId; // Create or update social profile await this.createOrUpdateProfile(playerId, playerData); // Load sample data await this.loadSampleData(); this.emit('service_initialized', { playerId }); } /** * Get player's social profile */ getProfile(playerId: string): SocialProfile | null { return this.profiles.get(playerId) || null; } /** * Update player's social profile */ async updateProfile(playerId: string, updates: Partial): Promise { const profile = this.profiles.get(playerId); if (!profile) { throw new GameLibraryError('Profile not found', GameErrorCode.PLAYER_NOT_SET); } const updatedProfile = { ...profile, ...updates }; this.profiles.set(playerId, updatedProfile); this.emit('profile_updated', { playerId, profile: updatedProfile }); } /** * Send friend request */ async sendFriendRequest(toPlayerId: string, message?: string): Promise { if (!this.currentPlayerId) { throw new GameLibraryError('Player not initialized', GameErrorCode.PLAYER_NOT_SET); } if (toPlayerId === this.currentPlayerId) { throw new GameLibraryError('Cannot send friend request to yourself', GameErrorCode.INVALID_CONFIG); } const targetProfile = this.profiles.get(toPlayerId); if (!targetProfile) { throw new GameLibraryError('Target player not found', GameErrorCode.PLAYER_NOT_SET); } if (!targetProfile.privacy.allowFriendRequests) { throw new GameLibraryError('Player does not accept friend requests', GameErrorCode.INVALID_CONFIG); } // Check if friendship already exists const existingFriendship = this.getFriendshipBetween(this.currentPlayerId, toPlayerId); if (existingFriendship) { throw new GameLibraryError('Friendship already exists', GameErrorCode.INVALID_CONFIG); } const friendship: Friendship = { id: this.generateFriendshipId(), senderId: this.currentPlayerId, receiverId: toPlayerId, status: 'pending', createdAt: new Date() }; // Add to both players' friendship lists this.addFriendshipToPlayer(this.currentPlayerId, friendship); this.addFriendshipToPlayer(toPlayerId, friendship); this.emit('friend_request_sent', { friendship, message }); return friendship; } /** * Accept friend request */ async acceptFriendRequest(friendshipId: string): Promise { if (!this.currentPlayerId) return; const friendship = this.findFriendship(friendshipId); if (!friendship) { throw new GameLibraryError('Friendship not found', GameErrorCode.GAME_NOT_FOUND); } if (friendship.receiverId !== this.currentPlayerId) { throw new GameLibraryError('Cannot accept this friend request', GameErrorCode.INVALID_CONFIG); } if (friendship.status !== 'pending') { throw new GameLibraryError('Friend request is not pending', GameErrorCode.INVALID_CONFIG); } friendship.status = 'accepted'; friendship.acceptedAt = new Date(); // Update stats const senderProfile = this.profiles.get(friendship.senderId); const receiverProfile = this.profiles.get(friendship.receiverId); if (senderProfile) { senderProfile.stats.friendsCount++; } if (receiverProfile) { receiverProfile.stats.friendsCount++; } this.emit('friend_request_accepted', { friendship }); } /** * Get friends list */ getFriends(playerId: string): SocialProfile[] { const friendships = this.friendships.get(playerId) || []; const acceptedFriendships = friendships.filter(f => f.status === 'accepted'); return acceptedFriendships.map(friendship => { const friendId = friendship.senderId === playerId ? friendship.receiverId : friendship.senderId; return this.profiles.get(friendId); }).filter(Boolean) as SocialProfile[]; } /** * Get pending friend requests */ getPendingFriendRequests(playerId: string): Friendship[] { const friendships = this.friendships.get(playerId) || []; return friendships.filter(f => f.status === 'pending' && f.receiverId === playerId); } /** * Send direct message */ async sendDirectMessage(toPlayerId: string, messageData: { type: DirectMessage['type']; content: string; metadata?: any; }): Promise { if (!this.currentPlayerId) { throw new GameLibraryError('Player not initialized', GameErrorCode.PLAYER_NOT_SET); } const targetProfile = this.profiles.get(toPlayerId); if (!targetProfile || !targetProfile.privacy.allowDirectMessages) { throw new GameLibraryError('Cannot send message to this player', GameErrorCode.INVALID_CONFIG); } const message: DirectMessage = { id: this.generateMessageId(), fromPlayerId: this.currentPlayerId, toPlayerId, content: messageData.content, type: messageData.type, metadata: messageData.metadata, sentAt: new Date(), status: 'sent' }; const conversationId = this.getConversationId(this.currentPlayerId, toPlayerId); const conversation = this.messages.get(conversationId) || []; conversation.push(message); this.messages.set(conversationId, conversation); this.emit('message_sent', { message }); return message; } /** * Get conversation between two players */ getConversation(playerId1: string, playerId2: string): DirectMessage[] { const conversationId = this.getConversationId(playerId1, playerId2); return this.messages.get(conversationId) || []; } /** * Share achievement */ async shareAchievement(achievement: Achievement, message?: string): Promise { if (!this.currentPlayerId) { throw new GameLibraryError('Player not initialized', GameErrorCode.PLAYER_NOT_SET); } const shareableContent: ShareableContent = { type: 'achievement', title: `השגתי את ההישג: ${achievement.name}`, description: achievement.description, data: achievement, shareUrl: `${window.location.origin}/achievement/${achievement.id}` }; this.emit('achievement_shared', { shareableContent, message }); return shareableContent; } /** * Share game result */ async shareGameResult(gameResult: GameResult, message?: string): Promise { if (!this.currentPlayerId) { throw new GameLibraryError('Player not initialized', GameErrorCode.PLAYER_NOT_SET); } const shareableContent: ShareableContent = { type: 'game_result', title: `השגתי ${gameResult.score} נקודות ב${gameResult.gameId}!`, description: `זמן משחק: ${Math.round(gameResult.timeSpent / 1000)} שניות`, data: gameResult, shareUrl: `${window.location.origin}/result/${gameResult.sessionId}` }; this.emit('game_result_shared', { shareableContent, message }); return shareableContent; } /** * Search for players */ searchPlayers(query: string): SocialProfile[] { const lowerQuery = query.toLowerCase(); return Array.from(this.profiles.values()) .filter(profile => profile.displayName.toLowerCase().includes(lowerQuery) || profile.playerId.toLowerCase().includes(lowerQuery) ) .slice(0, 20); } /** * Get online friends */ getOnlineFriends(playerId: string): SocialProfile[] { const friends = this.getFriends(playerId); return friends.filter(friend => friend.status === 'online' && friend.privacy.showOnlineStatus ); } /** * Event subscription */ on(event: string, callback: Function): void { if (!this.eventListeners.has(event)) { this.eventListeners.set(event, []); } this.eventListeners.get(event)!.push(callback); } off(event: string, callback: Function): void { const listeners = this.eventListeners.get(event); if (listeners) { const index = listeners.indexOf(callback); if (index > -1) { listeners.splice(index, 1); } } } /** * Private methods */ private emit(event: string, data: any): void { const listeners = this.eventListeners.get(event) || []; listeners.forEach(callback => { try { callback(data); } catch (error) { console.warn('Event listener error:', error); } }); } private async createOrUpdateProfile(playerId: string, playerData: PlayerData): Promise { const existingProfile = this.profiles.get(playerId); const profile: SocialProfile = { playerId, displayName: playerData.firstName ? `${playerData.firstName} ${playerData.lastName || ''}`.trim() : `שחקן ${playerId.slice(-4)}`, status: 'online', lastSeen: new Date(), level: 1, totalScore: 0, favoriteGames: [], achievements: playerData.achievements || [], stats: { totalGamesPlayed: playerData.totalGamesPlayed || 0, totalTimeSpent: 0, averageScore: 0, winRate: 0, longestStreak: 0, friendsCount: existingProfile?.stats.friendsCount || 0 }, privacy: { showOnlineStatus: true, allowFriendRequests: true, showGameActivity: true, allowDirectMessages: true, showInLeaderboards: true }, ...existingProfile }; this.profiles.set(playerId, profile); } private async loadSampleData(): Promise { const samplePlayers = [ { id: 'player_001', name: 'דני כהן', level: 15 }, { id: 'player_002', name: 'שרה לוי', level: 12 }, { id: 'player_003', name: 'מיכאל ישראלי', level: 8 }, { id: 'player_004', name: 'נועה אברהם', level: 20 } ]; samplePlayers.forEach(player => { if (!this.profiles.has(player.id)) { this.profiles.set(player.id, { playerId: player.id, displayName: player.name, status: Math.random() > 0.5 ? 'online' : 'offline', lastSeen: new Date(Date.now() - Math.random() * 24 * 60 * 60 * 1000), level: player.level, totalScore: player.level * 1000 + Math.floor(Math.random() * 5000), favoriteGames: ['memory-match', 'quiz'].slice(0, Math.floor(Math.random() * 2) + 1), achievements: [], stats: { totalGamesPlayed: player.level * 10, totalTimeSpent: player.level * 30000, averageScore: 500 + Math.floor(Math.random() * 1000), winRate: 0.6 + Math.random() * 0.3, longestStreak: Math.floor(Math.random() * 20), friendsCount: Math.floor(Math.random() * 15) }, privacy: { showOnlineStatus: true, allowFriendRequests: true, showGameActivity: true, allowDirectMessages: true, showInLeaderboards: true } }); } }); } private getFriendshipBetween(playerId1: string, playerId2: string): Friendship | null { const friendships = this.friendships.get(playerId1) || []; return friendships.find(f => (f.senderId === playerId1 && f.receiverId === playerId2) || (f.senderId === playerId2 && f.receiverId === playerId1) ) || null; } private findFriendship(friendshipId: string): Friendship | null { for (const [playerId, friendships] of this.friendships) { const friendship = friendships.find(f => f.id === friendshipId); if (friendship) return friendship; } return null; } private addFriendshipToPlayer(playerId: string, friendship: Friendship): void { const friendships = this.friendships.get(playerId) || []; friendships.push(friendship); this.friendships.set(playerId, friendships); } private getConversationId(playerId1: string, playerId2: string): string { return [playerId1, playerId2].sort().join('_'); } private generateFriendshipId(): string { return `friend_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; } private generateMessageId(): string { return `msg_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; } /** * Cleanup */ destroy(): void { this.profiles.clear(); this.friendships.clear(); this.messages.clear(); this.eventListeners.clear(); this.currentPlayerId = undefined; } }