import type { User, UserBasic, UserPresence, Chat, Message, MediaItem, StoryGroup, FriendRequest, FriendWithId, FriendshipStatus } from './types'; const API_BASE = '/api'; class ApiClient { private token: string | null = null; setToken(token: string | null) { this.token = token; } private async request(endpoint: string, options: RequestInit & { timeout?: number } = {}): Promise { const { timeout = 30_000, ...fetchOptions } = options; const controller = new AbortController(); const timer = timeout > 0 ? setTimeout(() => controller.abort(), timeout) : undefined; const headers: HeadersInit = { 'Content-Type': 'application/json', ...(this.token ? { Authorization: `Bearer ${this.token}` } : {}), ...fetchOptions.headers, }; let response: Response; try { response = await fetch(`${API_BASE}${endpoint}`, { ...fetchOptions, headers, signal: controller.signal, }); } catch (err) { clearTimeout(timer); if (err instanceof DOMException && err.name === 'AbortError') { throw new Error('Время ожидания запроса истекло'); } throw err; } clearTimeout(timer); if (!response.ok) { const errorData = await response.json().catch(() => ({})); const errorMessage = errorData.error || errorData.message || 'Ошибка запроса'; throw new Error(errorMessage); } return response.json(); } // \u0410\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u044f async login(username: string, password: string) { return this.request<{ token: string; user: User }>('/auth/login', { method: 'POST', body: JSON.stringify({ username, password }), }); } async register(username: string, displayName: string, password: string, bio?: string) { return this.request<{ token: string; user: User }>('/auth/register', { method: 'POST', body: JSON.stringify({ username, displayName, password, bio }), }); } async getMe() { return this.request<{ user: User }>('/auth/me'); } async getConfig() { return this.request('/config'); } // \u041f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0438 async searchUsers(query: string) { return this.request(`/users/search?q=${encodeURIComponent(query)}`); } async getUser(id: string) { return this.request(`/users/${id}`); } async updateProfile(data: { displayName?: string; bio?: string; birthday?: string }) { return this.request('/users/profile', { method: 'PUT', body: JSON.stringify(data), }); } async uploadAvatar(file: File) { const formData = new FormData(); formData.append('avatar', file); const controller = new AbortController(); const timer = setTimeout(() => controller.abort(), 120_000); const response = await fetch(`${API_BASE}/users/avatar`, { method: 'POST', headers: { ...(this.token ? { Authorization: `Bearer ${this.token}` } : {}), }, body: formData, signal: controller.signal, }); clearTimeout(timer); if (!response.ok) throw new Error('Ошибка загрузки аватара'); return response.json() as Promise; } async cropAvatar(file: File, cropData: { x: number; y: number; width: number; height: number }) { const formData = new FormData(); formData.append('avatar', file); formData.append('cropX', cropData.x.toString()); formData.append('cropY', cropData.y.toString()); formData.append('cropWidth', cropData.width.toString()); formData.append('cropHeight', cropData.height.toString()); const response = await fetch(`${API_BASE}/users/avatar/crop`, { method: 'POST', headers: { ...(this.token ? { Authorization: `Bearer ${this.token}` } : {}), }, body: formData, }); if (!response.ok) throw new Error('Ошибка кропа аватара'); return response.json() as Promise; } async removeAvatar() { return this.request('/users/avatar', { method: 'DELETE' }); } async searchMessages(query: string, chatId?: string) { const params = new URLSearchParams({ q: query }); if (chatId) params.append('chatId', chatId); return this.request(`/messages/search?${params}`); } // \u0427\u0430\u0442\u044b async getChats() { return this.request('/chats'); } async createPersonalChat(userId: string) { return this.request('/chats/personal', { method: 'POST', body: JSON.stringify({ userId }), }); } async createGroupChat(name: string, memberIds: string[]) { return this.request('/chats/group', { method: 'POST', body: JSON.stringify({ name, memberIds }), }); } // \u0421\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f async getMessages(chatId: string, cursor?: string) { const params = cursor ? `?cursor=${cursor}` : ''; return this.request(`/messages/chat/${chatId}${params}`); } async uploadFile(file: File) { const formData = new FormData(); formData.append('file', file); const controller = new AbortController(); const timer = setTimeout(() => controller.abort(), 120_000); const response = await fetch(`${API_BASE}/messages/upload`, { method: 'POST', headers: { ...(this.token ? { Authorization: `Bearer ${this.token}` } : {}), }, body: formData, signal: controller.signal, }); clearTimeout(timer); if (!response.ok) throw new Error('\u041e\u0448\u0438\u0431\u043a\u0430 \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0438 \u0444\u0430\u0439\u043b\u0430'); return response.json() as Promise<{ url: string; filename: string; size: number }>; } // \u0413\u0440\u0443\u043f\u043f\u044b async updateGroup(chatId: string, data: { name?: string; description?: string }) { return this.request(`/chats/${chatId}`, { method: 'PUT', body: JSON.stringify(data), }); } async uploadGroupAvatar(chatId: string, file: File) { const formData = new FormData(); formData.append('avatar', file); const controller = new AbortController(); const timer = setTimeout(() => controller.abort(), 120_000); const response = await fetch(`${API_BASE}/chats/${chatId}/avatar`, { method: 'POST', headers: { ...(this.token ? { Authorization: `Bearer ${this.token}` } : {}), }, body: formData, signal: controller.signal, }); clearTimeout(timer); if (!response.ok) throw new Error('\u041e\u0448\u0438\u0431\u043a\u0430 \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0438 \u0430\u0432\u0430\u0442\u0430\u0440\u0430'); return response.json() as Promise; } async cropGroupAvatar(chatId: string, file: File, cropData: { x: number; y: number; width: number; height: number }) { const formData = new FormData(); formData.append('avatar', file); formData.append('x', cropData.x.toString()); formData.append('y', cropData.y.toString()); formData.append('width', cropData.width.toString()); formData.append('height', cropData.height.toString()); const response = await fetch(`${API_BASE}/chats/${chatId}/avatar/crop`, { method: 'POST', headers: { ...(this.token ? { Authorization: `Bearer ${this.token}` } : {}), }, body: formData, }); if (!response.ok) throw new Error('Ошибка кропа аватара'); return response.json() as Promise; } async removeGroupAvatar(chatId: string) { return this.request(`/chats/${chatId}/avatar`, { method: 'DELETE' }); } async addGroupMembers(chatId: string, userIds: string[]) { return this.request(`/chats/${chatId}/members`, { method: 'POST', body: JSON.stringify({ userIds }), }); } async removeGroupMember(chatId: string, userId: string) { return this.request(`/chats/${chatId}/members/${userId}`, { method: 'DELETE', }); } async clearChat(chatId: string) { return this.request<{ message: string }>(`/chats/${chatId}/clear`, { method: 'POST' }); } async deleteChat(chatId: string) { return this.request<{ message: string }>(`/chats/${chatId}`, { method: 'DELETE' }); } async togglePinChat(chatId: string) { return this.request<{ isPinned: boolean }>(`/chats/${chatId}/pin`, { method: 'POST' }); } async getSharedMedia(chatId: string, type: 'media' | 'files' | 'links') { return this.request(`/messages/chat/${chatId}/shared?type=${type}`); } // Stories async getStories() { return this.request('/stories'); } async getUserStories(userId: string) { return this.request(`/stories/user/${userId}`); } async createStory(data: { type: string; mediaUrl?: string; content?: string; bgColor?: string }) { return this.request<{ id: string }>('/stories', { method: 'POST', body: JSON.stringify(data), }); } async uploadVideoToStory(file: File) { const formData = new FormData(); formData.append('file', file); const response = await fetch(`${API_BASE}/stories/video`, { method: 'POST', headers: { ...(this.token ? { Authorization: `Bearer ${this.token}` } : {}), }, body: formData, }); if (!response.ok) throw new Error('Ошибка загрузки видео истории'); return response.json() as Promise<{ url: string }>; } async viewStory(storyId: string) { return this.request<{ message: string }>(`/stories/${storyId}/view`, { method: 'POST' }); } async deleteStory(storyId: string) { return this.request<{ message: string }>(`/stories/${storyId}`, { method: 'DELETE' }); } async getStoryViewers(storyId: string) { return this.request>(`/stories/${storyId}/viewers`); } async addStoryReaction(storyId: string, emoji: string) { return this.request<{ message: string }>(`/stories/${storyId}/reaction`, { method: 'POST', body: JSON.stringify({ emoji }), }); } async removeStoryReaction(storyId: string, emoji: string) { return this.request<{ message: string }>(`/stories/${storyId}/reaction`, { method: 'DELETE', body: JSON.stringify({ emoji }), }); } async addStoryReply(storyId: string, content: string) { return this.request<{ message: string }>(`/stories/${storyId}/reply`, { method: 'POST', body: JSON.stringify({ content }), }); } async getStoryReplies(storyId: string) { return this.request>(`/stories/${storyId}/replies`); } // Favorites chat async getOrCreateFavorites() { return this.request('/chats/favorites', { method: 'POST' }); } // User settings async updateSettings(data: { hideStoryViews?: boolean }) { return this.request('/users/settings', { method: 'PUT', body: JSON.stringify(data), }); } // Friends async getFriends() { return this.request('/friends'); } async getFriendRequests() { return this.request('/friends/requests'); } async getOutgoingRequests() { return this.request('/friends/outgoing'); } async getFriendshipStatus(userId: string) { return this.request(`/friends/status/${userId}`); } async sendFriendRequest(friendId: string) { return this.request<{ status: string }>('/friends/request', { method: 'POST', body: JSON.stringify({ friendId }), }); } async acceptFriendRequest(friendshipId: string) { return this.request<{ id: string }>(`/friends/${friendshipId}/accept`, { method: 'POST' }); } async declineFriendRequest(friendshipId: string) { return this.request<{ success: boolean }>(`/friends/${friendshipId}/decline`, { method: 'POST' }); } async removeFriend(friendshipId: string) { return this.request<{ success: boolean }>(`/friends/${friendshipId}`, { method: 'DELETE' }); } async getIceServers() { return this.request<{ iceServers: RTCIceServer[] }>('/webrtc/ice-servers'); } } export const api = new ApiClient();