import { Playlist, ServerType, ApiResponse, PlexServerConnection, PlexConnectionSettings, PlexLibrary, SyncStrategy, RegexReplacement } from '../types'; import { MOCK_LOCAL_PLAYLISTS, MOCK_CLOUD_PLAYLISTS } from './mockData'; const SIMULATE_DELAY_MS = 800; // Mock available libraries on a server const MOCK_LIBRARIES: PlexLibrary[] = [ { id: 'lib1', title: 'Music (Flac)', type: 'artist' }, { id: 'lib2', title: 'MP3 Collection', type: 'artist' }, { id: 'lib3', title: 'Soundtracks', type: 'artist' }, { id: 'lib4', title: 'Audiobooks', type: 'artist' } ]; // Helper to simulate network request or call actual API const fetchPlaylists = async (type: ServerType, signal?: AbortSignal): Promise => { return new Promise((resolve, reject) => { const timer = setTimeout(() => { if (type === ServerType.LOCAL) { resolve([...MOCK_LOCAL_PLAYLISTS]); } else { resolve([...MOCK_CLOUD_PLAYLISTS]); } }, SIMULATE_DELAY_MS); if (signal) { signal.addEventListener('abort', () => { clearTimeout(timer); reject(new DOMException('Aborted', 'AbortError')); }); } }); }; const fetchServerStatus = async (signal?: AbortSignal): Promise => { return new Promise((resolve, reject) => { const timer = setTimeout(() => { // 90% chance of success for demo const isSuccess = Math.random() > 0.1; if (isSuccess) { resolve({ isConnected: true, name: 'Home Media Server', ip: '192.168.1.105', port: 32400, libraryName: 'Music (Flac)' }); } else { resolve({ isConnected: false }); } }, SIMULATE_DELAY_MS); if (signal) { signal.addEventListener('abort', () => { clearTimeout(timer); reject(new DOMException('Aborted', 'AbortError')); }); } }); }; const authenticatePlex = async (settings: PlexConnectionSettings, signal?: AbortSignal): Promise<{ token: string, serverInfo: PlexServerConnection }> => { return new Promise((resolve, reject) => { // Determine effective timeout const timeoutSeconds = settings.timeout || 9; const timeoutMs = timeoutSeconds * 1000; // Simulate latency (random between 1s and 2s, or longer to test timeout) const latency = 1500; const timer = setTimeout(() => { // Check if we timed out (simulated) if (latency > timeoutMs) { reject(new Error(`Connection timed out after ${settings.timeout}s`)); return; } // Simulate validation if (!settings.address) { reject(new Error("Server address is required")); return; } // If user provided username/password, mock a token generation let token = settings.token; if (!token && settings.username && settings.password) { token = "MOCK_TOKEN_XYZ_999"; } else if (!token) { reject(new Error("Token or Username/Password required")); return; } // Success response with libraries resolve({ token: token, serverInfo: { isConnected: true, name: 'My Plex Server', ip: settings.address, port: parseInt(settings.port) || 32400, libraryName: MOCK_LIBRARIES[0].title, // Default to first library libraries: MOCK_LIBRARIES } }); }, latency); // Handle User Cancellation if (signal) { signal.addEventListener('abort', () => { clearTimeout(timer); reject(new DOMException('Aborted', 'AbortError')); }); } // Handle Actual Timeout Logic (if latency was indeterminate in real world) // In this mock, the latency is fixed at 1500, but logic above simulates the check. // However, if the user set timeout < 1.5s, we should reject sooner. if (timeoutMs < latency) { setTimeout(() => { clearTimeout(timer); reject(new Error(`Connection timed out after ${settings.timeout}s`)); }, timeoutMs); } }); } const triggerSync = async (strategy: SyncStrategy, regexRules: RegexReplacement[]): Promise => { return new Promise((resolve) => { // Simulate a sync process taking 3 seconds setTimeout(() => { resolve(); }, 3000); }); }; export const apiService = { getPlaylists: async (serverType: ServerType, signal?: AbortSignal): Promise> => { try { const data = await fetchPlaylists(serverType, signal); return { data, status: 'success' }; } catch (error: any) { if (error.name === 'AbortError') { // Return a specific status or handle gracefully return { data: [], status: 'error', message: 'Request cancelled' }; } console.error(`Error fetching ${serverType} playlists:`, error); return { data: [], status: 'error', message: 'Failed to fetch playlists' }; } }, getServerStatus: async (signal?: AbortSignal): Promise> => { try { const data = await fetchServerStatus(signal); return { data, status: 'success' }; } catch (error: any) { if (error.name === 'AbortError') return { data: { isConnected: false }, status: 'error', message: 'Cancelled' }; return { data: { isConnected: false }, status: 'error', message: 'Failed to connect to server' }; } }, connectToPlex: async (settings: PlexConnectionSettings, signal?: AbortSignal): Promise> => { try { const data = await authenticatePlex(settings, signal); return { data, status: 'success', message: 'Connected successfully' }; } catch (error: any) { if (error.name === 'AbortError') { return { data: { token: '', serverInfo: { isConnected: false } }, status: 'error', message: 'Connection cancelled' }; } return { data: { token: '', serverInfo: { isConnected: false } }, status: 'error', message: error.message || 'Connection failed' }; } }, syncPlaylists: async (strategy: SyncStrategy, regexRules: RegexReplacement[]): Promise> => { try { await triggerSync(strategy, regexRules); return { data: null, status: 'success', message: 'Sync complete' }; } catch (error) { return { data: null, status: 'error', message: 'Sync failed' }; } } };