| undefined;
let sent = false;
let stopped = false;
const remaining = Math.max(0, Date.parse(ad.expiresAt) - Date.now());
const expiry = setTimeout(() => setHidden(true), remaining);
const send = async () => {
if (sent || stopped || document.visibilityState !== "visible") return;
sent = true;
try {
const r = await fetch(ad.tracking.impressionUrl, {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({
token: ad.tracking.token,
type: "impression",
}),
keepalive: true,
credentials: "omit",
referrerPolicy: "no-referrer",
});
if (!r.ok && !stopped) sent = false;
} catch {
if (!stopped) sent = false;
}
};
let visible = false;
const schedule = () => {
clearTimeout(timer);
if (visible && document.visibilityState === "visible" && !sent)
timer = setTimeout(() => {
void send();
}, 1000);
};
const observer =
typeof IntersectionObserver === "undefined"
? null
: new IntersectionObserver(
(entries) => {
visible = (entries[0]?.intersectionRatio ?? 0) >= 0.5;
schedule();
},
{ threshold: [0, 0.5] },
);
observer?.observe(el);
document.addEventListener("visibilitychange", schedule);
const stop = () => {
stopped = true;
clearTimeout(timer);
clearTimeout(expiry);
observer?.disconnect();
document.removeEventListener("visibilitychange", schedule);
};
cleanup.current = stop;
return stop;
}, [ad]);
let destination: string | undefined;
try {
const url = new URL(ad.tracking.destinationUrl ?? "");
if (url.protocol === "https:" && !url.username && !url.password)
destination = url.href;
} catch {}
const href = destination ?? ad.tracking.clickUrl;
const trackClick = () => {
if (!destination) return;
try {
const token = new URL(ad.tracking.clickUrl).searchParams.get("token");
if (token?.startsWith("c1."))
void fetch(ad.tracking.impressionUrl, {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ token, type: "click" }),
keepalive: true,
credentials: "omit",
referrerPolicy: "no-referrer",
}).catch(() => {});
} catch {
/* Tracking cannot interrupt native navigation. */
}
};
if (hidden || Date.parse(ad.expiresAt) <= Date.now()) return null;
return (
);
}