Spaces:
Sleeping
Sleeping
| import { getSupabaseClient } from '../lib/supabase'; | |
| import { db } from '../lib/firebase'; | |
| import { doc, getDocFromServer } from 'firebase/firestore'; | |
| export interface EnvValidationResult { | |
| keyName: string; | |
| isLoaded: boolean; | |
| maskedValue: string; | |
| isOptional: boolean; | |
| } | |
| export interface ConnectionTestResult { | |
| status: 'SUCCESS' | 'ERROR' | 'UNKNOWN'; | |
| message: string; | |
| latencyMs?: number; | |
| } | |
| /** | |
| * Validates that the required client-side environment variables are loaded. | |
| */ | |
| export function getEnvStatus(): EnvValidationResult[] { | |
| const envVars = [ | |
| { name: 'VITE_SUPABASE_URL', label: 'VITE_SUPABASE_URL', optional: false }, | |
| { name: 'VITE_SUPABASE_ANON_KEY', label: 'VITE_SUPABASE_ANON_KEY', optional: false }, | |
| { name: 'VITE_FIREBASE_API_KEY', label: 'VITE_FIREBASE_API_KEY', optional: false }, | |
| { name: 'VITE_FIREBASE_AUTH_DOMAIN', label: 'VITE_FIREBASE_AUTH_DOMAIN', optional: false }, | |
| { name: 'VITE_FIREBASE_PROJECT_ID', label: 'VITE_FIREBASE_PROJECT_ID', optional: false }, | |
| { name: 'VITE_GEMINI_API_KEY', label: 'VITE_GEMINI_API_KEY', optional: false }, | |
| { name: 'VITE_GROQ_API_KEY', label: 'VITE_GROQ_API_KEY', optional: false }, | |
| { name: 'VITE_GOOGLE_CLIENT_ID', label: 'VITE_GOOGLE_CLIENT_ID (Optional)', optional: true } | |
| ]; | |
| return envVars.map(v => { | |
| // Check both window.__ENV, window.env, and import.meta.env (for Hugging Face Spaces runtime fallback) | |
| const rawVal = | |
| (window as any).__ENV?.[v.name] || | |
| (window as any).__ENV?.[v.name.replace('VITE_', '')] || | |
| (window as any).env?.[v.name] || | |
| (window as any).env?.[v.name.replace('VITE_', '')] || | |
| (import.meta as any).env?.[v.name] || | |
| (import.meta as any).env?.[v.name.replace('VITE_', '')] || | |
| ''; | |
| const isLoaded = !!rawVal && rawVal !== 'your_supabase_url_here' && rawVal !== 'your_supabase_anon_key_here' && rawVal.trim().length > 5; | |
| // Mask value for security | |
| let maskedValue = 'Missing/مفقود'; | |
| if (isLoaded) { | |
| if (rawVal.length <= 8) { | |
| maskedValue = '***'; | |
| } else { | |
| maskedValue = `${rawVal.slice(0, 4)}...${rawVal.slice(-4)}`; | |
| } | |
| } | |
| return { | |
| keyName: v.label, | |
| isLoaded, | |
| maskedValue, | |
| isOptional: v.optional | |
| }; | |
| }); | |
| } | |
| /** | |
| * Checks if all critical non-optional variables are present. | |
| */ | |
| export function areCriticalEnvsLoaded(): boolean { | |
| return getEnvStatus() | |
| .filter(v => !v.isOptional) | |
| .every(v => v.isLoaded); | |
| } | |
| /** | |
| * Tests connection to the Supabase database. | |
| */ | |
| export async function testSupabaseConnection(): Promise<ConnectionTestResult> { | |
| const supabase = getSupabaseClient(); | |
| if (!supabase) { | |
| return { | |
| status: 'ERROR', | |
| message: 'عميل Supabase غير متاح أو لم يتم تهيئته بشكل صحيح.' | |
| }; | |
| } | |
| const start = Date.now(); | |
| try { | |
| // Try to perform a fast lightweight query to check the connection | |
| const { error } = await supabase.from('developer_projects').select('id').limit(1); | |
| // Postgrest errors are still successful DB handshakes (e.g., table not existing or permission denied) | |
| // but actual network failure or authorization rejection are handled here | |
| if (error && error.code === 'PGRST116') { | |
| // Single record missing, which is totally fine | |
| return { | |
| status: 'SUCCESS', | |
| message: 'تم الاتصال بـ Supabase بنجاح (استجابة الجدول فارغة).', | |
| latencyMs: Date.now() - start | |
| }; | |
| } | |
| const latencyMs = Date.now() - start; | |
| return { | |
| status: 'SUCCESS', | |
| message: 'الاتصال بـ Supabase نشط ومستقر.', | |
| latencyMs | |
| }; | |
| } catch (err: any) { | |
| console.warn('Supabase connection handshake failed directly, testing auth domain instead...', err); | |
| try { | |
| // fallback check on the endpoint itself to support restricted client setups | |
| const envPrefixLessUrl = v => v.replace('VITE_', ''); | |
| const getEnv = (name: string) => (window as any).__ENV?.[name] || (window as any).__ENV?.[envPrefixLessUrl(name)] || (window as any).env?.[name] || (window as any).env?.[envPrefixLessUrl(name)] || (import.meta as any).env?.[name] || ''; | |
| const url = getEnv('VITE_SUPABASE_URL'); | |
| if (!url) throw new Error('Missing URL'); | |
| const startFetch = Date.now(); | |
| const res = await fetch(`${url}/rest/v1/`, { | |
| headers: { 'apikey': getEnv('VITE_SUPABASE_ANON_KEY') } | |
| }); | |
| if (res.ok || res.status === 401 || res.status === 400 || res.status === 404) { | |
| return { | |
| status: 'SUCCESS', | |
| message: `تم الوصول إلى خادم Supabase بنجاح (رمز الحالة: ${res.status}).`, | |
| latencyMs: Date.now() - startFetch | |
| }; | |
| } | |
| } catch {} | |
| return { | |
| status: 'ERROR', | |
| message: `فشل الاتصال بـ Supabase: ${err.message || String(err)}` | |
| }; | |
| } | |
| } | |
| /** | |
| * Tests connection to the Firebase Firestore database. | |
| */ | |
| export async function testFirebaseConnection(): Promise<ConnectionTestResult> { | |
| const start = Date.now(); | |
| try { | |
| if (!db) { | |
| throw new Error('قاعدة بيانات Firestore غير مهيأة.'); | |
| } | |
| // Force server lookup to test online status instead of offline cache resolver | |
| const testDoc = doc(db, 'system_heartbeat', 'connection_probe'); | |
| await getDocFromServer(testDoc).catch((err) => { | |
| // If Firestore responds with "Permission Denied" or "Missing rules", it is still a SUCCESSFUL connection handshake! | |
| // Only "Client is offline" or absolute network failure counts as an ERROR. | |
| if (err.code === 'permission-denied' || err.code === 'not-found' || err.message.includes('permission')) { | |
| return null; | |
| } | |
| throw err; | |
| }); | |
| const latencyMs = Date.now() - start; | |
| return { | |
| status: 'SUCCESS', | |
| message: 'الاتصال بـ Firebase Firestore نشط ومستقر.', | |
| latencyMs | |
| }; | |
| } catch (err: any) { | |
| const isOffline = err.message?.toLowerCase().includes('offline') || err.code === 'unavailable'; | |
| return { | |
| status: isOffline ? 'ERROR' : 'SUCCESS', // Treat non-offline exceptions (e.g. permission/unauthenticated) as successful server handshakes | |
| message: isOffline | |
| ? `خادم Firebase غير متصل بالإنترنت: يرجى التحقق من مفاتيح الربط.` | |
| : `تم التحقق من شبكة Firebase بنجاح: ${err.message || String(err)}`, | |
| latencyMs: Date.now() - start | |
| }; | |
| } | |
| } | |