ads: switch to centralized RMP banner source; impression beacon → advertising.rmp; rearm viewport observer (12s debounce) for re-entry counting
This commit is contained in:
@@ -13,6 +13,7 @@ import { createClient } from "@supabase/supabase-js";
|
||||
|
||||
export interface Ad {
|
||||
slug?: string;
|
||||
campaign_id?: string;
|
||||
label: string;
|
||||
size: "300x250" | "300x600" | "728x90";
|
||||
src: string;
|
||||
@@ -70,7 +71,65 @@ interface DbRow {
|
||||
size: "300x250" | "300x600" | "728x90";
|
||||
}
|
||||
|
||||
// Centralized banner source: advertising.relevantmediaproperties.com
|
||||
// serves /api/banners/<property>/<placement> with CORS + 5min cache.
|
||||
// We fetch all three sizes once and merge.
|
||||
const RMP_API = process.env.NEXT_PUBLIC_RMP_ADS_API
|
||||
|| "https://advertising.relevantmediaproperties.com";
|
||||
const PROPERTY = process.env.NEXT_PUBLIC_RMP_PROPERTY || "broadcastbeat";
|
||||
|
||||
interface RmpBanner {
|
||||
campaign_id: string;
|
||||
name: string;
|
||||
image_url: string;
|
||||
image_webp_url?: string | null;
|
||||
click_url: string;
|
||||
width: number;
|
||||
height: number;
|
||||
placement: "300x250" | "300x600" | "728x90";
|
||||
property: string;
|
||||
slug?: string;
|
||||
}
|
||||
|
||||
async function fetchPlacement(size: Ad["size"]): Promise<Ad[]> {
|
||||
try {
|
||||
const res = await fetch(`${RMP_API}/api/banners/${PROPERTY}/${size}`, {
|
||||
next: { revalidate: 300 },
|
||||
});
|
||||
if (!res.ok) return [];
|
||||
const data = await res.json() as { banners?: RmpBanner[] };
|
||||
return (data.banners || []).map((b): Ad => ({
|
||||
// Prefer the human-readable slug for the centralized click URL; fall back
|
||||
// to the campaign UUID if the slug column ever ends up empty.
|
||||
slug: b.slug || b.campaign_id,
|
||||
campaign_id: b.campaign_id,
|
||||
label: b.name,
|
||||
size: b.placement,
|
||||
src: b.image_url,
|
||||
alt: b.name,
|
||||
click_url: `https://www.relevantmediaproperties.com/${PROPERTY}/${b.slug || b.campaign_id}`,
|
||||
}));
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
async function loadFromDb(): Promise<Ad[]> {
|
||||
// 1) Try centralized RMP source first (preferred path).
|
||||
try {
|
||||
const [a, b, c] = await Promise.all([
|
||||
fetchPlacement("300x250"),
|
||||
fetchPlacement("300x600"),
|
||||
fetchPlacement("728x90"),
|
||||
]);
|
||||
const all = [...a, ...b, ...c];
|
||||
if (all.length > 0) return all;
|
||||
} catch {
|
||||
/* fall through */
|
||||
}
|
||||
|
||||
// 2) Legacy fallback: query BB's own bb.banner_creatives (kept until the
|
||||
// centralized RMP source is fully verified in prod).
|
||||
if (!SUPABASE_URL || !SUPABASE_ANON) return FALLBACK;
|
||||
try {
|
||||
const sb = createClient(SUPABASE_URL, SUPABASE_ANON, {
|
||||
@@ -84,12 +143,7 @@ async function loadFromDb(): Promise<Ad[]> {
|
||||
.eq("status", "active")
|
||||
.or(`start_date.is.null,start_date.lte.${nowIso}`)
|
||||
.or(`end_date.is.null,end_date.gte.${nowIso}`);
|
||||
if (error || !data || data.length === 0) {
|
||||
if (typeof console !== "undefined") {
|
||||
console.warn("[ads] db query empty/error, using fallback:", error?.message || "no rows");
|
||||
}
|
||||
return FALLBACK;
|
||||
}
|
||||
if (error || !data || data.length === 0) return FALLBACK;
|
||||
return (data as DbRow[]).map((r) => ({
|
||||
slug: r.slug,
|
||||
label: r.name,
|
||||
@@ -98,10 +152,7 @@ async function loadFromDb(): Promise<Ad[]> {
|
||||
alt: r.name,
|
||||
click_url: r.click_url || undefined,
|
||||
}));
|
||||
} catch (err: any) {
|
||||
if (typeof console !== "undefined") {
|
||||
console.warn("[ads] db query threw, using fallback:", err?.message);
|
||||
}
|
||||
} catch {
|
||||
return FALLBACK;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user