From 474f25007b6b5665c89bf0a3ecdb667e49bd8b31 Mon Sep 17 00:00:00 2001 From: Pierre-Olivier Mercier Date: Fri, 24 Oct 2025 17:20:35 +0700 Subject: [PATCH] Fix typescript/svelte checks --- .../lib/components/AuthenticationCard.svelte | 34 ++-- .../lib/components/BimiRecordDisplay.svelte | 4 +- web/src/lib/components/BlacklistCard.svelte | 4 +- .../lib/components/DkimRecordsDisplay.svelte | 4 +- .../lib/components/DmarcRecordDisplay.svelte | 4 +- web/src/lib/components/DnsRecordsCard.svelte | 4 +- web/src/lib/components/GradeDisplay.svelte | 2 +- .../lib/components/HeaderAnalysisCard.svelte | 6 +- .../lib/components/MxRecordsDisplay.svelte | 6 +- web/src/lib/components/PendingState.svelte | 2 + .../lib/components/SpamAssassinCard.svelte | 4 +- .../lib/components/SpfRecordsDisplay.svelte | 20 +- web/src/lib/components/SummaryCard.svelte | 182 ++++++++++-------- web/src/lib/hey-api.ts | 4 +- web/src/routes/+layout.svelte | 31 ++- web/src/routes/test/+page.ts | 5 +- web/src/routes/test/[test]/+page.svelte | 38 ++-- 17 files changed, 199 insertions(+), 155 deletions(-) diff --git a/web/src/lib/components/AuthenticationCard.svelte b/web/src/lib/components/AuthenticationCard.svelte index cf1b80f..b76b48a 100644 --- a/web/src/lib/components/AuthenticationCard.svelte +++ b/web/src/lib/components/AuthenticationCard.svelte @@ -1,13 +1,13 @@ @@ -58,13 +58,13 @@ {#if spf.all_qualifier}
All Mechanism Policy: - {#if spf.all_qualifier === '-'} + {#if spf.all_qualifier === "-"} Strict (-all) - {:else if spf.all_qualifier === '~'} + {:else if spf.all_qualifier === "~"} Softfail (~all) - {:else if spf.all_qualifier === '+'} + {:else if spf.all_qualifier === "+"} Pass (+all) - {:else if spf.all_qualifier === '?'} + {:else if spf.all_qualifier === "?"} Neutral (?all) {/if} {#if index === 0 || (index === 1 && spfRecords[0].record?.includes('redirect='))} diff --git a/web/src/lib/components/SummaryCard.svelte b/web/src/lib/components/SummaryCard.svelte index 971c1ac..9eb6272 100644 --- a/web/src/lib/components/SummaryCard.svelte +++ b/web/src/lib/components/SummaryCard.svelte @@ -5,8 +5,10 @@ interface TextSegment { text: string; highlight?: { - color: "good" | "warning" | "danger"; + color?: "good" | "warning" | "danger"; bold?: boolean; + emphasis?: boolean; + monospace?: boolean; }; link?: string; } @@ -22,19 +24,19 @@ // Email sender information const mailFrom = report.header_analysis?.headers?.from?.value || "an unknown sender"; - const hasDkim = report.authentication?.dkim && report.authentication.dkim.length > 0; - const dkimPassed = hasDkim && report.authentication.dkim.some(d => d.result === "pass"); + const hasDkim = report.authentication?.dkim && report.authentication?.dkim.length > 0; + const dkimPassed = hasDkim && report.authentication?.dkim?.some((d) => d.result === "pass"); segments.push({ text: "Received a " }); segments.push({ text: dkimPassed ? "DKIM-signed" : "non-DKIM-signed", highlight: { color: dkimPassed ? "good" : "danger", bold: true }, - link: "#authentication-dkim" + link: "#authentication-dkim", }); segments.push({ text: " email from " }); segments.push({ text: mailFrom, - highlight: { emphasis: true } + highlight: { emphasis: true }, }); // Server information and hops @@ -47,12 +49,12 @@ segments.push({ text: serverName, highlight: { monospace: true }, - link: "#header-details" + link: "#header-details", }); segments.push({ text: " after " }); segments.push({ - text: `${hopCount-1} hop${hopCount-1 !== 1 ? "s" : ""}`, - link: "#email-path" + text: `${hopCount - 1} hop${hopCount - 1 !== 1 ? "s" : ""}`, + link: "#email-path", }); } @@ -65,22 +67,25 @@ segments.push({ text: "authenticated", highlight: { color: "good", bold: true }, - link: "#authentication-details" + link: "#authentication-details", }); segments.push({ text: " to send email on behalf of " }); - segments.push({ text: report.header_analysis?.domain_alignment?.from_domain, highlight: {monospace: true} }); + segments.push({ + text: report.header_analysis?.domain_alignment?.from_domain || "unknown domain", + highlight: { monospace: true }, + }); } else if (spfResult && spfResult !== "none") { segments.push({ text: "not authenticated", highlight: { color: "danger", bold: true }, - link: "#authentication-spf" + link: "#authentication-spf", }); segments.push({ text: " (failed authentication checks)" }); } else { segments.push({ text: "not authenticated", highlight: { color: "warning", bold: true }, - link: "#authentication-details" + link: "#authentication-details", }); segments.push({ text: " (lacks proper authentication)" }); } @@ -92,21 +97,23 @@ segments.push({ text: "failed", highlight: { color: "danger", bold: true }, - link: "#authentication-spf" + link: "#authentication-spf", + }); + segments.push({ + text: ", the sending server is not authorized to send mail for this domain", }); - segments.push({ text: ", the sending server is not authorized to send mail for this domain" }); } else if (spfResult === "softfail") { segments.push({ text: "soft-failed", highlight: { color: "warning", bold: true }, - link: "#authentication-spf" + link: "#authentication-spf", }); segments.push({ text: ", the sending server may not be authorized" }); } else if (spfResult === "temperror" || spfResult === "permerror") { segments.push({ text: "encountered an error", highlight: { color: "warning", bold: true }, - link: "#authentication-spf" + link: "#authentication-spf", }); segments.push({ text: ", check your SPF record configuration" }); } else if (spfResult === "none") { @@ -114,9 +121,11 @@ segments.push({ text: "no SPF record", highlight: { color: "danger", bold: true }, - link: "#dns-spf" + link: "#dns-spf", + }); + segments.push({ + text: ", you should add one to specify which servers can send email on your behalf", }); - segments.push({ text: ", you should add one to specify which servers can send email on your behalf" }); } } @@ -129,13 +138,13 @@ segments.push({ text: "good", highlight: { color: "good", bold: true }, - link: "#dns-ptr" + link: "#dns-ptr", }); } else if (iprevResult.result === "fail") { segments.push({ text: "failed", highlight: { color: "danger", bold: true }, - link: "#dns-ptr" + link: "#dns-ptr", }); segments.push({ text: " to pass the test" }); } else { @@ -143,7 +152,7 @@ segments.push({ text: iprevResult.result, highlight: { color: "warning", bold: true }, - link: "#dns-ptr" + link: "#dns-ptr", }); } } @@ -152,20 +161,20 @@ const blacklists = report.blacklists; if (blacklists && Object.keys(blacklists).length > 0) { const allChecks = Object.values(blacklists).flat(); - const listedCount = allChecks.filter(check => check.listed).length; + const listedCount = allChecks.filter((check) => check.listed).length; segments.push({ text: ". Your server is " }); if (listedCount > 0) { segments.push({ text: `blacklisted on ${listedCount} list${listedCount !== 1 ? "s" : ""}`, highlight: { color: "danger", bold: true }, - link: "#rbl-details" + link: "#rbl-details", }); } else { segments.push({ text: "not blacklisted", highlight: { color: "good", bold: true }, - link: "#rbl-details" + link: "#rbl-details", }); } } @@ -178,7 +187,7 @@ segments.push({ text: "good", highlight: { color: "good", bold: true }, - link: "#domain-alignment" + link: "#domain-alignment", }); if (!domainAlignment.aligned) { segments.push({ text: " using organizational domain" }); @@ -187,17 +196,22 @@ segments.push({ text: "misaligned", highlight: { color: "danger", bold: true }, - link: "#domain-alignment" + link: "#domain-alignment", }); segments.push({ text: ": " }); segments.push({ text: "Return-Path", highlight: { monospace: true } }); segments.push({ text: " is set to an address of " }); - segments.push({ text: report.header_analysis?.domain_alignment?.return_path_domain, highlight: { monospace: true } }); + segments.push({ + text: + report.header_analysis?.domain_alignment?.return_path_domain || + "unknown domain", + highlight: { monospace: true }, + }); segments.push({ text: ", you should " }); segments.push({ text: "update it", highlight: { bold: true }, - link: "#domain-alignment" + link: "#domain-alignment", }); } } @@ -210,25 +224,28 @@ segments.push({ text: "don't have", highlight: { color: "danger", bold: true }, - link: "#dns-dmarc" + link: "#dns-dmarc", }); segments.push({ text: " a DMARC record, " }); - segments.push({ text: "consider adding at least a record with the '", highlight: { bold : true } }); + segments.push({ + text: "consider adding at least a record with the '", + highlight: { bold: true }, + }); segments.push({ text: "none", highlight: { monospace: true, bold: true } }); - segments.push({ text: "' policy", highlight: { bold : true } }); + segments.push({ text: "' policy", highlight: { bold: true } }); } else if (!dmarcRecord.valid) { segments.push({ text: ". Your DMARC record has " }); segments.push({ text: "issues", highlight: { color: "danger", bold: true }, - link: "#dns-dmarc" + link: "#dns-dmarc", }); } else if (dmarcRecord.policy === "none") { segments.push({ text: ". Your DMARC policy is " }); segments.push({ text: "set to 'none'", highlight: { color: "warning", bold: true }, - link: "#dns-dmarc" + link: "#dns-dmarc", }); segments.push({ text: ", which provides monitoring but no protection" }); } else if (dmarcRecord.policy === "quarantine" || dmarcRecord.policy === "reject") { @@ -236,7 +253,7 @@ segments.push({ text: dmarcRecord.policy, highlight: { color: "good", bold: true, monospace: true }, - link: "#dns-dmarc" + link: "#dns-dmarc", }); segments.push({ text: "'" }); if (dmarcRecord.policy === "reject") { @@ -247,17 +264,17 @@ segments.push({ text: "'" }); } } - } else if (dmarcResult && dmarcResult.result === "fail") { + } else if (dmarcResult === "fail") { segments.push({ text: ". DMARC check " }); segments.push({ text: "failed", highlight: { color: "danger", bold: true }, - link: "#authentication-dmarc" + link: "#authentication-dmarc", }); } // BIMI - if (dmarcRecord.valid && dmarcRecord.policy != "none") { + if (dmarcRecord && dmarcRecord.valid && dmarcRecord.policy != "none") { const bimiResult = report.authentication?.bimi; const bimiRecord = report.dns_results?.bimi_record; if (bimiRecord?.valid) { @@ -268,7 +285,7 @@ link: "#dns-bimi" }); segments.push({ text: " for brand indicator display" }); - } else if (bimiResult && bimiResult.details.indexOf("(No BIMI records found)") >= 0) { + } else if (bimiResult && bimiResult.details && bimiResult.details.indexOf("(No BIMI records found)") >= 0) { segments.push({ text: ". Your domain has no " }); segments.push({ text: "BIMI record", @@ -293,19 +310,21 @@ segments.push({ text: ". " }); segments.push({ text: "ARC chain validation", - link: "#authentication-arc" + link: "#authentication-arc", }); segments.push({ text: " " }); if (arcResult.chain_valid) { segments.push({ text: "passed", - highlight: { color: "good", bold: true } + highlight: { color: "good", bold: true }, + }); + segments.push({ + text: ` with ${arcResult.chain_length} set${arcResult.chain_length !== 1 ? "s" : ""}, indicating proper email forwarding`, }); - segments.push({ text: ` with ${arcResult.chain_length} set${arcResult.chain_length !== 1 ? "s" : ""}, indicating proper email forwarding` }); } else { segments.push({ text: "failed", - highlight: { color: "danger", bold: true } + highlight: { color: "danger", bold: true }, }); segments.push({ text: ", which may indicate issues with email forwarding" }); } @@ -316,20 +335,25 @@ const listUnsubscribe = headers?.["list-unsubscribe"]; const listUnsubscribePost = headers?.["list-unsubscribe-post"]; - const hasNewsletterHeaders = (listUnsubscribe?.importance === "newsletter" && listUnsubscribe?.present) || - (listUnsubscribePost?.importance === "newsletter" && listUnsubscribePost?.present); + const hasNewsletterHeaders = + (listUnsubscribe?.importance === "newsletter" && listUnsubscribe?.present) || + (listUnsubscribePost?.importance === "newsletter" && listUnsubscribePost?.present); - if (!hasNewsletterHeaders && (listUnsubscribe?.importance === "newsletter" || listUnsubscribePost?.importance === "newsletter")) { + if ( + !hasNewsletterHeaders && + (listUnsubscribe?.importance === "newsletter" || + listUnsubscribePost?.importance === "newsletter") + ) { segments.push({ text: ". This email is " }); segments.push({ text: "missing unsubscribe headers", highlight: { color: "warning", bold: true }, - link: "#header-details" + link: "#header-details", }); segments.push({ text: " and is " }); segments.push({ text: "not suitable for marketing campaigns", - highlight: { bold: true } + highlight: { bold: true }, }); } @@ -344,7 +368,7 @@ segments.push({ text: "flagged as spam", highlight: { color: "danger", bold: true }, - link: "#spam-details" + link: "#spam-details", }); segments.push({ text: " and needs review" }); } else if (contentScore < 50) { @@ -352,49 +376,55 @@ segments.push({ text: "needs improvement", highlight: { color: "warning", bold: true }, - link: "#content-details" + link: "#content-details", }); } else if (contentScore >= 100 && spamScore >= 100) { segments.push({ text: "Content " }); segments.push({ text: "looks great", highlight: { color: "good", bold: true }, - link: "#content-details" + link: "#content-details", }); } else if (spamScore < 50) { segments.push({ text: "Your " }); segments.push({ text: "spam score", highlight: { color: "danger", bold: true }, - link: "#spam-details" + link: "#spam-details", }); segments.push({ text: " is low" }); - if (report.spamassassin.tests.includes("EMPTY_MESSAGE")) { - segments.push({ text: " (you sent an empty message, which can cause this issue, retry with some real content)", highlight: { bold: true } }); + if (report.spamassassin?.tests?.includes("EMPTY_MESSAGE")) { + segments.push({ + text: " (you sent an empty message, which can cause this issue, retry with some real content)", + highlight: { bold: true }, + }); } } else if (spamScore < 90) { segments.push({ text: "Pay attention to your " }); segments.push({ text: "spam score", highlight: { color: "warning", bold: true }, - link: "#spam-details" + link: "#spam-details", }); - if (report.spamassassin.tests.includes("EMPTY_MESSAGE")) { - segments.push({ text: " (you sent an empty message, which can cause this issue, retry with some real content)", highlight: { bold: true } }); + if (report.spamassassin?.tests?.includes("EMPTY_MESSAGE")) { + segments.push({ + text: " (you sent an empty message, which can cause this issue, retry with some real content)", + highlight: { bold: true }, + }); } } else if (contentScore >= 80) { segments.push({ text: "Content " }); segments.push({ text: "looks good", highlight: { color: "good", bold: true }, - link: "#content-details" + link: "#content-details", }); } else { segments.push({ text: "Content " }); segments.push({ text: "should be reviewed", highlight: { color: "warning", bold: true }, - link: "#content-details" + link: "#content-details", }); } @@ -403,7 +433,7 @@ return segments; } - function getColorClass(color: "good" | "warning" | "danger"): string { + function getColorClass(color?: "good" | "warning" | "danger"): string { switch (color) { case "good": return "text-success"; @@ -411,28 +441,14 @@ return "text-warning"; case "danger": return "text-danger"; + default: + return ""; } } const summarySegments = $derived(buildSummary()); - -
@@ -460,3 +476,19 @@

+ + diff --git a/web/src/lib/hey-api.ts b/web/src/lib/hey-api.ts index e75e70a..6983e5d 100644 --- a/web/src/lib/hey-api.ts +++ b/web/src/lib/hey-api.ts @@ -7,8 +7,8 @@ export class NotAuthorizedError extends Error { } } -async function customFetch(url: string, init: RequestInit): Promise { - const response = await fetch(url, init); +async function customFetch(input: RequestInfo | URL, init?: RequestInit): Promise { + const response = await fetch(input, init); if (response.status === 400) { const json = await response.json(); diff --git a/web/src/routes/+layout.svelte b/web/src/routes/+layout.svelte index f0031bb..35cf00e 100644 --- a/web/src/routes/+layout.svelte +++ b/web/src/routes/+layout.svelte @@ -38,7 +38,12 @@
diff --git a/web/src/routes/test/+page.ts b/web/src/routes/test/+page.ts index d2f88f2..8f8fd5b 100644 --- a/web/src/routes/test/+page.ts +++ b/web/src/routes/test/+page.ts @@ -10,10 +10,11 @@ export const load: Load = async ({}) => { try { response = await apiCreateTest(); } catch (err) { - error(err.response.status, err.message); + const errorObj = err as { response?: { status?: number }; message?: string }; + error(errorObj.response?.status || 500, errorObj.message || "Unknown error"); } - if (response.response.ok) { + if (response.response.ok && response.data) { redirect(302, `/test/${response.data.id}`); } else { error(response.response.status, response.error); diff --git a/web/src/routes/test/[test]/+page.svelte b/web/src/routes/test/[test]/+page.svelte index 8ac67eb..7ef2b63 100644 --- a/web/src/routes/test/[test]/+page.svelte +++ b/web/src/routes/test/[test]/+page.svelte @@ -28,6 +28,8 @@ let fetching = $state(false); async function fetchTest() { + if (!testId) return; + if (nbfetch > 0) { nextfetch = Math.max(nextfetch, Math.floor(3 + nbfetch * 0.5)); } @@ -89,8 +91,8 @@ } } - let lastTestId = null; - function testChange(newTestId) { + let lastTestId: string | null = null; + function testChange(newTestId: string) { if (lastTestId != newTestId) { lastTestId = newTestId; test = null; @@ -100,7 +102,10 @@ } $effect(() => { - testChange(page.params.test); + const newTestId = page.params.test; + if (newTestId) { + testChange(newTestId); + } }) onDestroy(() => { @@ -128,9 +133,9 @@ function handleExportJSON() { const dataStr = JSON.stringify(report, null, 2); - const dataBlob = new Blob([dataStr], { type: 'application/json' }); + const dataBlob = new Blob([dataStr], { type: "application/json" }); const url = URL.createObjectURL(dataBlob); - const link = document.createElement('a'); + const link = document.createElement("a"); link.href = url; link.download = `report-${testId}.json`; link.click(); @@ -140,7 +145,7 @@ - {report ? `Test of ${report.dns_results.from_domain} ${report.test_id.slice(0, 7)}` : (test ? `Test ${test.id.slice(0, 7)}` : "Loading...")} - happyDeliver + {report ? `Test${report.dns_results ? ` of ${report.dns_results.from_domain}` : ''} ${report.test_id?.slice(0, 7) || ''}` : (test ? `Test ${test.id.slice(0, 7)}` : "Loading...")} - happyDeliver
@@ -283,11 +288,11 @@ @@ -348,23 +353,6 @@ } } - .category-section { - margin-bottom: 2rem; - } - - .category-title { - font-size: 1.25rem; - font-weight: 600; - color: #495057; - padding-bottom: 0.5rem; - border-bottom: 2px solid #e9ecef; - } - - .category-score { - font-size: 1rem; - font-weight: 700; - } - .menu-container { position: relative; }