import useSWRMutation from "swr/mutation";
import useSWRImmutable from "swr/immutable";
import { OBSERVATORY_API_URL } from "../env";
import { ObservatoryResult } from "./types";
import { ReactComponent as PassSVG } from "../../public/assets/observatory/pass-icon.svg";
import { ReactComponent as FailSVG } from "../../public/assets/observatory/fail-icon.svg";
import { HumanDuration } from "./results/human-duration";
export function Link({ href, children }: { href: string; children: any }) {
return (
{children}
);
}
export function PassIcon({ pass }: { pass: boolean | null }) {
if (pass === null) {
return <>->;
}
return (
{pass ? : }
{pass ? "Passed" : "Failed"}
);
}
export function FeedbackLink() {
return (
// eslint-disable-next-line react/jsx-no-target-blank
Report Feedback
);
}
export function FaqLink() {
return (
// eslint-disable-next-line react/jsx-no-target-blank
Read our FAQ
);
}
export const ERROR_MAP = {
TypeError: "Observatory is currently down.", // `fetch()` errors catch-all
};
export function formatMinus(term: string | null | undefined) {
if (!term) {
return null;
}
// replace dash with unicode minus symbol
// −
// MINUS SIGN
// Unicode: U+2212, UTF-8: E2 88 92
return `${term}`.replaceAll(/-/g, "−");
}
export function useUpdateResult(host: string) {
return useSWRMutation(
host,
async (key: string) => {
const url = new URL(OBSERVATORY_API_URL + "/api/v2/analyze");
url.searchParams.set("host", key);
const res = await fetch(url, {
method: "POST",
});
return await handleJsonResponse(res);
},
{ populateCache: true, throwOnError: false }
);
}
export function useResult(host?: string) {
return useSWRImmutable(host, async (key) => {
const url = new URL(OBSERVATORY_API_URL + "/api/v2/analyze");
url.searchParams.set("host", key);
const res = await fetch(url);
return await handleJsonResponse(res);
});
}
export async function handleJsonResponse(res: Response): Promise {
if (!res.ok && res.status !== 429) {
// Example error payload we get from the Observatory API:
// {
// "statusCode": 422,
// "error": "invalid-hostname-lookup",
// "message": "unknownhostmcunknownhostface.mozilla.org cannot be resolved"
// }
// We convey the `message` to the user and use the `error` field for glean telemetry.
let message = `${res.status}: ${res.statusText}`;
let errName = "Error";
try {
const data = await res.json();
errName = data.error || errName;
message = data.message || message;
} finally {
const err = new Error(message);
err.name = errName;
throw err;
}
}
return await res.json();
}
export function Timestamp({ expires }: { expires: string }) {
const d = new Date(expires);
if (d.toString() === "Invalid Date") {
return {expires}
;
}
const ts = d
.toISOString()
.replace("T", " ")
.replace(/\....Z/, " UTC");
return (
<>
{ts}
()
>
);
}
export function formatDateTime(date: Date): string {
return date.toLocaleString([], {
dateStyle: "medium",
timeStyle: "medium",
});
}
export function hostAsRedirectChain(host: string, result: ObservatoryResult) {
const chain = result.tests.redirection?.route;
if (!chain || chain.length < 1) {
return host;
}
try {
const firstUrl = new URL(chain[0]);
const lastUrl = new URL(chain[chain.length - 1]);
if (firstUrl.hostname === lastUrl.hostname) {
return host;
}
return `${firstUrl.hostname} → ${lastUrl.hostname}`;
} catch (e) {
return host;
}
}