(baseUrl)
| 711 | const domainCache = new Map(); |
| 712 | |
| 713 | async function getDomainBaseline(baseUrl) { |
| 714 | if (domainCache.has(baseUrl)) { |
| 715 | const cached = domainCache.get(baseUrl); |
| 716 | if (Date.now() - cached.timestamp < 300000) { // 5 min cache |
| 717 | return cached; |
| 718 | } |
| 719 | } |
| 720 | |
| 721 | try { |
| 722 | // Get baseline response |
| 723 | const baseResponse = await rateLimitedFetch(baseUrl); |
| 724 | const baseText = await baseResponse.text(); |
| 725 | |
| 726 | // Get soft-404 fingerprint (request a random non-existent path) |
| 727 | const randomPath = `/${Math.random().toString(36).substring(7)}-${Date.now()}`; |
| 728 | let soft404Fingerprint = null; |
| 729 | let soft404Length = 0; |
| 730 | |
| 731 | try { |
| 732 | const soft404Response = await rateLimitedFetch(baseUrl + randomPath); |
| 733 | const soft404Text = await soft404Response.text(); |
| 734 | soft404Length = soft404Text.length; |
| 735 | // Create a fingerprint based on content structure, not exact content |
| 736 | soft404Fingerprint = { |
| 737 | status: soft404Response.status, |
| 738 | length: soft404Text.length, |
| 739 | hasTitle: /<title>/i.test(soft404Text), |
| 740 | isSoft404: isSoft404(soft404Text), |
| 741 | }; |
| 742 | } catch (e) { |
| 743 | // Couldn't get soft-404, that's fine |
| 744 | } |
| 745 | |
| 746 | const cached = { |
| 747 | baseText, |
| 748 | baseLength: baseText.length, |
| 749 | soft404Fingerprint, |
| 750 | soft404Length, |
| 751 | timestamp: Date.now(), |
| 752 | }; |
| 753 | |
| 754 | domainCache.set(baseUrl, cached); |
| 755 | return cached; |
| 756 | } catch (e) { |
| 757 | return null; |
| 758 | } |
| 759 | } |
| 760 | |
| 761 | function matchesSoft404(response, text, fingerprint) { |
| 762 | if (!fingerprint) return false; |
no test coverage detected