.fanpay.africa et domaines custom vérifiés. * * - /robots.txt & /sitemap.xml : artefacts de la release live (quel que soit * l'agent — ces fichiers sont destinés aux machines). * - Bots sociaux/moteurs : artefact HTML compilé (méta OG/Twitter, * canonical, JSON-LD correctes + contenu SSR) au lieu de la coquille SPA. * * Repli : tout hôte plateforme, site introuvable/non publié ou panne de * web-serve retombe sur index.php (flux SPA + bot-detector existant) — * comportement inchangé par rapport à avant. */ $WB_SERVE = 'https://pgnkuxeckqnzkyfcrwzs.supabase.co/functions/v1/web-serve'; function wb_fallback(): void { $index = __DIR__ . '/index.php'; if (is_file($index)) { require $index; } else { readfile(__DIR__ . '/index.html'); } exit; } $rawHost = $_SERVER['HTTP_HOST'] ?? ($_SERVER['HTTP_X_FORWARDED_HOST'] ?? ''); $host = strtolower(trim(explode(',', (string) $rawHost)[0])); $host = preg_replace('/:\d+$/', '', $host); $platformHosts = ['fanpay.africa', 'www.fanpay.africa', 'votafrica.com', 'www.votafrica.com']; // Filet de sécurité : jamais de proxy pour un domaine plateforme. if ($host === '' || in_array($host, $platformHosts, true)) { wb_fallback(); } $uri = $_SERVER['REQUEST_URI'] ?? '/'; $path = parse_url($uri, PHP_URL_PATH) ?: '/'; $query = parse_url($uri, PHP_URL_QUERY); $isSpecial = (bool) preg_match('#^/(robots\.txt|sitemap[^/]*\.xml)$#', $path); $ch = curl_init($WB_SERVE . $path . ($query ? '?' . $query : '')); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_HEADER => true, CURLOPT_TIMEOUT => 12, // Les 301 vers le domaine primaire sont transmis au client, pas suivis : // suivre l'URL cible retomberait sur ce docroot (SPA), pas sur web-serve. CURLOPT_FOLLOWLOCATION => false, CURLOPT_HTTPHEADER => [ 'X-WB-Host: ' . $host, 'X-Original-Host: ' . $host, 'Accept: ' . ($_SERVER['HTTP_ACCEPT'] ?? '*/*'), 'Save-Data: ' . ($_SERVER['HTTP_SAVE_DATA'] ?? 'off'), ], ]); if (($_SERVER['REQUEST_METHOD'] ?? 'GET') === 'HEAD') { curl_setopt($ch, CURLOPT_NOBODY, true); } $raw = curl_exec($ch); $status = (int) curl_getinfo($ch, CURLINFO_RESPONSE_CODE); $headerSize = (int) curl_getinfo($ch, CURLINFO_HEADER_SIZE); curl_close($ch); if ($raw === false || $status === 0) { wb_fallback(); } $headers = substr($raw, 0, $headerSize); $body = substr($raw, $headerSize); $isWbSite = stripos($headers, 'x-web-site:') !== false; $isRedirect = in_array($status, [301, 302, 307, 308], true); // Décision : // - 200 → artefact (page compilée, robots, sitemap) : servir. // - 301/302 → redirection domaine primaire : transmettre. // - 404 + X-Web-Site → page absente d'un site existant : vraie 404. // - 403 → domaine non vérifié : page informative. // - 404 sans X-Web-Site sur robots/sitemap → hôte sans site : disallow-all // (évite l'indexation d'un sous-domaine vide). // - le reste → repli SPA. $serve = $status === 200 || $isRedirect || $status === 403 || ($status === 404 && $isWbSite); if (!$serve && $isSpecial && $status === 404) { header('Content-Type: ' . ($path === '/robots.txt' ? 'text/plain' : 'application/xml') . '; charset=utf-8'); http_response_code(404); echo $path === '/robots.txt' ? "User-agent: *\nDisallow: /\n" : ''; exit; } if (!$serve) { wb_fallback(); } http_response_code($status); foreach (explode("\r\n", $headers) as $line) { // Ne retransmettre que les en-têtes utiles (jamais ceux du transport interne). if (preg_match('/^(content-type|location|cache-control|x-web-|x-wb-|x-robots-tag)/i', $line)) { header($line); } } echo $body;