From 474f25007b6b5665c89bf0a3ecdb667e49bd8b31 Mon Sep 17 00:00:00 2001
From: Pierre-Olivier Mercier
Date: Fri, 24 Oct 2025 17:20:35 +0700
Subject: [PATCH 01/13] 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());
-
-
+
+
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;
}
--
2.52.0
From 82f21abcfff135fe77c920b7c42f413252e420a6 Mon Sep 17 00:00:00 2001
From: Pierre-Olivier Mercier
Date: Fri, 24 Oct 2025 17:35:58 +0700
Subject: [PATCH 02/13] Update features on home page
---
web/routes.go | 4 +++
web/src/lib/config.ts | 48 ++++++++++++++++++++++++++++
web/src/routes/+page.svelte | 62 +++++++++++++++++++++++++++++--------
3 files changed, 101 insertions(+), 13 deletions(-)
create mode 100644 web/src/lib/config.ts
diff --git a/web/routes.go b/web/routes.go
index f67b453..184da64 100644
--- a/web/routes.go
+++ b/web/routes.go
@@ -54,6 +54,10 @@ func init() {
func DeclareRoutes(cfg *config.Config, router *gin.Engine) {
appConfig := map[string]interface{}{}
+ if cfg.ReportRetention > 0 {
+ appConfig["report_retention"] = cfg.ReportRetention
+ }
+
if appcfg, err := json.MarshalIndent(appConfig, "", " "); err != nil {
log.Println("Unable to generate JSON config to inject in web application")
} else {
diff --git a/web/src/lib/config.ts b/web/src/lib/config.ts
new file mode 100644
index 0000000..65eb1bb
--- /dev/null
+++ b/web/src/lib/config.ts
@@ -0,0 +1,48 @@
+// This file is part of the happyDeliver (R) project.
+// Copyright (c) 2025 happyDomain
+// Authors: Pierre-Olivier Mercier, et al.
+//
+// This program is offered under a commercial and under the AGPL license.
+// For commercial licensing, contact us at .
+//
+// For AGPL licensing:
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+import { writable } from "svelte/store";
+
+interface AppConfig {
+ report_retention?: number;
+}
+
+const defaultConfig: AppConfig = {
+ report_retention: 0,
+};
+
+function getConfigFromScriptTag(): AppConfig | null {
+ if (typeof document !== "undefined") {
+ const configScript = document.getElementById("app-config");
+ if (configScript) {
+ try {
+ return JSON.parse(configScript.textContent || "");
+ } catch (e) {
+ console.error("Failed to parse app config:", e);
+ }
+ }
+ }
+ return null;
+}
+
+const initialConfig = getConfigFromScriptTag() || defaultConfig;
+
+export const appConfig = writable(initialConfig);
diff --git a/web/src/routes/+page.svelte b/web/src/routes/+page.svelte
index ecfbbdd..8783582 100644
--- a/web/src/routes/+page.svelte
+++ b/web/src/routes/+page.svelte
@@ -1,6 +1,7 @@
+
+{#if $appConfig.surveyUrl}
+
+{/if}
diff --git a/web/src/lib/components/index.ts b/web/src/lib/components/index.ts
index 8b83ae5..e600c11 100644
--- a/web/src/lib/components/index.ts
+++ b/web/src/lib/components/index.ts
@@ -13,3 +13,4 @@ export { default as ContentAnalysisCard } from "./ContentAnalysisCard.svelte";
export { default as HeaderAnalysisCard } from "./HeaderAnalysisCard.svelte";
export { default as PtrRecordsDisplay } from "./PtrRecordsDisplay.svelte";
export { default as PtrForwardRecordsDisplay } from "./PtrForwardRecordsDisplay.svelte";
+export { default as TinySurvey } from "./TinySurvey.svelte";
diff --git a/web/src/lib/config.ts b/web/src/lib/config.ts
index 65eb1bb..c4c0bd4 100644
--- a/web/src/lib/config.ts
+++ b/web/src/lib/config.ts
@@ -23,10 +23,12 @@ import { writable } from "svelte/store";
interface AppConfig {
report_retention?: number;
+ surveyUrl?: string;
}
const defaultConfig: AppConfig = {
report_retention: 0,
+ surveyUrl: "",
};
function getConfigFromScriptTag(): AppConfig | null {
diff --git a/web/src/routes/test/[test]/+page.svelte b/web/src/routes/test/[test]/+page.svelte
index 7ef2b63..7f50923 100644
--- a/web/src/routes/test/[test]/+page.svelte
+++ b/web/src/routes/test/[test]/+page.svelte
@@ -12,7 +12,8 @@
DnsRecordsCard,
BlacklistCard,
ContentAnalysisCard,
- HeaderAnalysisCard
+ HeaderAnalysisCard,
+ TinySurvey,
} from "$lib/components";
let testId = $derived(page.params.test);
@@ -236,7 +237,11 @@
--
2.52.0
From 8cb13b912fe60cec0ef53bf9755b5d8d6eb370a1 Mon Sep 17 00:00:00 2001
From: Pierre-Olivier Mercier
Date: Fri, 24 Oct 2025 18:27:51 +0700
Subject: [PATCH 05/13] Handle errors on test page
---
web/src/lib/components/ErrorDisplay.svelte | 158 +++++++++++++++++++++
web/src/lib/components/index.ts | 1 +
web/src/routes/+error.svelte | 126 +---------------
web/src/routes/test/[test]/+page.svelte | 57 ++++++--
4 files changed, 209 insertions(+), 133 deletions(-)
create mode 100644 web/src/lib/components/ErrorDisplay.svelte
diff --git a/web/src/lib/components/ErrorDisplay.svelte b/web/src/lib/components/ErrorDisplay.svelte
new file mode 100644
index 0000000..96cfae2
--- /dev/null
+++ b/web/src/lib/components/ErrorDisplay.svelte
@@ -0,0 +1,158 @@
+
+
+
+
+
+
+
+
+
+
+
{status}
+
+
+
{getErrorTitle(status)}
+
+
+
{getErrorDescription(status)}
+
+
+ {#if message && message !== defaultDescription}
+
+
+ {message}
+
+ {/if}
+
+
+ {#if showActions}
+
+
+
+ Go Home
+
+
window.history.back()}
+ >
+
+ Go Back
+
+
+ {/if}
+
+
+ {#if status === 404 && showActions}
+
+
Looking for something specific?
+
+
+ {/if}
+
+
+
+
diff --git a/web/src/lib/components/index.ts b/web/src/lib/components/index.ts
index e600c11..dadab9e 100644
--- a/web/src/lib/components/index.ts
+++ b/web/src/lib/components/index.ts
@@ -14,3 +14,4 @@ export { default as HeaderAnalysisCard } from "./HeaderAnalysisCard.svelte";
export { default as PtrRecordsDisplay } from "./PtrRecordsDisplay.svelte";
export { default as PtrForwardRecordsDisplay } from "./PtrForwardRecordsDisplay.svelte";
export { default as TinySurvey } from "./TinySurvey.svelte";
+export { default as ErrorDisplay } from "./ErrorDisplay.svelte";
diff --git a/web/src/routes/+error.svelte b/web/src/routes/+error.svelte
index 5d0514c..a429ea5 100644
--- a/web/src/routes/+error.svelte
+++ b/web/src/routes/+error.svelte
@@ -1,5 +1,6 @@
@@ -55,96 +28,5 @@
-
-
-
-
-
-
-
-
-
{status}
-
-
-
{getErrorTitle(status)}
-
-
-
{getErrorDescription(status)}
-
-
- {#if message !== getErrorDescription(status)}
-
-
- {message}
-
- {/if}
-
-
-
-
-
- Go Home
-
-
window.history.back()}
- >
-
- Go Back
-
-
-
-
- {#if status === 404}
-
-
Looking for something specific?
-
-
- {/if}
-
-
+
-
-
diff --git a/web/src/routes/test/[test]/+page.svelte b/web/src/routes/test/[test]/+page.svelte
index 7f50923..c8b5cc0 100644
--- a/web/src/routes/test/[test]/+page.svelte
+++ b/web/src/routes/test/[test]/+page.svelte
@@ -14,6 +14,7 @@
ContentAnalysisCard,
HeaderAnalysisCard,
TinySurvey,
+ ErrorDisplay,
} from "$lib/components";
let testId = $derived(page.params.test);
@@ -21,6 +22,7 @@
let report = $state(null);
let loading = $state(true);
let error = $state(null);
+ let errorStatus = $state(500);
let reanalyzing = $state(false);
let pollInterval: ReturnType | null = null;
let nextfetch = $state(23);
@@ -28,6 +30,36 @@
let menuOpen = $state(false);
let fetching = $state(false);
+ // Helper function to handle API errors
+ function handleApiError(apiError: unknown, defaultMessage: string) {
+ if (apiError && typeof apiError === "object") {
+ if ("message" in apiError) {
+ error = String(apiError.message);
+ } else {
+ error = defaultMessage;
+ }
+
+ // Determine status code based on error type
+ if ("error" in apiError) {
+ if (apiError.error === "rate_limit_exceeded") {
+ errorStatus = 429;
+ } else if (apiError.error === "not_found") {
+ errorStatus = 404;
+ } else {
+ errorStatus = 500;
+ }
+ } else {
+ errorStatus = 500;
+ }
+ } else if (apiError instanceof Error) {
+ error = apiError.message;
+ errorStatus = 500;
+ } else {
+ error = defaultMessage;
+ errorStatus = 500;
+ }
+ }
+
async function fetchTest() {
if (!testId) return;
@@ -36,6 +68,9 @@
}
nbfetch += 1;
+ // Clear any previous errors
+ error = null;
+
// Set fetching state and ensure minimum 500ms display time
fetching = true;
const startTime = Date.now();
@@ -52,10 +87,15 @@
}
stopPolling();
}
+ } else if (testResponse.error) {
+ handleApiError(testResponse.error, "Failed to fetch test");
+ loading = false;
+ stopPolling();
+ return;
}
loading = false;
} catch (err) {
- error = err instanceof Error ? err.message : "Failed to fetch test";
+ handleApiError(err, "Failed to fetch test");
loading = false;
stopPolling();
} finally {
@@ -107,7 +147,7 @@
if (newTestId) {
testChange(newTestId);
}
- })
+ });
onDestroy(() => {
stopPolling();
@@ -124,9 +164,11 @@
const response = await reanalyzeReport({ path: { id: testId } });
if (response.data) {
report = response.data;
+ } else if (response.error) {
+ handleApiError(response.error, "Failed to reanalyze report");
}
} catch (err) {
- error = err instanceof Error ? err.message : "Failed to reanalyze report";
+ handleApiError(err, "Failed to reanalyze report");
} finally {
reanalyzing = false;
}
@@ -162,14 +204,7 @@
Loading test...
{:else if error}
-
+
{:else if test && test.status !== "analyzed"}
Date: Fri, 24 Oct 2025 18:43:55 +0700
Subject: [PATCH 06/13] Add a background report sample in hero on home page
Why use a report from icloud.com to illustrate this project?
Among all the common email providers I tested, it achieved the best results.
---
web/src/routes/+page.svelte | 41 ++++++++++++++++++++++++++++++++++--
web/static/img/report.webp | Bin 0 -> 86668 bytes
2 files changed, 39 insertions(+), 2 deletions(-)
create mode 100644 web/static/img/report.webp
diff --git a/web/src/routes/+page.svelte b/web/src/routes/+page.svelte
index 8783582..f26f8e2 100644
--- a/web/src/routes/+page.svelte
+++ b/web/src/routes/+page.svelte
@@ -151,7 +151,7 @@
and more. Open-source, self-hosted, and privacy-focused.
@@ -238,10 +238,20 @@
diff --git a/web/static/img/report.webp b/web/static/img/report.webp
new file mode 100644
index 0000000000000000000000000000000000000000..97c3b8c416e2cbf4158a26f81f196d51e9fd3d23
GIT binary patch
literal 86668
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