@adland/react 0.16.2 → 0.16.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +20 -16
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +20 -16
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -82,9 +82,15 @@ function createReadClient(chainId, rpcUrl) {
|
|
|
82
82
|
});
|
|
83
83
|
return new import_sdk.SlotsClient({ chainId, publicClient });
|
|
84
84
|
}
|
|
85
|
+
function extractCid(uri) {
|
|
86
|
+
if (uri.startsWith("ipfs://")) return uri.slice(7);
|
|
87
|
+
if (uri.startsWith("Qm") || uri.startsWith("bafy")) return uri;
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
85
90
|
var fetchAdFromURI = async (uri) => {
|
|
86
91
|
if (!uri) throw new Error("NO_AD" /* NO_AD */);
|
|
87
|
-
const
|
|
92
|
+
const cid = extractCid(uri);
|
|
93
|
+
const url = cid ? `${IPFS_GATEWAY}${cid}` : uri;
|
|
88
94
|
const res = await fetch(url, {
|
|
89
95
|
method: "GET",
|
|
90
96
|
headers: { Accept: "application/json" }
|
|
@@ -95,7 +101,7 @@ var fetchAdFromURI = async (uri) => {
|
|
|
95
101
|
}
|
|
96
102
|
const data = await res.json();
|
|
97
103
|
if (data.error) throw new Error(data.error);
|
|
98
|
-
return data;
|
|
104
|
+
return { data, cid };
|
|
99
105
|
};
|
|
100
106
|
var fetchMetadataURI = async (client, slotAddress) => {
|
|
101
107
|
const info = await client.getSlotInfo(slotAddress);
|
|
@@ -421,15 +427,14 @@ async function sendEvent(eventName, data) {
|
|
|
421
427
|
}
|
|
422
428
|
function trackImpression(element, data) {
|
|
423
429
|
if (!element || typeof window === "undefined") return;
|
|
424
|
-
const
|
|
425
|
-
const key = `${eventName}:${data.slot}:${window.location.href}`;
|
|
430
|
+
const key = `impression:${data.slot}:${window.location.href}`;
|
|
426
431
|
if (tracked.has(key)) return;
|
|
427
432
|
const observer = new IntersectionObserver(
|
|
428
433
|
(entries) => {
|
|
429
434
|
for (const entry of entries) {
|
|
430
435
|
if (entry.isIntersecting && !tracked.has(key)) {
|
|
431
436
|
tracked.add(key);
|
|
432
|
-
sendEvent(
|
|
437
|
+
sendEvent("impression", data);
|
|
433
438
|
observer.disconnect();
|
|
434
439
|
}
|
|
435
440
|
}
|
|
@@ -462,7 +467,7 @@ function Ad({
|
|
|
462
467
|
[slot, chainId, rpcUrl]
|
|
463
468
|
);
|
|
464
469
|
const {
|
|
465
|
-
data:
|
|
470
|
+
data: fetchResult,
|
|
466
471
|
isLoading,
|
|
467
472
|
error
|
|
468
473
|
} = useFetch(
|
|
@@ -478,42 +483,41 @@ function Ad({
|
|
|
478
483
|
},
|
|
479
484
|
{ enabled: !!slot && !staticData }
|
|
480
485
|
);
|
|
481
|
-
const adData = staticData ??
|
|
486
|
+
const adData = staticData ?? fetchResult?.data ?? null;
|
|
487
|
+
const cid = fetchResult?.cid ?? null;
|
|
482
488
|
const isEmpty = !adData && !isLoading && (error instanceof Error ? error.message === "NO_AD" /* NO_AD */ : !error);
|
|
483
489
|
(0, import_react3.useEffect)(() => {
|
|
484
|
-
if (!
|
|
485
|
-
return trackImpression(ref.current, { slot, chainId, auth, context });
|
|
486
|
-
}, [adData, slot, chainId, auth, context]);
|
|
487
|
-
(0, import_react3.useEffect)(() => {
|
|
488
|
-
if (!isEmpty || !slot) return;
|
|
490
|
+
if (!slot || !adData && !isEmpty) return;
|
|
489
491
|
return trackImpression(ref.current, {
|
|
490
492
|
slot,
|
|
491
493
|
chainId,
|
|
492
494
|
auth,
|
|
493
495
|
context,
|
|
494
|
-
|
|
496
|
+
cid,
|
|
497
|
+
empty: isEmpty
|
|
495
498
|
});
|
|
496
|
-
}, [isEmpty, slot, chainId, auth, context]);
|
|
499
|
+
}, [adData, isEmpty, slot, chainId, auth, context, cid]);
|
|
497
500
|
const onClick = (0, import_react3.useCallback)(
|
|
498
501
|
(e) => {
|
|
499
502
|
const target = e.target;
|
|
500
503
|
const isInteractive = target.tagName === "A" || target.tagName === "BUTTON" || target.closest("a") !== null || target.closest("button") !== null;
|
|
501
504
|
if (isInteractive) return;
|
|
502
505
|
if (adData) {
|
|
503
|
-
if (slot) trackClick("click", { slot, chainId, auth, context });
|
|
506
|
+
if (slot) trackClick("click", { slot, chainId, auth, context, cid });
|
|
504
507
|
performAdAction(adData);
|
|
505
508
|
} else if (isEmpty && slot) {
|
|
506
509
|
trackClick("click-empty", { slot, chainId, auth, context });
|
|
507
510
|
performEmptyAdAction(slot, chainId, baseLinkUrl);
|
|
508
511
|
}
|
|
509
512
|
},
|
|
510
|
-
[adData, isEmpty, slot, chainId, baseLinkUrl]
|
|
513
|
+
[adData, isEmpty, slot, chainId, baseLinkUrl, cid]
|
|
511
514
|
);
|
|
512
515
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
513
516
|
AdContext.Provider,
|
|
514
517
|
{
|
|
515
518
|
value: {
|
|
516
519
|
data: adData ?? null,
|
|
520
|
+
cid,
|
|
517
521
|
isLoading: !!slot && !staticData && isLoading,
|
|
518
522
|
error,
|
|
519
523
|
isEmpty,
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/components/Ad.tsx","../src/fetch.ts","../src/types.ts","../src/hooks/useAdContext.ts","../src/hooks/useFetch.ts","../src/utils/fetchCache.ts","../src/utils/ad-actions.ts","../src/utils/ad-fields.ts","../src/utils/constants.ts","../src/utils/tracking.ts"],"sourcesContent":["// Compound Ad components\nexport {\n Ad,\n AdBadge,\n AdDescription,\n AdEmpty,\n AdError,\n AdImage,\n AdLabel,\n AdLoaded,\n AdLoading,\n AdTitle,\n} from \"./components/Ad\";\n\nexport type {\n AdBadgeProps,\n AdDescriptionProps,\n AdImageProps,\n AdLabelProps,\n AdStatusProps,\n AdTitleProps,\n} from \"./components/Ad\";\n\n// Context hook\nexport { useAd } from \"./hooks/useAdContext\";\nexport type { AdContextValue } from \"./hooks/useAdContext\";\n\n// Field helpers\nexport {\n getAdDescription,\n getAdImage,\n getAdTitle,\n getAdType,\n} from \"./utils/ad-fields\";\n\n// Types\nexport type { AdProps, AdAuth } from \"./types\";\nexport { AdDataQueryError } from \"./types\";\n\n// Constants\nexport { adCardIcon, adCardLabel } from \"./utils/constants\";\n","import { SlotsChain } from \"@0xslots/sdk\";\nimport type { AdData } from \"@adland/data\";\nimport { useCallback, useEffect, useMemo, useRef } from \"react\";\n\nimport { createReadClient, fetchAdFromURI, fetchMetadataURI } from \"../fetch\";\nimport { AdContext, useAd } from \"../hooks/useAdContext\";\nimport { useFetch } from \"../hooks/useFetch\";\nimport { AdDataQueryError, type AdProps } from \"../types\";\nimport { performAdAction, performEmptyAdAction } from \"../utils/ad-actions\";\nimport {\n getAdDescription,\n getAdImage,\n getAdTitle,\n getAdType,\n} from \"../utils/ad-fields\";\nimport { adCardIcon, adCardLabel } from \"../utils/constants\";\nimport { trackClick, trackImpression } from \"../utils/tracking\";\n\n// ─── Root component ──────────────────────────────────────────────────────────\n\n/**\n * Root Ad component — compound pattern.\n *\n * @example\n * ```tsx\n * <Ad slot=\"0xabc...123\" className=\"rounded-md border p-3\">\n * <AdImage className=\"size-10 rounded-md\" />\n * <AdTitle className=\"text-sm font-medium\" />\n * <AdDescription className=\"text-xs text-muted-foreground\" />\n * <AdBadge />\n * </Ad>\n * ```\n */\nexport function Ad({\n slot,\n data: staticData,\n chainId = SlotsChain.BASE,\n rpcUrl,\n baseLinkUrl = \"https://app.0xslots.org\",\n auth = \"none\",\n context,\n children,\n ...props\n}: AdProps) {\n const ref = useRef<HTMLDivElement>(null);\n\n const client = useMemo(\n () => (slot ? createReadClient(chainId, rpcUrl) : null),\n [slot, chainId, rpcUrl],\n );\n\n const {\n data: fetchedData,\n isLoading,\n error,\n } = useFetch<AdData>(\n `ad-data-${slot}`,\n async () => {\n if (!client || !slot) throw new Error(AdDataQueryError.NO_AD);\n const uri = await fetchMetadataURI(client, slot);\n if (!uri) {\n console.info(\"[Ad] no metadata URI found for slot\", slot);\n return null;\n }\n return fetchAdFromURI(uri);\n },\n { enabled: !!slot && !staticData },\n );\n\n const adData = staticData ?? fetchedData;\n\n const isEmpty =\n !adData &&\n !isLoading &&\n (error instanceof Error\n ? error.message === AdDataQueryError.NO_AD\n : !error);\n\n // Track impression when ad is visible in viewport (once per slot per page load)\n useEffect(() => {\n if (!adData || !slot) return;\n return trackImpression(ref.current, { slot, chainId, auth, context });\n }, [adData, slot, chainId, auth, context]);\n\n // Track impression for empty slots too\n useEffect(() => {\n if (!isEmpty || !slot) return;\n return trackImpression(ref.current, {\n slot,\n chainId,\n auth,\n context,\n empty: true,\n });\n }, [isEmpty, slot, chainId, auth, context]);\n\n const onClick = useCallback(\n (e: React.MouseEvent<HTMLDivElement>) => {\n const target = e.target as HTMLElement;\n const isInteractive =\n target.tagName === \"A\" ||\n target.tagName === \"BUTTON\" ||\n target.closest(\"a\") !== null ||\n target.closest(\"button\") !== null;\n if (isInteractive) return;\n\n if (adData) {\n if (slot) trackClick(\"click\", { slot, chainId, auth, context });\n performAdAction(adData);\n } else if (isEmpty && slot) {\n trackClick(\"click-empty\", { slot, chainId, auth, context });\n performEmptyAdAction(slot, chainId, baseLinkUrl);\n }\n },\n [adData, isEmpty, slot, chainId, baseLinkUrl],\n );\n\n return (\n <AdContext.Provider\n value={{\n data: adData ?? null,\n isLoading: !!slot && !staticData && isLoading,\n error,\n isEmpty,\n slot,\n baseLinkUrl,\n chainId,\n }}\n >\n <div ref={ref} onClick={onClick} {...props}>\n {children}\n </div>\n </AdContext.Provider>\n );\n}\n\n// ─── Sub-components ──────────────────────────────────────────────────────────\n\nexport interface AdImageProps\n extends React.ImgHTMLAttributes<HTMLImageElement> {\n fallback?: React.ReactNode;\n}\n\nexport function AdImage({ fallback, ...props }: AdImageProps) {\n const { data } = useAd();\n const src = getAdImage(data);\n if (!src) return fallback ? <>{fallback}</> : null;\n return <img src={src} alt=\"\" {...props} />;\n}\n\nexport interface AdTitleProps\n extends React.HTMLAttributes<HTMLParagraphElement> {\n fallback?: React.ReactNode;\n}\n\nexport function AdTitle({ fallback, children, ...props }: AdTitleProps) {\n const { data } = useAd();\n const title = getAdTitle(data);\n if (!title) return fallback ? <>{fallback}</> : null;\n return <p {...props}>{children ?? title}</p>;\n}\n\nexport interface AdDescriptionProps\n extends React.HTMLAttributes<HTMLParagraphElement> {\n fallback?: React.ReactNode;\n}\n\nexport function AdDescription({\n fallback,\n children,\n ...props\n}: AdDescriptionProps) {\n const { data } = useAd();\n const description = getAdDescription(data);\n if (!description) return fallback ? <>{fallback}</> : null;\n return <p {...props}>{children ?? description}</p>;\n}\n\nexport interface AdBadgeProps extends React.HTMLAttributes<HTMLSpanElement> {}\n\nexport function AdBadge({ children, ...props }: AdBadgeProps) {\n const { data } = useAd();\n const type = getAdType(data);\n if (!type) return null;\n const Icon = adCardIcon[type];\n const label = adCardLabel[type];\n return (\n <span {...props}>\n {children ?? (\n <>\n {Icon && <Icon className=\"size-3\" />}\n {label}\n </>\n )}\n </span>\n );\n}\n\nexport interface AdLabelProps extends React.HTMLAttributes<HTMLSpanElement> {}\n\nexport function AdLabel({ children, ...props }: AdLabelProps) {\n return <span {...props}>{children ?? \"AD\"}</span>;\n}\n\n// ─── State components ────────────────────────────────────────────────────────\n\nexport interface AdStatusProps extends React.HTMLAttributes<HTMLDivElement> {\n children?: React.ReactNode;\n}\n\nexport function AdLoading({ children, ...props }: AdStatusProps) {\n const { isLoading } = useAd();\n if (!isLoading) return null;\n return <div {...props}>{children ?? \"Loading...\"}</div>;\n}\n\nexport function AdEmpty({ children, ...props }: AdStatusProps) {\n const { isEmpty } = useAd();\n if (!isEmpty) return null;\n return <div {...props}>{children ?? \"Your ad here\"}</div>;\n}\n\nexport function AdError({ children, ...props }: AdStatusProps) {\n const { error, isEmpty } = useAd();\n if (!error || isEmpty) return null;\n return <div {...props}>{children ?? \"Error loading ad\"}</div>;\n}\n\nexport function AdLoaded({ children, ...props }: AdStatusProps) {\n const { data } = useAd();\n if (!data) return null;\n return <div {...props}>{children}</div>;\n}\n","import { SlotsClient, type SlotsChain } from \"@0xslots/sdk\";\nimport { type Address, createPublicClient, http } from \"viem\";\nimport { base, baseSepolia } from \"viem/chains\";\n\nimport { AdDataQueryError } from \"./types\";\n\nconst IPFS_GATEWAY = \"https://amethyst-representative-mandrill-369.mypinata.cloud/ipfs/\";\n\nconst viemChains: Record<number, typeof base> = {\n 8453: base,\n 84532: baseSepolia,\n};\n\n/**\n * Create a read-only SlotsClient for a given chain.\n */\nexport function createReadClient(\n chainId: SlotsChain,\n rpcUrl?: string,\n): SlotsClient {\n const chain = viemChains[chainId];\n if (!chain) throw new Error(`Unsupported chain: ${chainId}`);\n\n const publicClient = createPublicClient({\n chain,\n transport: http(rpcUrl),\n });\n\n return new SlotsClient({ chainId, publicClient });\n}\n\n/**\n * Fetch ad content from a metadata URI (IPFS or HTTP)\n */\nexport const fetchAdFromURI = async (uri: string) => {\n if (!uri) throw new Error(AdDataQueryError.NO_AD);\n\n const url = uri.startsWith(\"ipfs://\")\n ? `${IPFS_GATEWAY}${uri.slice(7)}`\n : uri;\n\n const res = await fetch(url, {\n method: \"GET\",\n headers: { Accept: \"application/json\" },\n });\n\n if (!res.ok) {\n if (res.status === 404) throw new Error(AdDataQueryError.NO_AD);\n throw new Error(AdDataQueryError.ERROR);\n }\n\n const data = await res.json();\n if (data.error) throw new Error(data.error);\n\n return data;\n};\n\n/**\n * Fetch the metadata URI for a slot using the SDK.\n */\nexport const fetchMetadataURI = async (\n client: SlotsClient,\n slotAddress: string,\n): Promise<string> => {\n const info = await client.getSlotInfo(slotAddress as Address);\n const moduleAddress = (info as { module: Address }).module;\n\n if (\n !moduleAddress ||\n moduleAddress === \"0x0000000000000000000000000000000000000000\"\n ) {\n return \"\";\n }\n\n return client.modules.metadata.getURI(\n moduleAddress,\n slotAddress as Address,\n );\n};\n","import type { SlotsChain } from \"@0xslots/sdk\";\nimport type { AdData } from \"@adland/data\";\n\n/**\n * Optional analytics context passed with every impression/click event.\n * Only include dimensions you'll filter or group by — Umami already\n * captures hostname, URL, referrer, country, device, OS, and browser.\n */\n/** Authentication method for verified impressions */\nexport type AdAuth = \"farcaster\" | \"none\";\n\nexport interface AdProps extends React.HTMLAttributes<HTMLDivElement> {\n /**\n * The slot contract address (0xSlots v3).\n * Required when fetching from chain. Omit when passing static `data`.\n */\n slot?: string;\n /**\n * Chain ID for on-chain reads. Defaults to BASE (8453).\n */\n chainId?: SlotsChain;\n /**\n * Static ad data. When provided, skips on-chain fetching.\n */\n data?: AdData;\n /**\n * Optional RPC URL override. If not provided, uses public RPC for the chain.\n */\n rpcUrl?: string;\n /**\n * Base URL for the \"Your ad here\" CTA link.\n * Empty-state click navigates to `${baseLinkUrl}/slots/${slot}?chain=${chainId}`.\n * Defaults to \"https://app.0xslots.org\".\n */\n baseLinkUrl?: string;\n /**\n * Authentication method for verified impressions. Default: \"none\".\n * \"farcaster\" uses Quick Auth — server resolves FID + address automatically.\n */\n auth?: AdAuth;\n /**\n * Placement context sent with events (e.g. \"carousel\", \"sidebar\", \"frame\").\n */\n context?: string;\n /**\n * Compound children (AdImage, AdTitle, etc.)\n */\n children?: React.ReactNode;\n}\n\nexport enum AdDataQueryError {\n NO_AD = \"NO_AD\",\n ERROR = \"ERROR\",\n}\n","import type { SlotsChain } from \"@0xslots/sdk\";\nimport type { AdData } from \"@adland/data\";\nimport { createContext, useContext } from \"react\";\n\nexport interface AdContextValue {\n data: AdData | null;\n isLoading: boolean;\n error: unknown;\n isEmpty: boolean;\n slot?: string;\n baseLinkUrl: string;\n chainId: SlotsChain;\n}\n\nexport const AdContext = createContext<AdContextValue | null>(null);\n\nexport function useAd(): AdContextValue {\n const ctx = useContext(AdContext);\n if (!ctx) throw new Error(\"useAd must be used within an <Ad> component\");\n return ctx;\n}\n","import { useEffect, useState } from \"react\";\nimport FetchCache from \"../utils/fetchCache\";\n\ntype Status = \"idle\" | \"loading\" | \"success\" | \"error\";\n\nconst globalCache = new FetchCache();\n\nexport const fetchCache = {\n clear: () => globalCache.clear(),\n};\n\nexport function useFetch<T>(\n key: string,\n fetcher: () => Promise<T>,\n opts?: {\n enabled?: boolean;\n ttl?: number; // ms\n },\n) {\n const { enabled = true, ttl = 0 } = opts ?? {};\n\n // Get cached data from global cache\n const getCachedData = (): T | null => {\n return globalCache.get<T>(key, ttl || undefined);\n };\n\n const hasCachedData = () => {\n return globalCache.has(key, ttl || undefined);\n };\n\n // Check if there's an active fetch for this key (dedupe concurrent requests)\n const getActiveFetch = (): Promise<T> | undefined => {\n return globalCache.getActiveFetch<T>(key);\n };\n\n const cachedData = getCachedData();\n const [data, setData] = useState<T | null>(cachedData);\n const [error, setError] = useState<unknown>(null);\n // If we have cached data, start with success status to avoid showing loader\n const [status, setStatus] = useState<Status>(cachedData ? \"success\" : \"idle\");\n\n const refetch = async () => {\n // ALWAYS check cache first - never show loading if we have valid cached data\n const cached = getCachedData();\n if (cached) {\n // Already have valid cached data, ensure state is correct\n setData(cached);\n setStatus(\"success\");\n return cached;\n }\n\n // Check if there's already an active fetch for this key (dedupe)\n const activeFetch = getActiveFetch();\n if (activeFetch) {\n try {\n const res = await activeFetch;\n setData(res);\n setStatus(\"success\");\n return res;\n } catch (e) {\n setError(e);\n setStatus(\"error\");\n throw e;\n }\n }\n\n // Only set loading if we actually need to fetch\n setStatus(\"loading\");\n setError(null);\n\n try {\n // Create fetch promise and store it for deduplication\n const fetchPromise = fetcher();\n globalCache.setActiveFetch(key, fetchPromise);\n\n const res = await fetchPromise;\n globalCache.set(key, res);\n setData(res);\n setStatus(\"success\");\n return res;\n } catch (e) {\n setError(e);\n setStatus(\"error\");\n throw e;\n }\n };\n\n useEffect(() => {\n if (!enabled) return;\n\n const cached = getCachedData();\n\n if (cached) {\n setData(cached);\n setStatus(\"success\");\n return;\n }\n\n const activeFetch = getActiveFetch();\n if (activeFetch) {\n setStatus(\"loading\");\n activeFetch\n .then((res) => {\n setData(res);\n setStatus(\"success\");\n })\n .catch((e) => {\n setError(e);\n setStatus(\"error\");\n });\n return;\n }\n\n if (status !== \"loading\" && status !== \"success\") {\n refetch();\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [key, enabled]);\n\n const hasValidCache = hasCachedData();\n const isLoading = status === \"loading\" && !hasValidCache;\n\n return {\n data,\n error,\n status,\n isIdle: status === \"idle\",\n isLoading,\n isSuccess: status === \"success\",\n isError: status === \"error\",\n refetch,\n };\n}\n","class FetchCache {\n private cache = new Map<string, { data: unknown; ts: number }>();\n private activeFetches = new Map<string, Promise<unknown>>();\n\n get<T>(key: string, ttl?: number): T | null {\n const cached = this.cache.get(key);\n if (!cached) return null;\n\n if (ttl && Date.now() - cached.ts > ttl) {\n // Cache expired, remove it\n this.cache.delete(key);\n return null;\n }\n\n return cached.data as T;\n }\n\n has(key: string, ttl?: number): boolean {\n const cached = this.cache.get(key);\n if (!cached) return false;\n\n if (ttl && Date.now() - cached.ts > ttl) {\n this.cache.delete(key);\n return false;\n }\n\n return true;\n }\n\n set<T>(key: string, data: T): void {\n this.cache.set(key, { data, ts: Date.now() });\n }\n\n getActiveFetch<T>(key: string): Promise<T> | undefined {\n return this.activeFetches.get(key) as Promise<T> | undefined;\n }\n\n setActiveFetch<T>(key: string, promise: Promise<T>): void {\n this.activeFetches.set(key, promise);\n promise.finally(() => {\n this.activeFetches.delete(key);\n });\n }\n\n clear(): void {\n this.cache.clear();\n this.activeFetches.clear();\n }\n}\n\nexport default FetchCache;\n","import type { SlotsChain } from \"@0xslots/sdk\";\nimport type { AdData } from \"@adland/data\";\nimport sdk from \"@farcaster/miniapp-sdk\";\nimport { getAddress } from \"viem\";\n\n// Cache the miniapp check at module level — resolved once, sync thereafter\nlet _isMiniApp: boolean | null = null;\nconst _miniAppPromise = sdk\n .isInMiniApp()\n .then((v) => {\n _isMiniApp = v;\n return v;\n })\n .catch(() => {\n _isMiniApp = false;\n return false;\n });\n\nasync function isMiniApp(): Promise<boolean> {\n if (_isMiniApp !== null) return _isMiniApp;\n return _miniAppPromise;\n}\n\nexport function performAdAction(adData: AdData) {\n try {\n switch (adData.type) {\n case \"link\":\n sdk.actions.openUrl(adData.data.url);\n break;\n case \"cast\":\n sdk.actions.viewCast({ hash: adData.data.hash });\n break;\n case \"miniapp\":\n sdk.actions.openMiniApp({ url: adData.data.url });\n break;\n case \"token\": {\n const address = adData.data.address;\n const chainId = adData.data.chainId;\n const buyToken = `eip155:${chainId}/erc20:${getAddress(address)}`;\n sdk.actions.swapToken({ buyToken });\n break;\n }\n case \"farcasterProfile\":\n sdk.actions.viewProfile({\n fid: Number.parseInt(adData.data.fid, 10),\n });\n break;\n }\n } catch (err) {\n // Fallback for web (non-miniapp) context\n if (adData.type === \"link\" || adData.type === \"miniapp\") {\n window.open(adData.data.url, \"_blank\");\n } else {\n console.error(\"[@adland/react] Failed to perform ad action:\", err);\n }\n }\n}\n\nexport async function performEmptyAdAction(\n slot: string,\n chainId: SlotsChain,\n baseLinkUrl: string,\n) {\n const url = `${baseLinkUrl}/slots/${slot}?chain=${chainId}`;\n if (await isMiniApp()) {\n sdk.actions.openMiniApp({ url });\n } else {\n window.open(url, \"_blank\");\n }\n}\n","import type { AdData, AdType } from \"@adland/data\";\n\nconst IMAGE_KEYS = [\"image\", \"icon\", \"pfpUrl\", \"logoURI\", \"imageUrl\"] as const;\nconst TITLE_KEYS = [\"title\", \"displayName\", \"username\", \"name\", \"symbol\"] as const;\nconst DESC_KEYS = [\"description\", \"bio\", \"text\", \"name\"] as const;\n\nfunction flatFields(data: AdData): Record<string, unknown> {\n return { ...data.data, ...(data.metadata ?? {}) };\n}\n\nexport function getAdImage(data: AdData | null): string | null {\n if (!data) return null;\n const fields = flatFields(data);\n for (const key of IMAGE_KEYS) {\n const v = fields[key];\n if (typeof v === \"string\" && v) return v;\n }\n return null;\n}\n\nexport function getAdTitle(data: AdData | null): string | null {\n if (!data) return null;\n const fields = flatFields(data);\n for (const key of TITLE_KEYS) {\n const v = fields[key];\n if (typeof v === \"string\" && v) return v;\n }\n return null;\n}\n\nexport function getAdDescription(data: AdData | null): string | null {\n if (!data) return null;\n const fields = flatFields(data);\n const title = getAdTitle(data);\n for (const key of DESC_KEYS) {\n const v = fields[key];\n if (typeof v === \"string\" && v && v !== title) return v;\n }\n return null;\n}\n\nexport function getAdType(data: AdData | null): AdType | null {\n if (!data) return null;\n return data.type as AdType;\n}\n","import { AdType } from \"@adland/data\";\nimport { ForwardRefExoticComponent, RefAttributes } from \"react\";\nimport {\n Link,\n MessageCircle,\n LayoutGrid,\n LucideProps,\n Coins,\n User,\n} from \"lucide-react\";\n\nexport const adCardIcon: Record<\n AdType,\n ForwardRefExoticComponent<\n Omit<LucideProps, \"ref\"> & RefAttributes<SVGSVGElement>\n >\n> = {\n link: Link,\n cast: MessageCircle,\n miniapp: LayoutGrid,\n token: Coins,\n farcasterProfile: User,\n};\n\nexport const adCardLabel: Record<AdType, string> = {\n link: \"Link\",\n cast: \"Cast\",\n miniapp: \"Miniapp\",\n token: \"Token\",\n farcasterProfile: \"Profile\",\n};\n\nexport const adlandApiUrl =\n process.env.NODE_ENV === \"development\"\n ? \"http://localhost:3069\"\n : \"https://api.adland.space\";\n","import type { AdAuth } from \"../types\";\n\nconst API_URL = \"https://api.0xslots.org\";\n\ninterface TrackingPayload {\n slot: string;\n chainId: number;\n auth?: AdAuth;\n context?: string;\n empty?: boolean;\n}\n\n/** Track which slot+url combos have already been counted this session */\nconst tracked = new Set<string>();\n\n/** Cache the auth token per session */\nlet cachedAuthToken: string | null = null;\nlet authAttempted = false;\n\n/**\n * Get a Farcaster Quick Auth token via miniapp SDK.\n */\nasync function getFarcasterToken(): Promise<string | null> {\n if (cachedAuthToken) return cachedAuthToken;\n if (authAttempted) return null;\n\n authAttempted = true;\n try {\n const sdk = await import(\"@farcaster/miniapp-sdk\").then((m) => m.default);\n const isInMiniApp = await sdk.isInMiniApp();\n if (!isInMiniApp) return null;\n\n const result = await sdk.quickAuth.getToken();\n cachedAuthToken = result.token;\n return cachedAuthToken;\n } catch {\n return null;\n }\n}\n\n/**\n * Send a tracking event through the API proxy.\n */\nasync function sendEvent(\n eventName: string,\n data: TrackingPayload,\n): Promise<void> {\n if (typeof window === \"undefined\") return;\n\n const { auth, ...eventData } = data;\n let authToken: string | null = null;\n\n if (auth === \"farcaster\") {\n authToken = await getFarcasterToken();\n }\n\n const headers: Record<string, string> = {\n \"Content-Type\": \"application/json\",\n };\n\n if (authToken) {\n headers[\"Authorization\"] = `Bearer ${authToken}`;\n }\n\n try {\n fetch(`${API_URL}/events/track`, {\n method: \"POST\",\n headers,\n body: JSON.stringify({\n event: eventName,\n url: window.location.href,\n referrer: document.referrer || undefined,\n hostname: window.location.hostname,\n data: eventData,\n authMethod: auth || \"none\",\n }),\n keepalive: true,\n }).catch(() => {});\n } catch {}\n}\n\n/**\n * Track a unique impression (once per slot per page load).\n */\nexport function trackImpression(\n element: HTMLElement | null,\n data: TrackingPayload,\n): (() => void) | undefined {\n if (!element || typeof window === \"undefined\") return;\n\n const eventName = data.empty ? \"impression-empty\" : \"impression\";\n const key = `${eventName}:${data.slot}:${window.location.href}`;\n if (tracked.has(key)) return;\n\n const observer = new IntersectionObserver(\n (entries) => {\n for (const entry of entries) {\n if (entry.isIntersecting && !tracked.has(key)) {\n tracked.add(key);\n sendEvent(eventName, data);\n observer.disconnect();\n }\n }\n },\n { threshold: 0.5 },\n );\n\n observer.observe(element);\n\n return () => observer.disconnect();\n}\n\n/**\n * Track a click event.\n */\nexport function trackClick(eventName: string, data: TrackingPayload): void {\n sendEvent(eventName, data);\n}\n"],"mappings":"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","names":["import_sdk","import_react","AdDataQueryError","import_react","import_viem","sdk","sdk"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/components/Ad.tsx","../src/fetch.ts","../src/types.ts","../src/hooks/useAdContext.ts","../src/hooks/useFetch.ts","../src/utils/fetchCache.ts","../src/utils/ad-actions.ts","../src/utils/ad-fields.ts","../src/utils/constants.ts","../src/utils/tracking.ts"],"sourcesContent":["// Compound Ad components\nexport {\n Ad,\n AdBadge,\n AdDescription,\n AdEmpty,\n AdError,\n AdImage,\n AdLabel,\n AdLoaded,\n AdLoading,\n AdTitle,\n} from \"./components/Ad\";\n\nexport type {\n AdBadgeProps,\n AdDescriptionProps,\n AdImageProps,\n AdLabelProps,\n AdStatusProps,\n AdTitleProps,\n} from \"./components/Ad\";\n\n// Context hook\nexport { useAd } from \"./hooks/useAdContext\";\nexport type { AdContextValue } from \"./hooks/useAdContext\";\n\n// Field helpers\nexport {\n getAdDescription,\n getAdImage,\n getAdTitle,\n getAdType,\n} from \"./utils/ad-fields\";\n\n// Types\nexport type { AdProps, AdAuth } from \"./types\";\nexport { AdDataQueryError } from \"./types\";\n\n// Constants\nexport { adCardIcon, adCardLabel } from \"./utils/constants\";\n","import { SlotsChain } from \"@0xslots/sdk\";\nimport type { AdData } from \"@adland/data\";\nimport { useCallback, useEffect, useMemo, useRef } from \"react\";\n\nimport { createReadClient, fetchAdFromURI, fetchMetadataURI } from \"../fetch\";\nimport { AdContext, useAd } from \"../hooks/useAdContext\";\nimport { useFetch } from \"../hooks/useFetch\";\nimport { AdDataQueryError, type AdProps } from \"../types\";\nimport { performAdAction, performEmptyAdAction } from \"../utils/ad-actions\";\nimport {\n getAdDescription,\n getAdImage,\n getAdTitle,\n getAdType,\n} from \"../utils/ad-fields\";\nimport { adCardIcon, adCardLabel } from \"../utils/constants\";\nimport { trackClick, trackImpression } from \"../utils/tracking\";\n\n// ─── Root component ──────────────────────────────────────────────────────────\n\n/**\n * Root Ad component — compound pattern.\n *\n * @example\n * ```tsx\n * <Ad slot=\"0xabc...123\" className=\"rounded-md border p-3\">\n * <AdImage className=\"size-10 rounded-md\" />\n * <AdTitle className=\"text-sm font-medium\" />\n * <AdDescription className=\"text-xs text-muted-foreground\" />\n * <AdBadge />\n * </Ad>\n * ```\n */\nexport function Ad({\n slot,\n data: staticData,\n chainId = SlotsChain.BASE,\n rpcUrl,\n baseLinkUrl = \"https://app.0xslots.org\",\n auth = \"none\",\n context,\n children,\n ...props\n}: AdProps) {\n const ref = useRef<HTMLDivElement>(null);\n\n const client = useMemo(\n () => (slot ? createReadClient(chainId, rpcUrl) : null),\n [slot, chainId, rpcUrl],\n );\n\n const {\n data: fetchResult,\n isLoading,\n error,\n } = useFetch<{ data: AdData; cid: string | null } | null>(\n `ad-data-${slot}`,\n async () => {\n if (!client || !slot) throw new Error(AdDataQueryError.NO_AD);\n const uri = await fetchMetadataURI(client, slot);\n if (!uri) {\n console.info(\"[Ad] no metadata URI found for slot\", slot);\n return null;\n }\n return fetchAdFromURI(uri);\n },\n { enabled: !!slot && !staticData },\n );\n\n const adData = staticData ?? fetchResult?.data ?? null;\n const cid = fetchResult?.cid ?? null;\n\n const isEmpty =\n !adData &&\n !isLoading &&\n (error instanceof Error\n ? error.message === AdDataQueryError.NO_AD\n : !error);\n\n // Track impression when ad or empty state is visible in viewport\n useEffect(() => {\n if (!slot || (!adData && !isEmpty)) return;\n return trackImpression(ref.current, {\n slot,\n chainId,\n auth,\n context,\n cid,\n empty: isEmpty,\n });\n }, [adData, isEmpty, slot, chainId, auth, context, cid]);\n\n const onClick = useCallback(\n (e: React.MouseEvent<HTMLDivElement>) => {\n const target = e.target as HTMLElement;\n const isInteractive =\n target.tagName === \"A\" ||\n target.tagName === \"BUTTON\" ||\n target.closest(\"a\") !== null ||\n target.closest(\"button\") !== null;\n if (isInteractive) return;\n\n if (adData) {\n if (slot) trackClick(\"click\", { slot, chainId, auth, context, cid });\n performAdAction(adData);\n } else if (isEmpty && slot) {\n trackClick(\"click-empty\", { slot, chainId, auth, context });\n performEmptyAdAction(slot, chainId, baseLinkUrl);\n }\n },\n [adData, isEmpty, slot, chainId, baseLinkUrl, cid],\n );\n\n return (\n <AdContext.Provider\n value={{\n data: adData ?? null,\n cid,\n isLoading: !!slot && !staticData && isLoading,\n error,\n isEmpty,\n slot,\n baseLinkUrl,\n chainId,\n }}\n >\n <div ref={ref} onClick={onClick} {...props}>\n {children}\n </div>\n </AdContext.Provider>\n );\n}\n\n// ─── Sub-components ──────────────────────────────────────────────────────────\n\nexport interface AdImageProps\n extends React.ImgHTMLAttributes<HTMLImageElement> {\n fallback?: React.ReactNode;\n}\n\nexport function AdImage({ fallback, ...props }: AdImageProps) {\n const { data } = useAd();\n const src = getAdImage(data);\n if (!src) return fallback ? <>{fallback}</> : null;\n return <img src={src} alt=\"\" {...props} />;\n}\n\nexport interface AdTitleProps\n extends React.HTMLAttributes<HTMLParagraphElement> {\n fallback?: React.ReactNode;\n}\n\nexport function AdTitle({ fallback, children, ...props }: AdTitleProps) {\n const { data } = useAd();\n const title = getAdTitle(data);\n if (!title) return fallback ? <>{fallback}</> : null;\n return <p {...props}>{children ?? title}</p>;\n}\n\nexport interface AdDescriptionProps\n extends React.HTMLAttributes<HTMLParagraphElement> {\n fallback?: React.ReactNode;\n}\n\nexport function AdDescription({\n fallback,\n children,\n ...props\n}: AdDescriptionProps) {\n const { data } = useAd();\n const description = getAdDescription(data);\n if (!description) return fallback ? <>{fallback}</> : null;\n return <p {...props}>{children ?? description}</p>;\n}\n\nexport interface AdBadgeProps extends React.HTMLAttributes<HTMLSpanElement> {}\n\nexport function AdBadge({ children, ...props }: AdBadgeProps) {\n const { data } = useAd();\n const type = getAdType(data);\n if (!type) return null;\n const Icon = adCardIcon[type];\n const label = adCardLabel[type];\n return (\n <span {...props}>\n {children ?? (\n <>\n {Icon && <Icon className=\"size-3\" />}\n {label}\n </>\n )}\n </span>\n );\n}\n\nexport interface AdLabelProps extends React.HTMLAttributes<HTMLSpanElement> {}\n\nexport function AdLabel({ children, ...props }: AdLabelProps) {\n return <span {...props}>{children ?? \"AD\"}</span>;\n}\n\n// ─── State components ────────────────────────────────────────────────────────\n\nexport interface AdStatusProps extends React.HTMLAttributes<HTMLDivElement> {\n children?: React.ReactNode;\n}\n\nexport function AdLoading({ children, ...props }: AdStatusProps) {\n const { isLoading } = useAd();\n if (!isLoading) return null;\n return <div {...props}>{children ?? \"Loading...\"}</div>;\n}\n\nexport function AdEmpty({ children, ...props }: AdStatusProps) {\n const { isEmpty } = useAd();\n if (!isEmpty) return null;\n return <div {...props}>{children ?? \"Your ad here\"}</div>;\n}\n\nexport function AdError({ children, ...props }: AdStatusProps) {\n const { error, isEmpty } = useAd();\n if (!error || isEmpty) return null;\n return <div {...props}>{children ?? \"Error loading ad\"}</div>;\n}\n\nexport function AdLoaded({ children, ...props }: AdStatusProps) {\n const { data } = useAd();\n if (!data) return null;\n return <div {...props}>{children}</div>;\n}\n","import { SlotsClient, type SlotsChain } from \"@0xslots/sdk\";\nimport type { AdData } from \"@adland/data\";\nimport { type Address, createPublicClient, http } from \"viem\";\nimport { base, baseSepolia } from \"viem/chains\";\n\nimport { AdDataQueryError } from \"./types\";\n\nconst IPFS_GATEWAY = \"https://amethyst-representative-mandrill-369.mypinata.cloud/ipfs/\";\n\nconst viemChains: Record<number, typeof base> = {\n 8453: base,\n 84532: baseSepolia,\n};\n\n/**\n * Create a read-only SlotsClient for a given chain.\n */\nexport function createReadClient(\n chainId: SlotsChain,\n rpcUrl?: string,\n): SlotsClient {\n const chain = viemChains[chainId];\n if (!chain) throw new Error(`Unsupported chain: ${chainId}`);\n\n const publicClient = createPublicClient({\n chain,\n transport: http(rpcUrl),\n });\n\n return new SlotsClient({ chainId, publicClient });\n}\n\n/**\n * Extract the IPFS CID from a metadata URI, or null for non-IPFS URIs.\n */\nexport function extractCid(uri: string): string | null {\n if (uri.startsWith(\"ipfs://\")) return uri.slice(7);\n if (uri.startsWith(\"Qm\") || uri.startsWith(\"bafy\")) return uri;\n return null;\n}\n\n/**\n * Fetch ad content from a metadata URI (IPFS or HTTP).\n * Returns both the ad data and the CID (if IPFS).\n */\nexport const fetchAdFromURI = async (\n uri: string,\n): Promise<{ data: AdData; cid: string | null }> => {\n if (!uri) throw new Error(AdDataQueryError.NO_AD);\n\n const cid = extractCid(uri);\n const url = cid ? `${IPFS_GATEWAY}${cid}` : uri;\n\n const res = await fetch(url, {\n method: \"GET\",\n headers: { Accept: \"application/json\" },\n });\n\n if (!res.ok) {\n if (res.status === 404) throw new Error(AdDataQueryError.NO_AD);\n throw new Error(AdDataQueryError.ERROR);\n }\n\n const data = await res.json();\n if (data.error) throw new Error(data.error);\n\n return { data, cid };\n};\n\n/**\n * Fetch the metadata URI for a slot using the SDK.\n */\nexport const fetchMetadataURI = async (\n client: SlotsClient,\n slotAddress: string,\n): Promise<string> => {\n const info = await client.getSlotInfo(slotAddress as Address);\n const moduleAddress = (info as { module: Address }).module;\n\n if (\n !moduleAddress ||\n moduleAddress === \"0x0000000000000000000000000000000000000000\"\n ) {\n return \"\";\n }\n\n return client.modules.metadata.getURI(\n moduleAddress,\n slotAddress as Address,\n );\n};\n","import type { SlotsChain } from \"@0xslots/sdk\";\nimport type { AdData } from \"@adland/data\";\n\n/**\n * Optional analytics context passed with every impression/click event.\n * Only include dimensions you'll filter or group by — Umami already\n * captures hostname, URL, referrer, country, device, OS, and browser.\n */\n/** Authentication method for verified impressions */\nexport type AdAuth = \"farcaster\" | \"none\";\n\nexport interface AdProps extends React.HTMLAttributes<HTMLDivElement> {\n /**\n * The slot contract address (0xSlots v3).\n * Required when fetching from chain. Omit when passing static `data`.\n */\n slot?: string;\n /**\n * Chain ID for on-chain reads. Defaults to BASE (8453).\n */\n chainId?: SlotsChain;\n /**\n * Static ad data. When provided, skips on-chain fetching.\n */\n data?: AdData;\n /**\n * Optional RPC URL override. If not provided, uses public RPC for the chain.\n */\n rpcUrl?: string;\n /**\n * Base URL for the \"Your ad here\" CTA link.\n * Empty-state click navigates to `${baseLinkUrl}/slots/${slot}?chain=${chainId}`.\n * Defaults to \"https://app.0xslots.org\".\n */\n baseLinkUrl?: string;\n /**\n * Authentication method for verified impressions. Default: \"none\".\n * \"farcaster\" uses Quick Auth — server resolves FID + address automatically.\n */\n auth?: AdAuth;\n /**\n * Placement context sent with events (e.g. \"carousel\", \"sidebar\", \"frame\").\n */\n context?: string;\n /**\n * Compound children (AdImage, AdTitle, etc.)\n */\n children?: React.ReactNode;\n}\n\nexport enum AdDataQueryError {\n NO_AD = \"NO_AD\",\n ERROR = \"ERROR\",\n}\n","import type { SlotsChain } from \"@0xslots/sdk\";\nimport type { AdData } from \"@adland/data\";\nimport { createContext, useContext } from \"react\";\n\nexport interface AdContextValue {\n data: AdData | null;\n cid: string | null;\n isLoading: boolean;\n error: unknown;\n isEmpty: boolean;\n slot?: string;\n baseLinkUrl: string;\n chainId: SlotsChain;\n}\n\nexport const AdContext = createContext<AdContextValue | null>(null);\n\nexport function useAd(): AdContextValue {\n const ctx = useContext(AdContext);\n if (!ctx) throw new Error(\"useAd must be used within an <Ad> component\");\n return ctx;\n}\n","import { useEffect, useState } from \"react\";\nimport FetchCache from \"../utils/fetchCache\";\n\ntype Status = \"idle\" | \"loading\" | \"success\" | \"error\";\n\nconst globalCache = new FetchCache();\n\nexport const fetchCache = {\n clear: () => globalCache.clear(),\n};\n\nexport function useFetch<T>(\n key: string,\n fetcher: () => Promise<T>,\n opts?: {\n enabled?: boolean;\n ttl?: number; // ms\n },\n) {\n const { enabled = true, ttl = 0 } = opts ?? {};\n\n // Get cached data from global cache\n const getCachedData = (): T | null => {\n return globalCache.get<T>(key, ttl || undefined);\n };\n\n const hasCachedData = () => {\n return globalCache.has(key, ttl || undefined);\n };\n\n // Check if there's an active fetch for this key (dedupe concurrent requests)\n const getActiveFetch = (): Promise<T> | undefined => {\n return globalCache.getActiveFetch<T>(key);\n };\n\n const cachedData = getCachedData();\n const [data, setData] = useState<T | null>(cachedData);\n const [error, setError] = useState<unknown>(null);\n // If we have cached data, start with success status to avoid showing loader\n const [status, setStatus] = useState<Status>(cachedData ? \"success\" : \"idle\");\n\n const refetch = async () => {\n // ALWAYS check cache first - never show loading if we have valid cached data\n const cached = getCachedData();\n if (cached) {\n // Already have valid cached data, ensure state is correct\n setData(cached);\n setStatus(\"success\");\n return cached;\n }\n\n // Check if there's already an active fetch for this key (dedupe)\n const activeFetch = getActiveFetch();\n if (activeFetch) {\n try {\n const res = await activeFetch;\n setData(res);\n setStatus(\"success\");\n return res;\n } catch (e) {\n setError(e);\n setStatus(\"error\");\n throw e;\n }\n }\n\n // Only set loading if we actually need to fetch\n setStatus(\"loading\");\n setError(null);\n\n try {\n // Create fetch promise and store it for deduplication\n const fetchPromise = fetcher();\n globalCache.setActiveFetch(key, fetchPromise);\n\n const res = await fetchPromise;\n globalCache.set(key, res);\n setData(res);\n setStatus(\"success\");\n return res;\n } catch (e) {\n setError(e);\n setStatus(\"error\");\n throw e;\n }\n };\n\n useEffect(() => {\n if (!enabled) return;\n\n const cached = getCachedData();\n\n if (cached) {\n setData(cached);\n setStatus(\"success\");\n return;\n }\n\n const activeFetch = getActiveFetch();\n if (activeFetch) {\n setStatus(\"loading\");\n activeFetch\n .then((res) => {\n setData(res);\n setStatus(\"success\");\n })\n .catch((e) => {\n setError(e);\n setStatus(\"error\");\n });\n return;\n }\n\n if (status !== \"loading\" && status !== \"success\") {\n refetch();\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [key, enabled]);\n\n const hasValidCache = hasCachedData();\n const isLoading = status === \"loading\" && !hasValidCache;\n\n return {\n data,\n error,\n status,\n isIdle: status === \"idle\",\n isLoading,\n isSuccess: status === \"success\",\n isError: status === \"error\",\n refetch,\n };\n}\n","class FetchCache {\n private cache = new Map<string, { data: unknown; ts: number }>();\n private activeFetches = new Map<string, Promise<unknown>>();\n\n get<T>(key: string, ttl?: number): T | null {\n const cached = this.cache.get(key);\n if (!cached) return null;\n\n if (ttl && Date.now() - cached.ts > ttl) {\n // Cache expired, remove it\n this.cache.delete(key);\n return null;\n }\n\n return cached.data as T;\n }\n\n has(key: string, ttl?: number): boolean {\n const cached = this.cache.get(key);\n if (!cached) return false;\n\n if (ttl && Date.now() - cached.ts > ttl) {\n this.cache.delete(key);\n return false;\n }\n\n return true;\n }\n\n set<T>(key: string, data: T): void {\n this.cache.set(key, { data, ts: Date.now() });\n }\n\n getActiveFetch<T>(key: string): Promise<T> | undefined {\n return this.activeFetches.get(key) as Promise<T> | undefined;\n }\n\n setActiveFetch<T>(key: string, promise: Promise<T>): void {\n this.activeFetches.set(key, promise);\n promise.finally(() => {\n this.activeFetches.delete(key);\n });\n }\n\n clear(): void {\n this.cache.clear();\n this.activeFetches.clear();\n }\n}\n\nexport default FetchCache;\n","import type { SlotsChain } from \"@0xslots/sdk\";\nimport type { AdData } from \"@adland/data\";\nimport sdk from \"@farcaster/miniapp-sdk\";\nimport { getAddress } from \"viem\";\n\n// Cache the miniapp check at module level — resolved once, sync thereafter\nlet _isMiniApp: boolean | null = null;\nconst _miniAppPromise = sdk\n .isInMiniApp()\n .then((v) => {\n _isMiniApp = v;\n return v;\n })\n .catch(() => {\n _isMiniApp = false;\n return false;\n });\n\nasync function isMiniApp(): Promise<boolean> {\n if (_isMiniApp !== null) return _isMiniApp;\n return _miniAppPromise;\n}\n\nexport function performAdAction(adData: AdData) {\n try {\n switch (adData.type) {\n case \"link\":\n sdk.actions.openUrl(adData.data.url);\n break;\n case \"cast\":\n sdk.actions.viewCast({ hash: adData.data.hash });\n break;\n case \"miniapp\":\n sdk.actions.openMiniApp({ url: adData.data.url });\n break;\n case \"token\": {\n const address = adData.data.address;\n const chainId = adData.data.chainId;\n const buyToken = `eip155:${chainId}/erc20:${getAddress(address)}`;\n sdk.actions.swapToken({ buyToken });\n break;\n }\n case \"farcasterProfile\":\n sdk.actions.viewProfile({\n fid: Number.parseInt(adData.data.fid, 10),\n });\n break;\n }\n } catch (err) {\n // Fallback for web (non-miniapp) context\n if (adData.type === \"link\" || adData.type === \"miniapp\") {\n window.open(adData.data.url, \"_blank\");\n } else {\n console.error(\"[@adland/react] Failed to perform ad action:\", err);\n }\n }\n}\n\nexport async function performEmptyAdAction(\n slot: string,\n chainId: SlotsChain,\n baseLinkUrl: string,\n) {\n const url = `${baseLinkUrl}/slots/${slot}?chain=${chainId}`;\n if (await isMiniApp()) {\n sdk.actions.openMiniApp({ url });\n } else {\n window.open(url, \"_blank\");\n }\n}\n","import type { AdData, AdType } from \"@adland/data\";\n\nconst IMAGE_KEYS = [\"image\", \"icon\", \"pfpUrl\", \"logoURI\", \"imageUrl\"] as const;\nconst TITLE_KEYS = [\"title\", \"displayName\", \"username\", \"name\", \"symbol\"] as const;\nconst DESC_KEYS = [\"description\", \"bio\", \"text\", \"name\"] as const;\n\nfunction flatFields(data: AdData): Record<string, unknown> {\n return { ...data.data, ...(data.metadata ?? {}) };\n}\n\nexport function getAdImage(data: AdData | null): string | null {\n if (!data) return null;\n const fields = flatFields(data);\n for (const key of IMAGE_KEYS) {\n const v = fields[key];\n if (typeof v === \"string\" && v) return v;\n }\n return null;\n}\n\nexport function getAdTitle(data: AdData | null): string | null {\n if (!data) return null;\n const fields = flatFields(data);\n for (const key of TITLE_KEYS) {\n const v = fields[key];\n if (typeof v === \"string\" && v) return v;\n }\n return null;\n}\n\nexport function getAdDescription(data: AdData | null): string | null {\n if (!data) return null;\n const fields = flatFields(data);\n const title = getAdTitle(data);\n for (const key of DESC_KEYS) {\n const v = fields[key];\n if (typeof v === \"string\" && v && v !== title) return v;\n }\n return null;\n}\n\nexport function getAdType(data: AdData | null): AdType | null {\n if (!data) return null;\n return data.type as AdType;\n}\n","import { AdType } from \"@adland/data\";\nimport { ForwardRefExoticComponent, RefAttributes } from \"react\";\nimport {\n Link,\n MessageCircle,\n LayoutGrid,\n LucideProps,\n Coins,\n User,\n} from \"lucide-react\";\n\nexport const adCardIcon: Record<\n AdType,\n ForwardRefExoticComponent<\n Omit<LucideProps, \"ref\"> & RefAttributes<SVGSVGElement>\n >\n> = {\n link: Link,\n cast: MessageCircle,\n miniapp: LayoutGrid,\n token: Coins,\n farcasterProfile: User,\n};\n\nexport const adCardLabel: Record<AdType, string> = {\n link: \"Link\",\n cast: \"Cast\",\n miniapp: \"Miniapp\",\n token: \"Token\",\n farcasterProfile: \"Profile\",\n};\n\nexport const adlandApiUrl =\n process.env.NODE_ENV === \"development\"\n ? \"http://localhost:3069\"\n : \"https://api.adland.space\";\n","import type { AdAuth } from \"../types\";\n\nconst API_URL = \"https://api.0xslots.org\";\n\ninterface TrackingPayload {\n slot: string;\n chainId: number;\n auth?: AdAuth;\n context?: string;\n cid?: string | null;\n empty?: boolean;\n}\n\n/** Track which slot+url combos have already been counted this session */\nconst tracked = new Set<string>();\n\n/** Cache the auth token per session */\nlet cachedAuthToken: string | null = null;\nlet authAttempted = false;\n\n/**\n * Get a Farcaster Quick Auth token via miniapp SDK.\n */\nasync function getFarcasterToken(): Promise<string | null> {\n if (cachedAuthToken) return cachedAuthToken;\n if (authAttempted) return null;\n\n authAttempted = true;\n try {\n const sdk = await import(\"@farcaster/miniapp-sdk\").then((m) => m.default);\n const isInMiniApp = await sdk.isInMiniApp();\n if (!isInMiniApp) return null;\n\n const result = await sdk.quickAuth.getToken();\n cachedAuthToken = result.token;\n return cachedAuthToken;\n } catch {\n return null;\n }\n}\n\n/**\n * Send a tracking event through the API proxy.\n */\nasync function sendEvent(\n eventName: string,\n data: TrackingPayload,\n): Promise<void> {\n if (typeof window === \"undefined\") return;\n\n const { auth, ...eventData } = data;\n let authToken: string | null = null;\n\n if (auth === \"farcaster\") {\n authToken = await getFarcasterToken();\n }\n\n const headers: Record<string, string> = {\n \"Content-Type\": \"application/json\",\n };\n\n if (authToken) {\n headers[\"Authorization\"] = `Bearer ${authToken}`;\n }\n\n try {\n fetch(`${API_URL}/events/track`, {\n method: \"POST\",\n headers,\n body: JSON.stringify({\n event: eventName,\n url: window.location.href,\n referrer: document.referrer || undefined,\n hostname: window.location.hostname,\n data: eventData,\n authMethod: auth || \"none\",\n }),\n keepalive: true,\n }).catch(() => {});\n } catch {}\n}\n\n/**\n * Track a unique impression (once per slot per page load).\n */\nexport function trackImpression(\n element: HTMLElement | null,\n data: TrackingPayload,\n): (() => void) | undefined {\n if (!element || typeof window === \"undefined\") return;\n\n const key = `impression:${data.slot}:${window.location.href}`;\n if (tracked.has(key)) return;\n\n const observer = new IntersectionObserver(\n (entries) => {\n for (const entry of entries) {\n if (entry.isIntersecting && !tracked.has(key)) {\n tracked.add(key);\n sendEvent(\"impression\", data);\n observer.disconnect();\n }\n }\n },\n { threshold: 0.5 },\n );\n\n observer.observe(element);\n\n return () => observer.disconnect();\n}\n\n/**\n * Track a click event.\n */\nexport function trackClick(eventName: string, data: TrackingPayload): void {\n sendEvent(eventName, data);\n}\n"],"mappings":"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","names":["import_sdk","import_react","AdDataQueryError","import_react","import_viem","sdk","sdk"]}
|
package/dist/index.d.cts
CHANGED
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -29,9 +29,15 @@ function createReadClient(chainId, rpcUrl) {
|
|
|
29
29
|
});
|
|
30
30
|
return new SlotsClient({ chainId, publicClient });
|
|
31
31
|
}
|
|
32
|
+
function extractCid(uri) {
|
|
33
|
+
if (uri.startsWith("ipfs://")) return uri.slice(7);
|
|
34
|
+
if (uri.startsWith("Qm") || uri.startsWith("bafy")) return uri;
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
32
37
|
var fetchAdFromURI = async (uri) => {
|
|
33
38
|
if (!uri) throw new Error("NO_AD" /* NO_AD */);
|
|
34
|
-
const
|
|
39
|
+
const cid = extractCid(uri);
|
|
40
|
+
const url = cid ? `${IPFS_GATEWAY}${cid}` : uri;
|
|
35
41
|
const res = await fetch(url, {
|
|
36
42
|
method: "GET",
|
|
37
43
|
headers: { Accept: "application/json" }
|
|
@@ -42,7 +48,7 @@ var fetchAdFromURI = async (uri) => {
|
|
|
42
48
|
}
|
|
43
49
|
const data = await res.json();
|
|
44
50
|
if (data.error) throw new Error(data.error);
|
|
45
|
-
return data;
|
|
51
|
+
return { data, cid };
|
|
46
52
|
};
|
|
47
53
|
var fetchMetadataURI = async (client, slotAddress) => {
|
|
48
54
|
const info = await client.getSlotInfo(slotAddress);
|
|
@@ -374,15 +380,14 @@ async function sendEvent(eventName, data) {
|
|
|
374
380
|
}
|
|
375
381
|
function trackImpression(element, data) {
|
|
376
382
|
if (!element || typeof window === "undefined") return;
|
|
377
|
-
const
|
|
378
|
-
const key = `${eventName}:${data.slot}:${window.location.href}`;
|
|
383
|
+
const key = `impression:${data.slot}:${window.location.href}`;
|
|
379
384
|
if (tracked.has(key)) return;
|
|
380
385
|
const observer = new IntersectionObserver(
|
|
381
386
|
(entries) => {
|
|
382
387
|
for (const entry of entries) {
|
|
383
388
|
if (entry.isIntersecting && !tracked.has(key)) {
|
|
384
389
|
tracked.add(key);
|
|
385
|
-
sendEvent(
|
|
390
|
+
sendEvent("impression", data);
|
|
386
391
|
observer.disconnect();
|
|
387
392
|
}
|
|
388
393
|
}
|
|
@@ -415,7 +420,7 @@ function Ad({
|
|
|
415
420
|
[slot, chainId, rpcUrl]
|
|
416
421
|
);
|
|
417
422
|
const {
|
|
418
|
-
data:
|
|
423
|
+
data: fetchResult,
|
|
419
424
|
isLoading,
|
|
420
425
|
error
|
|
421
426
|
} = useFetch(
|
|
@@ -431,42 +436,41 @@ function Ad({
|
|
|
431
436
|
},
|
|
432
437
|
{ enabled: !!slot && !staticData }
|
|
433
438
|
);
|
|
434
|
-
const adData = staticData ??
|
|
439
|
+
const adData = staticData ?? fetchResult?.data ?? null;
|
|
440
|
+
const cid = fetchResult?.cid ?? null;
|
|
435
441
|
const isEmpty = !adData && !isLoading && (error instanceof Error ? error.message === "NO_AD" /* NO_AD */ : !error);
|
|
436
442
|
useEffect2(() => {
|
|
437
|
-
if (!
|
|
438
|
-
return trackImpression(ref.current, { slot, chainId, auth, context });
|
|
439
|
-
}, [adData, slot, chainId, auth, context]);
|
|
440
|
-
useEffect2(() => {
|
|
441
|
-
if (!isEmpty || !slot) return;
|
|
443
|
+
if (!slot || !adData && !isEmpty) return;
|
|
442
444
|
return trackImpression(ref.current, {
|
|
443
445
|
slot,
|
|
444
446
|
chainId,
|
|
445
447
|
auth,
|
|
446
448
|
context,
|
|
447
|
-
|
|
449
|
+
cid,
|
|
450
|
+
empty: isEmpty
|
|
448
451
|
});
|
|
449
|
-
}, [isEmpty, slot, chainId, auth, context]);
|
|
452
|
+
}, [adData, isEmpty, slot, chainId, auth, context, cid]);
|
|
450
453
|
const onClick = useCallback(
|
|
451
454
|
(e) => {
|
|
452
455
|
const target = e.target;
|
|
453
456
|
const isInteractive = target.tagName === "A" || target.tagName === "BUTTON" || target.closest("a") !== null || target.closest("button") !== null;
|
|
454
457
|
if (isInteractive) return;
|
|
455
458
|
if (adData) {
|
|
456
|
-
if (slot) trackClick("click", { slot, chainId, auth, context });
|
|
459
|
+
if (slot) trackClick("click", { slot, chainId, auth, context, cid });
|
|
457
460
|
performAdAction(adData);
|
|
458
461
|
} else if (isEmpty && slot) {
|
|
459
462
|
trackClick("click-empty", { slot, chainId, auth, context });
|
|
460
463
|
performEmptyAdAction(slot, chainId, baseLinkUrl);
|
|
461
464
|
}
|
|
462
465
|
},
|
|
463
|
-
[adData, isEmpty, slot, chainId, baseLinkUrl]
|
|
466
|
+
[adData, isEmpty, slot, chainId, baseLinkUrl, cid]
|
|
464
467
|
);
|
|
465
468
|
return /* @__PURE__ */ jsx(
|
|
466
469
|
AdContext.Provider,
|
|
467
470
|
{
|
|
468
471
|
value: {
|
|
469
472
|
data: adData ?? null,
|
|
473
|
+
cid,
|
|
470
474
|
isLoading: !!slot && !staticData && isLoading,
|
|
471
475
|
error,
|
|
472
476
|
isEmpty,
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/components/Ad.tsx","../src/fetch.ts","../src/types.ts","../src/hooks/useAdContext.ts","../src/hooks/useFetch.ts","../src/utils/fetchCache.ts","../src/utils/ad-actions.ts","../src/utils/ad-fields.ts","../src/utils/constants.ts","../src/utils/tracking.ts"],"sourcesContent":["import { SlotsChain } from \"@0xslots/sdk\";\nimport type { AdData } from \"@adland/data\";\nimport { useCallback, useEffect, useMemo, useRef } from \"react\";\n\nimport { createReadClient, fetchAdFromURI, fetchMetadataURI } from \"../fetch\";\nimport { AdContext, useAd } from \"../hooks/useAdContext\";\nimport { useFetch } from \"../hooks/useFetch\";\nimport { AdDataQueryError, type AdProps } from \"../types\";\nimport { performAdAction, performEmptyAdAction } from \"../utils/ad-actions\";\nimport {\n getAdDescription,\n getAdImage,\n getAdTitle,\n getAdType,\n} from \"../utils/ad-fields\";\nimport { adCardIcon, adCardLabel } from \"../utils/constants\";\nimport { trackClick, trackImpression } from \"../utils/tracking\";\n\n// ─── Root component ──────────────────────────────────────────────────────────\n\n/**\n * Root Ad component — compound pattern.\n *\n * @example\n * ```tsx\n * <Ad slot=\"0xabc...123\" className=\"rounded-md border p-3\">\n * <AdImage className=\"size-10 rounded-md\" />\n * <AdTitle className=\"text-sm font-medium\" />\n * <AdDescription className=\"text-xs text-muted-foreground\" />\n * <AdBadge />\n * </Ad>\n * ```\n */\nexport function Ad({\n slot,\n data: staticData,\n chainId = SlotsChain.BASE,\n rpcUrl,\n baseLinkUrl = \"https://app.0xslots.org\",\n auth = \"none\",\n context,\n children,\n ...props\n}: AdProps) {\n const ref = useRef<HTMLDivElement>(null);\n\n const client = useMemo(\n () => (slot ? createReadClient(chainId, rpcUrl) : null),\n [slot, chainId, rpcUrl],\n );\n\n const {\n data: fetchedData,\n isLoading,\n error,\n } = useFetch<AdData>(\n `ad-data-${slot}`,\n async () => {\n if (!client || !slot) throw new Error(AdDataQueryError.NO_AD);\n const uri = await fetchMetadataURI(client, slot);\n if (!uri) {\n console.info(\"[Ad] no metadata URI found for slot\", slot);\n return null;\n }\n return fetchAdFromURI(uri);\n },\n { enabled: !!slot && !staticData },\n );\n\n const adData = staticData ?? fetchedData;\n\n const isEmpty =\n !adData &&\n !isLoading &&\n (error instanceof Error\n ? error.message === AdDataQueryError.NO_AD\n : !error);\n\n // Track impression when ad is visible in viewport (once per slot per page load)\n useEffect(() => {\n if (!adData || !slot) return;\n return trackImpression(ref.current, { slot, chainId, auth, context });\n }, [adData, slot, chainId, auth, context]);\n\n // Track impression for empty slots too\n useEffect(() => {\n if (!isEmpty || !slot) return;\n return trackImpression(ref.current, {\n slot,\n chainId,\n auth,\n context,\n empty: true,\n });\n }, [isEmpty, slot, chainId, auth, context]);\n\n const onClick = useCallback(\n (e: React.MouseEvent<HTMLDivElement>) => {\n const target = e.target as HTMLElement;\n const isInteractive =\n target.tagName === \"A\" ||\n target.tagName === \"BUTTON\" ||\n target.closest(\"a\") !== null ||\n target.closest(\"button\") !== null;\n if (isInteractive) return;\n\n if (adData) {\n if (slot) trackClick(\"click\", { slot, chainId, auth, context });\n performAdAction(adData);\n } else if (isEmpty && slot) {\n trackClick(\"click-empty\", { slot, chainId, auth, context });\n performEmptyAdAction(slot, chainId, baseLinkUrl);\n }\n },\n [adData, isEmpty, slot, chainId, baseLinkUrl],\n );\n\n return (\n <AdContext.Provider\n value={{\n data: adData ?? null,\n isLoading: !!slot && !staticData && isLoading,\n error,\n isEmpty,\n slot,\n baseLinkUrl,\n chainId,\n }}\n >\n <div ref={ref} onClick={onClick} {...props}>\n {children}\n </div>\n </AdContext.Provider>\n );\n}\n\n// ─── Sub-components ──────────────────────────────────────────────────────────\n\nexport interface AdImageProps\n extends React.ImgHTMLAttributes<HTMLImageElement> {\n fallback?: React.ReactNode;\n}\n\nexport function AdImage({ fallback, ...props }: AdImageProps) {\n const { data } = useAd();\n const src = getAdImage(data);\n if (!src) return fallback ? <>{fallback}</> : null;\n return <img src={src} alt=\"\" {...props} />;\n}\n\nexport interface AdTitleProps\n extends React.HTMLAttributes<HTMLParagraphElement> {\n fallback?: React.ReactNode;\n}\n\nexport function AdTitle({ fallback, children, ...props }: AdTitleProps) {\n const { data } = useAd();\n const title = getAdTitle(data);\n if (!title) return fallback ? <>{fallback}</> : null;\n return <p {...props}>{children ?? title}</p>;\n}\n\nexport interface AdDescriptionProps\n extends React.HTMLAttributes<HTMLParagraphElement> {\n fallback?: React.ReactNode;\n}\n\nexport function AdDescription({\n fallback,\n children,\n ...props\n}: AdDescriptionProps) {\n const { data } = useAd();\n const description = getAdDescription(data);\n if (!description) return fallback ? <>{fallback}</> : null;\n return <p {...props}>{children ?? description}</p>;\n}\n\nexport interface AdBadgeProps extends React.HTMLAttributes<HTMLSpanElement> {}\n\nexport function AdBadge({ children, ...props }: AdBadgeProps) {\n const { data } = useAd();\n const type = getAdType(data);\n if (!type) return null;\n const Icon = adCardIcon[type];\n const label = adCardLabel[type];\n return (\n <span {...props}>\n {children ?? (\n <>\n {Icon && <Icon className=\"size-3\" />}\n {label}\n </>\n )}\n </span>\n );\n}\n\nexport interface AdLabelProps extends React.HTMLAttributes<HTMLSpanElement> {}\n\nexport function AdLabel({ children, ...props }: AdLabelProps) {\n return <span {...props}>{children ?? \"AD\"}</span>;\n}\n\n// ─── State components ────────────────────────────────────────────────────────\n\nexport interface AdStatusProps extends React.HTMLAttributes<HTMLDivElement> {\n children?: React.ReactNode;\n}\n\nexport function AdLoading({ children, ...props }: AdStatusProps) {\n const { isLoading } = useAd();\n if (!isLoading) return null;\n return <div {...props}>{children ?? \"Loading...\"}</div>;\n}\n\nexport function AdEmpty({ children, ...props }: AdStatusProps) {\n const { isEmpty } = useAd();\n if (!isEmpty) return null;\n return <div {...props}>{children ?? \"Your ad here\"}</div>;\n}\n\nexport function AdError({ children, ...props }: AdStatusProps) {\n const { error, isEmpty } = useAd();\n if (!error || isEmpty) return null;\n return <div {...props}>{children ?? \"Error loading ad\"}</div>;\n}\n\nexport function AdLoaded({ children, ...props }: AdStatusProps) {\n const { data } = useAd();\n if (!data) return null;\n return <div {...props}>{children}</div>;\n}\n","import { SlotsClient, type SlotsChain } from \"@0xslots/sdk\";\nimport { type Address, createPublicClient, http } from \"viem\";\nimport { base, baseSepolia } from \"viem/chains\";\n\nimport { AdDataQueryError } from \"./types\";\n\nconst IPFS_GATEWAY = \"https://amethyst-representative-mandrill-369.mypinata.cloud/ipfs/\";\n\nconst viemChains: Record<number, typeof base> = {\n 8453: base,\n 84532: baseSepolia,\n};\n\n/**\n * Create a read-only SlotsClient for a given chain.\n */\nexport function createReadClient(\n chainId: SlotsChain,\n rpcUrl?: string,\n): SlotsClient {\n const chain = viemChains[chainId];\n if (!chain) throw new Error(`Unsupported chain: ${chainId}`);\n\n const publicClient = createPublicClient({\n chain,\n transport: http(rpcUrl),\n });\n\n return new SlotsClient({ chainId, publicClient });\n}\n\n/**\n * Fetch ad content from a metadata URI (IPFS or HTTP)\n */\nexport const fetchAdFromURI = async (uri: string) => {\n if (!uri) throw new Error(AdDataQueryError.NO_AD);\n\n const url = uri.startsWith(\"ipfs://\")\n ? `${IPFS_GATEWAY}${uri.slice(7)}`\n : uri;\n\n const res = await fetch(url, {\n method: \"GET\",\n headers: { Accept: \"application/json\" },\n });\n\n if (!res.ok) {\n if (res.status === 404) throw new Error(AdDataQueryError.NO_AD);\n throw new Error(AdDataQueryError.ERROR);\n }\n\n const data = await res.json();\n if (data.error) throw new Error(data.error);\n\n return data;\n};\n\n/**\n * Fetch the metadata URI for a slot using the SDK.\n */\nexport const fetchMetadataURI = async (\n client: SlotsClient,\n slotAddress: string,\n): Promise<string> => {\n const info = await client.getSlotInfo(slotAddress as Address);\n const moduleAddress = (info as { module: Address }).module;\n\n if (\n !moduleAddress ||\n moduleAddress === \"0x0000000000000000000000000000000000000000\"\n ) {\n return \"\";\n }\n\n return client.modules.metadata.getURI(\n moduleAddress,\n slotAddress as Address,\n );\n};\n","import type { SlotsChain } from \"@0xslots/sdk\";\nimport type { AdData } from \"@adland/data\";\n\n/**\n * Optional analytics context passed with every impression/click event.\n * Only include dimensions you'll filter or group by — Umami already\n * captures hostname, URL, referrer, country, device, OS, and browser.\n */\n/** Authentication method for verified impressions */\nexport type AdAuth = \"farcaster\" | \"none\";\n\nexport interface AdProps extends React.HTMLAttributes<HTMLDivElement> {\n /**\n * The slot contract address (0xSlots v3).\n * Required when fetching from chain. Omit when passing static `data`.\n */\n slot?: string;\n /**\n * Chain ID for on-chain reads. Defaults to BASE (8453).\n */\n chainId?: SlotsChain;\n /**\n * Static ad data. When provided, skips on-chain fetching.\n */\n data?: AdData;\n /**\n * Optional RPC URL override. If not provided, uses public RPC for the chain.\n */\n rpcUrl?: string;\n /**\n * Base URL for the \"Your ad here\" CTA link.\n * Empty-state click navigates to `${baseLinkUrl}/slots/${slot}?chain=${chainId}`.\n * Defaults to \"https://app.0xslots.org\".\n */\n baseLinkUrl?: string;\n /**\n * Authentication method for verified impressions. Default: \"none\".\n * \"farcaster\" uses Quick Auth — server resolves FID + address automatically.\n */\n auth?: AdAuth;\n /**\n * Placement context sent with events (e.g. \"carousel\", \"sidebar\", \"frame\").\n */\n context?: string;\n /**\n * Compound children (AdImage, AdTitle, etc.)\n */\n children?: React.ReactNode;\n}\n\nexport enum AdDataQueryError {\n NO_AD = \"NO_AD\",\n ERROR = \"ERROR\",\n}\n","import type { SlotsChain } from \"@0xslots/sdk\";\nimport type { AdData } from \"@adland/data\";\nimport { createContext, useContext } from \"react\";\n\nexport interface AdContextValue {\n data: AdData | null;\n isLoading: boolean;\n error: unknown;\n isEmpty: boolean;\n slot?: string;\n baseLinkUrl: string;\n chainId: SlotsChain;\n}\n\nexport const AdContext = createContext<AdContextValue | null>(null);\n\nexport function useAd(): AdContextValue {\n const ctx = useContext(AdContext);\n if (!ctx) throw new Error(\"useAd must be used within an <Ad> component\");\n return ctx;\n}\n","import { useEffect, useState } from \"react\";\nimport FetchCache from \"../utils/fetchCache\";\n\ntype Status = \"idle\" | \"loading\" | \"success\" | \"error\";\n\nconst globalCache = new FetchCache();\n\nexport const fetchCache = {\n clear: () => globalCache.clear(),\n};\n\nexport function useFetch<T>(\n key: string,\n fetcher: () => Promise<T>,\n opts?: {\n enabled?: boolean;\n ttl?: number; // ms\n },\n) {\n const { enabled = true, ttl = 0 } = opts ?? {};\n\n // Get cached data from global cache\n const getCachedData = (): T | null => {\n return globalCache.get<T>(key, ttl || undefined);\n };\n\n const hasCachedData = () => {\n return globalCache.has(key, ttl || undefined);\n };\n\n // Check if there's an active fetch for this key (dedupe concurrent requests)\n const getActiveFetch = (): Promise<T> | undefined => {\n return globalCache.getActiveFetch<T>(key);\n };\n\n const cachedData = getCachedData();\n const [data, setData] = useState<T | null>(cachedData);\n const [error, setError] = useState<unknown>(null);\n // If we have cached data, start with success status to avoid showing loader\n const [status, setStatus] = useState<Status>(cachedData ? \"success\" : \"idle\");\n\n const refetch = async () => {\n // ALWAYS check cache first - never show loading if we have valid cached data\n const cached = getCachedData();\n if (cached) {\n // Already have valid cached data, ensure state is correct\n setData(cached);\n setStatus(\"success\");\n return cached;\n }\n\n // Check if there's already an active fetch for this key (dedupe)\n const activeFetch = getActiveFetch();\n if (activeFetch) {\n try {\n const res = await activeFetch;\n setData(res);\n setStatus(\"success\");\n return res;\n } catch (e) {\n setError(e);\n setStatus(\"error\");\n throw e;\n }\n }\n\n // Only set loading if we actually need to fetch\n setStatus(\"loading\");\n setError(null);\n\n try {\n // Create fetch promise and store it for deduplication\n const fetchPromise = fetcher();\n globalCache.setActiveFetch(key, fetchPromise);\n\n const res = await fetchPromise;\n globalCache.set(key, res);\n setData(res);\n setStatus(\"success\");\n return res;\n } catch (e) {\n setError(e);\n setStatus(\"error\");\n throw e;\n }\n };\n\n useEffect(() => {\n if (!enabled) return;\n\n const cached = getCachedData();\n\n if (cached) {\n setData(cached);\n setStatus(\"success\");\n return;\n }\n\n const activeFetch = getActiveFetch();\n if (activeFetch) {\n setStatus(\"loading\");\n activeFetch\n .then((res) => {\n setData(res);\n setStatus(\"success\");\n })\n .catch((e) => {\n setError(e);\n setStatus(\"error\");\n });\n return;\n }\n\n if (status !== \"loading\" && status !== \"success\") {\n refetch();\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [key, enabled]);\n\n const hasValidCache = hasCachedData();\n const isLoading = status === \"loading\" && !hasValidCache;\n\n return {\n data,\n error,\n status,\n isIdle: status === \"idle\",\n isLoading,\n isSuccess: status === \"success\",\n isError: status === \"error\",\n refetch,\n };\n}\n","class FetchCache {\n private cache = new Map<string, { data: unknown; ts: number }>();\n private activeFetches = new Map<string, Promise<unknown>>();\n\n get<T>(key: string, ttl?: number): T | null {\n const cached = this.cache.get(key);\n if (!cached) return null;\n\n if (ttl && Date.now() - cached.ts > ttl) {\n // Cache expired, remove it\n this.cache.delete(key);\n return null;\n }\n\n return cached.data as T;\n }\n\n has(key: string, ttl?: number): boolean {\n const cached = this.cache.get(key);\n if (!cached) return false;\n\n if (ttl && Date.now() - cached.ts > ttl) {\n this.cache.delete(key);\n return false;\n }\n\n return true;\n }\n\n set<T>(key: string, data: T): void {\n this.cache.set(key, { data, ts: Date.now() });\n }\n\n getActiveFetch<T>(key: string): Promise<T> | undefined {\n return this.activeFetches.get(key) as Promise<T> | undefined;\n }\n\n setActiveFetch<T>(key: string, promise: Promise<T>): void {\n this.activeFetches.set(key, promise);\n promise.finally(() => {\n this.activeFetches.delete(key);\n });\n }\n\n clear(): void {\n this.cache.clear();\n this.activeFetches.clear();\n }\n}\n\nexport default FetchCache;\n","import type { SlotsChain } from \"@0xslots/sdk\";\nimport type { AdData } from \"@adland/data\";\nimport sdk from \"@farcaster/miniapp-sdk\";\nimport { getAddress } from \"viem\";\n\n// Cache the miniapp check at module level — resolved once, sync thereafter\nlet _isMiniApp: boolean | null = null;\nconst _miniAppPromise = sdk\n .isInMiniApp()\n .then((v) => {\n _isMiniApp = v;\n return v;\n })\n .catch(() => {\n _isMiniApp = false;\n return false;\n });\n\nasync function isMiniApp(): Promise<boolean> {\n if (_isMiniApp !== null) return _isMiniApp;\n return _miniAppPromise;\n}\n\nexport function performAdAction(adData: AdData) {\n try {\n switch (adData.type) {\n case \"link\":\n sdk.actions.openUrl(adData.data.url);\n break;\n case \"cast\":\n sdk.actions.viewCast({ hash: adData.data.hash });\n break;\n case \"miniapp\":\n sdk.actions.openMiniApp({ url: adData.data.url });\n break;\n case \"token\": {\n const address = adData.data.address;\n const chainId = adData.data.chainId;\n const buyToken = `eip155:${chainId}/erc20:${getAddress(address)}`;\n sdk.actions.swapToken({ buyToken });\n break;\n }\n case \"farcasterProfile\":\n sdk.actions.viewProfile({\n fid: Number.parseInt(adData.data.fid, 10),\n });\n break;\n }\n } catch (err) {\n // Fallback for web (non-miniapp) context\n if (adData.type === \"link\" || adData.type === \"miniapp\") {\n window.open(adData.data.url, \"_blank\");\n } else {\n console.error(\"[@adland/react] Failed to perform ad action:\", err);\n }\n }\n}\n\nexport async function performEmptyAdAction(\n slot: string,\n chainId: SlotsChain,\n baseLinkUrl: string,\n) {\n const url = `${baseLinkUrl}/slots/${slot}?chain=${chainId}`;\n if (await isMiniApp()) {\n sdk.actions.openMiniApp({ url });\n } else {\n window.open(url, \"_blank\");\n }\n}\n","import type { AdData, AdType } from \"@adland/data\";\n\nconst IMAGE_KEYS = [\"image\", \"icon\", \"pfpUrl\", \"logoURI\", \"imageUrl\"] as const;\nconst TITLE_KEYS = [\"title\", \"displayName\", \"username\", \"name\", \"symbol\"] as const;\nconst DESC_KEYS = [\"description\", \"bio\", \"text\", \"name\"] as const;\n\nfunction flatFields(data: AdData): Record<string, unknown> {\n return { ...data.data, ...(data.metadata ?? {}) };\n}\n\nexport function getAdImage(data: AdData | null): string | null {\n if (!data) return null;\n const fields = flatFields(data);\n for (const key of IMAGE_KEYS) {\n const v = fields[key];\n if (typeof v === \"string\" && v) return v;\n }\n return null;\n}\n\nexport function getAdTitle(data: AdData | null): string | null {\n if (!data) return null;\n const fields = flatFields(data);\n for (const key of TITLE_KEYS) {\n const v = fields[key];\n if (typeof v === \"string\" && v) return v;\n }\n return null;\n}\n\nexport function getAdDescription(data: AdData | null): string | null {\n if (!data) return null;\n const fields = flatFields(data);\n const title = getAdTitle(data);\n for (const key of DESC_KEYS) {\n const v = fields[key];\n if (typeof v === \"string\" && v && v !== title) return v;\n }\n return null;\n}\n\nexport function getAdType(data: AdData | null): AdType | null {\n if (!data) return null;\n return data.type as AdType;\n}\n","import { AdType } from \"@adland/data\";\nimport { ForwardRefExoticComponent, RefAttributes } from \"react\";\nimport {\n Link,\n MessageCircle,\n LayoutGrid,\n LucideProps,\n Coins,\n User,\n} from \"lucide-react\";\n\nexport const adCardIcon: Record<\n AdType,\n ForwardRefExoticComponent<\n Omit<LucideProps, \"ref\"> & RefAttributes<SVGSVGElement>\n >\n> = {\n link: Link,\n cast: MessageCircle,\n miniapp: LayoutGrid,\n token: Coins,\n farcasterProfile: User,\n};\n\nexport const adCardLabel: Record<AdType, string> = {\n link: \"Link\",\n cast: \"Cast\",\n miniapp: \"Miniapp\",\n token: \"Token\",\n farcasterProfile: \"Profile\",\n};\n\nexport const adlandApiUrl =\n process.env.NODE_ENV === \"development\"\n ? \"http://localhost:3069\"\n : \"https://api.adland.space\";\n","import type { AdAuth } from \"../types\";\n\nconst API_URL = \"https://api.0xslots.org\";\n\ninterface TrackingPayload {\n slot: string;\n chainId: number;\n auth?: AdAuth;\n context?: string;\n empty?: boolean;\n}\n\n/** Track which slot+url combos have already been counted this session */\nconst tracked = new Set<string>();\n\n/** Cache the auth token per session */\nlet cachedAuthToken: string | null = null;\nlet authAttempted = false;\n\n/**\n * Get a Farcaster Quick Auth token via miniapp SDK.\n */\nasync function getFarcasterToken(): Promise<string | null> {\n if (cachedAuthToken) return cachedAuthToken;\n if (authAttempted) return null;\n\n authAttempted = true;\n try {\n const sdk = await import(\"@farcaster/miniapp-sdk\").then((m) => m.default);\n const isInMiniApp = await sdk.isInMiniApp();\n if (!isInMiniApp) return null;\n\n const result = await sdk.quickAuth.getToken();\n cachedAuthToken = result.token;\n return cachedAuthToken;\n } catch {\n return null;\n }\n}\n\n/**\n * Send a tracking event through the API proxy.\n */\nasync function sendEvent(\n eventName: string,\n data: TrackingPayload,\n): Promise<void> {\n if (typeof window === \"undefined\") return;\n\n const { auth, ...eventData } = data;\n let authToken: string | null = null;\n\n if (auth === \"farcaster\") {\n authToken = await getFarcasterToken();\n }\n\n const headers: Record<string, string> = {\n \"Content-Type\": \"application/json\",\n };\n\n if (authToken) {\n headers[\"Authorization\"] = `Bearer ${authToken}`;\n }\n\n try {\n fetch(`${API_URL}/events/track`, {\n method: \"POST\",\n headers,\n body: JSON.stringify({\n event: eventName,\n url: window.location.href,\n referrer: document.referrer || undefined,\n hostname: window.location.hostname,\n data: eventData,\n authMethod: auth || \"none\",\n }),\n keepalive: true,\n }).catch(() => {});\n } catch {}\n}\n\n/**\n * Track a unique impression (once per slot per page load).\n */\nexport function trackImpression(\n element: HTMLElement | null,\n data: TrackingPayload,\n): (() => void) | undefined {\n if (!element || typeof window === \"undefined\") return;\n\n const eventName = data.empty ? \"impression-empty\" : \"impression\";\n const key = `${eventName}:${data.slot}:${window.location.href}`;\n if (tracked.has(key)) return;\n\n const observer = new IntersectionObserver(\n (entries) => {\n for (const entry of entries) {\n if (entry.isIntersecting && !tracked.has(key)) {\n tracked.add(key);\n sendEvent(eventName, data);\n observer.disconnect();\n }\n }\n },\n { threshold: 0.5 },\n );\n\n observer.observe(element);\n\n return () => observer.disconnect();\n}\n\n/**\n * Track a click event.\n */\nexport function trackClick(eventName: string, data: TrackingPayload): void {\n sendEvent(eventName, data);\n}\n"],"mappings":"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","names":["useEffect","AdDataQueryError","sdk","useEffect"]}
|
|
1
|
+
{"version":3,"sources":["../src/components/Ad.tsx","../src/fetch.ts","../src/types.ts","../src/hooks/useAdContext.ts","../src/hooks/useFetch.ts","../src/utils/fetchCache.ts","../src/utils/ad-actions.ts","../src/utils/ad-fields.ts","../src/utils/constants.ts","../src/utils/tracking.ts"],"sourcesContent":["import { SlotsChain } from \"@0xslots/sdk\";\nimport type { AdData } from \"@adland/data\";\nimport { useCallback, useEffect, useMemo, useRef } from \"react\";\n\nimport { createReadClient, fetchAdFromURI, fetchMetadataURI } from \"../fetch\";\nimport { AdContext, useAd } from \"../hooks/useAdContext\";\nimport { useFetch } from \"../hooks/useFetch\";\nimport { AdDataQueryError, type AdProps } from \"../types\";\nimport { performAdAction, performEmptyAdAction } from \"../utils/ad-actions\";\nimport {\n getAdDescription,\n getAdImage,\n getAdTitle,\n getAdType,\n} from \"../utils/ad-fields\";\nimport { adCardIcon, adCardLabel } from \"../utils/constants\";\nimport { trackClick, trackImpression } from \"../utils/tracking\";\n\n// ─── Root component ──────────────────────────────────────────────────────────\n\n/**\n * Root Ad component — compound pattern.\n *\n * @example\n * ```tsx\n * <Ad slot=\"0xabc...123\" className=\"rounded-md border p-3\">\n * <AdImage className=\"size-10 rounded-md\" />\n * <AdTitle className=\"text-sm font-medium\" />\n * <AdDescription className=\"text-xs text-muted-foreground\" />\n * <AdBadge />\n * </Ad>\n * ```\n */\nexport function Ad({\n slot,\n data: staticData,\n chainId = SlotsChain.BASE,\n rpcUrl,\n baseLinkUrl = \"https://app.0xslots.org\",\n auth = \"none\",\n context,\n children,\n ...props\n}: AdProps) {\n const ref = useRef<HTMLDivElement>(null);\n\n const client = useMemo(\n () => (slot ? createReadClient(chainId, rpcUrl) : null),\n [slot, chainId, rpcUrl],\n );\n\n const {\n data: fetchResult,\n isLoading,\n error,\n } = useFetch<{ data: AdData; cid: string | null } | null>(\n `ad-data-${slot}`,\n async () => {\n if (!client || !slot) throw new Error(AdDataQueryError.NO_AD);\n const uri = await fetchMetadataURI(client, slot);\n if (!uri) {\n console.info(\"[Ad] no metadata URI found for slot\", slot);\n return null;\n }\n return fetchAdFromURI(uri);\n },\n { enabled: !!slot && !staticData },\n );\n\n const adData = staticData ?? fetchResult?.data ?? null;\n const cid = fetchResult?.cid ?? null;\n\n const isEmpty =\n !adData &&\n !isLoading &&\n (error instanceof Error\n ? error.message === AdDataQueryError.NO_AD\n : !error);\n\n // Track impression when ad or empty state is visible in viewport\n useEffect(() => {\n if (!slot || (!adData && !isEmpty)) return;\n return trackImpression(ref.current, {\n slot,\n chainId,\n auth,\n context,\n cid,\n empty: isEmpty,\n });\n }, [adData, isEmpty, slot, chainId, auth, context, cid]);\n\n const onClick = useCallback(\n (e: React.MouseEvent<HTMLDivElement>) => {\n const target = e.target as HTMLElement;\n const isInteractive =\n target.tagName === \"A\" ||\n target.tagName === \"BUTTON\" ||\n target.closest(\"a\") !== null ||\n target.closest(\"button\") !== null;\n if (isInteractive) return;\n\n if (adData) {\n if (slot) trackClick(\"click\", { slot, chainId, auth, context, cid });\n performAdAction(adData);\n } else if (isEmpty && slot) {\n trackClick(\"click-empty\", { slot, chainId, auth, context });\n performEmptyAdAction(slot, chainId, baseLinkUrl);\n }\n },\n [adData, isEmpty, slot, chainId, baseLinkUrl, cid],\n );\n\n return (\n <AdContext.Provider\n value={{\n data: adData ?? null,\n cid,\n isLoading: !!slot && !staticData && isLoading,\n error,\n isEmpty,\n slot,\n baseLinkUrl,\n chainId,\n }}\n >\n <div ref={ref} onClick={onClick} {...props}>\n {children}\n </div>\n </AdContext.Provider>\n );\n}\n\n// ─── Sub-components ──────────────────────────────────────────────────────────\n\nexport interface AdImageProps\n extends React.ImgHTMLAttributes<HTMLImageElement> {\n fallback?: React.ReactNode;\n}\n\nexport function AdImage({ fallback, ...props }: AdImageProps) {\n const { data } = useAd();\n const src = getAdImage(data);\n if (!src) return fallback ? <>{fallback}</> : null;\n return <img src={src} alt=\"\" {...props} />;\n}\n\nexport interface AdTitleProps\n extends React.HTMLAttributes<HTMLParagraphElement> {\n fallback?: React.ReactNode;\n}\n\nexport function AdTitle({ fallback, children, ...props }: AdTitleProps) {\n const { data } = useAd();\n const title = getAdTitle(data);\n if (!title) return fallback ? <>{fallback}</> : null;\n return <p {...props}>{children ?? title}</p>;\n}\n\nexport interface AdDescriptionProps\n extends React.HTMLAttributes<HTMLParagraphElement> {\n fallback?: React.ReactNode;\n}\n\nexport function AdDescription({\n fallback,\n children,\n ...props\n}: AdDescriptionProps) {\n const { data } = useAd();\n const description = getAdDescription(data);\n if (!description) return fallback ? <>{fallback}</> : null;\n return <p {...props}>{children ?? description}</p>;\n}\n\nexport interface AdBadgeProps extends React.HTMLAttributes<HTMLSpanElement> {}\n\nexport function AdBadge({ children, ...props }: AdBadgeProps) {\n const { data } = useAd();\n const type = getAdType(data);\n if (!type) return null;\n const Icon = adCardIcon[type];\n const label = adCardLabel[type];\n return (\n <span {...props}>\n {children ?? (\n <>\n {Icon && <Icon className=\"size-3\" />}\n {label}\n </>\n )}\n </span>\n );\n}\n\nexport interface AdLabelProps extends React.HTMLAttributes<HTMLSpanElement> {}\n\nexport function AdLabel({ children, ...props }: AdLabelProps) {\n return <span {...props}>{children ?? \"AD\"}</span>;\n}\n\n// ─── State components ────────────────────────────────────────────────────────\n\nexport interface AdStatusProps extends React.HTMLAttributes<HTMLDivElement> {\n children?: React.ReactNode;\n}\n\nexport function AdLoading({ children, ...props }: AdStatusProps) {\n const { isLoading } = useAd();\n if (!isLoading) return null;\n return <div {...props}>{children ?? \"Loading...\"}</div>;\n}\n\nexport function AdEmpty({ children, ...props }: AdStatusProps) {\n const { isEmpty } = useAd();\n if (!isEmpty) return null;\n return <div {...props}>{children ?? \"Your ad here\"}</div>;\n}\n\nexport function AdError({ children, ...props }: AdStatusProps) {\n const { error, isEmpty } = useAd();\n if (!error || isEmpty) return null;\n return <div {...props}>{children ?? \"Error loading ad\"}</div>;\n}\n\nexport function AdLoaded({ children, ...props }: AdStatusProps) {\n const { data } = useAd();\n if (!data) return null;\n return <div {...props}>{children}</div>;\n}\n","import { SlotsClient, type SlotsChain } from \"@0xslots/sdk\";\nimport type { AdData } from \"@adland/data\";\nimport { type Address, createPublicClient, http } from \"viem\";\nimport { base, baseSepolia } from \"viem/chains\";\n\nimport { AdDataQueryError } from \"./types\";\n\nconst IPFS_GATEWAY = \"https://amethyst-representative-mandrill-369.mypinata.cloud/ipfs/\";\n\nconst viemChains: Record<number, typeof base> = {\n 8453: base,\n 84532: baseSepolia,\n};\n\n/**\n * Create a read-only SlotsClient for a given chain.\n */\nexport function createReadClient(\n chainId: SlotsChain,\n rpcUrl?: string,\n): SlotsClient {\n const chain = viemChains[chainId];\n if (!chain) throw new Error(`Unsupported chain: ${chainId}`);\n\n const publicClient = createPublicClient({\n chain,\n transport: http(rpcUrl),\n });\n\n return new SlotsClient({ chainId, publicClient });\n}\n\n/**\n * Extract the IPFS CID from a metadata URI, or null for non-IPFS URIs.\n */\nexport function extractCid(uri: string): string | null {\n if (uri.startsWith(\"ipfs://\")) return uri.slice(7);\n if (uri.startsWith(\"Qm\") || uri.startsWith(\"bafy\")) return uri;\n return null;\n}\n\n/**\n * Fetch ad content from a metadata URI (IPFS or HTTP).\n * Returns both the ad data and the CID (if IPFS).\n */\nexport const fetchAdFromURI = async (\n uri: string,\n): Promise<{ data: AdData; cid: string | null }> => {\n if (!uri) throw new Error(AdDataQueryError.NO_AD);\n\n const cid = extractCid(uri);\n const url = cid ? `${IPFS_GATEWAY}${cid}` : uri;\n\n const res = await fetch(url, {\n method: \"GET\",\n headers: { Accept: \"application/json\" },\n });\n\n if (!res.ok) {\n if (res.status === 404) throw new Error(AdDataQueryError.NO_AD);\n throw new Error(AdDataQueryError.ERROR);\n }\n\n const data = await res.json();\n if (data.error) throw new Error(data.error);\n\n return { data, cid };\n};\n\n/**\n * Fetch the metadata URI for a slot using the SDK.\n */\nexport const fetchMetadataURI = async (\n client: SlotsClient,\n slotAddress: string,\n): Promise<string> => {\n const info = await client.getSlotInfo(slotAddress as Address);\n const moduleAddress = (info as { module: Address }).module;\n\n if (\n !moduleAddress ||\n moduleAddress === \"0x0000000000000000000000000000000000000000\"\n ) {\n return \"\";\n }\n\n return client.modules.metadata.getURI(\n moduleAddress,\n slotAddress as Address,\n );\n};\n","import type { SlotsChain } from \"@0xslots/sdk\";\nimport type { AdData } from \"@adland/data\";\n\n/**\n * Optional analytics context passed with every impression/click event.\n * Only include dimensions you'll filter or group by — Umami already\n * captures hostname, URL, referrer, country, device, OS, and browser.\n */\n/** Authentication method for verified impressions */\nexport type AdAuth = \"farcaster\" | \"none\";\n\nexport interface AdProps extends React.HTMLAttributes<HTMLDivElement> {\n /**\n * The slot contract address (0xSlots v3).\n * Required when fetching from chain. Omit when passing static `data`.\n */\n slot?: string;\n /**\n * Chain ID for on-chain reads. Defaults to BASE (8453).\n */\n chainId?: SlotsChain;\n /**\n * Static ad data. When provided, skips on-chain fetching.\n */\n data?: AdData;\n /**\n * Optional RPC URL override. If not provided, uses public RPC for the chain.\n */\n rpcUrl?: string;\n /**\n * Base URL for the \"Your ad here\" CTA link.\n * Empty-state click navigates to `${baseLinkUrl}/slots/${slot}?chain=${chainId}`.\n * Defaults to \"https://app.0xslots.org\".\n */\n baseLinkUrl?: string;\n /**\n * Authentication method for verified impressions. Default: \"none\".\n * \"farcaster\" uses Quick Auth — server resolves FID + address automatically.\n */\n auth?: AdAuth;\n /**\n * Placement context sent with events (e.g. \"carousel\", \"sidebar\", \"frame\").\n */\n context?: string;\n /**\n * Compound children (AdImage, AdTitle, etc.)\n */\n children?: React.ReactNode;\n}\n\nexport enum AdDataQueryError {\n NO_AD = \"NO_AD\",\n ERROR = \"ERROR\",\n}\n","import type { SlotsChain } from \"@0xslots/sdk\";\nimport type { AdData } from \"@adland/data\";\nimport { createContext, useContext } from \"react\";\n\nexport interface AdContextValue {\n data: AdData | null;\n cid: string | null;\n isLoading: boolean;\n error: unknown;\n isEmpty: boolean;\n slot?: string;\n baseLinkUrl: string;\n chainId: SlotsChain;\n}\n\nexport const AdContext = createContext<AdContextValue | null>(null);\n\nexport function useAd(): AdContextValue {\n const ctx = useContext(AdContext);\n if (!ctx) throw new Error(\"useAd must be used within an <Ad> component\");\n return ctx;\n}\n","import { useEffect, useState } from \"react\";\nimport FetchCache from \"../utils/fetchCache\";\n\ntype Status = \"idle\" | \"loading\" | \"success\" | \"error\";\n\nconst globalCache = new FetchCache();\n\nexport const fetchCache = {\n clear: () => globalCache.clear(),\n};\n\nexport function useFetch<T>(\n key: string,\n fetcher: () => Promise<T>,\n opts?: {\n enabled?: boolean;\n ttl?: number; // ms\n },\n) {\n const { enabled = true, ttl = 0 } = opts ?? {};\n\n // Get cached data from global cache\n const getCachedData = (): T | null => {\n return globalCache.get<T>(key, ttl || undefined);\n };\n\n const hasCachedData = () => {\n return globalCache.has(key, ttl || undefined);\n };\n\n // Check if there's an active fetch for this key (dedupe concurrent requests)\n const getActiveFetch = (): Promise<T> | undefined => {\n return globalCache.getActiveFetch<T>(key);\n };\n\n const cachedData = getCachedData();\n const [data, setData] = useState<T | null>(cachedData);\n const [error, setError] = useState<unknown>(null);\n // If we have cached data, start with success status to avoid showing loader\n const [status, setStatus] = useState<Status>(cachedData ? \"success\" : \"idle\");\n\n const refetch = async () => {\n // ALWAYS check cache first - never show loading if we have valid cached data\n const cached = getCachedData();\n if (cached) {\n // Already have valid cached data, ensure state is correct\n setData(cached);\n setStatus(\"success\");\n return cached;\n }\n\n // Check if there's already an active fetch for this key (dedupe)\n const activeFetch = getActiveFetch();\n if (activeFetch) {\n try {\n const res = await activeFetch;\n setData(res);\n setStatus(\"success\");\n return res;\n } catch (e) {\n setError(e);\n setStatus(\"error\");\n throw e;\n }\n }\n\n // Only set loading if we actually need to fetch\n setStatus(\"loading\");\n setError(null);\n\n try {\n // Create fetch promise and store it for deduplication\n const fetchPromise = fetcher();\n globalCache.setActiveFetch(key, fetchPromise);\n\n const res = await fetchPromise;\n globalCache.set(key, res);\n setData(res);\n setStatus(\"success\");\n return res;\n } catch (e) {\n setError(e);\n setStatus(\"error\");\n throw e;\n }\n };\n\n useEffect(() => {\n if (!enabled) return;\n\n const cached = getCachedData();\n\n if (cached) {\n setData(cached);\n setStatus(\"success\");\n return;\n }\n\n const activeFetch = getActiveFetch();\n if (activeFetch) {\n setStatus(\"loading\");\n activeFetch\n .then((res) => {\n setData(res);\n setStatus(\"success\");\n })\n .catch((e) => {\n setError(e);\n setStatus(\"error\");\n });\n return;\n }\n\n if (status !== \"loading\" && status !== \"success\") {\n refetch();\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [key, enabled]);\n\n const hasValidCache = hasCachedData();\n const isLoading = status === \"loading\" && !hasValidCache;\n\n return {\n data,\n error,\n status,\n isIdle: status === \"idle\",\n isLoading,\n isSuccess: status === \"success\",\n isError: status === \"error\",\n refetch,\n };\n}\n","class FetchCache {\n private cache = new Map<string, { data: unknown; ts: number }>();\n private activeFetches = new Map<string, Promise<unknown>>();\n\n get<T>(key: string, ttl?: number): T | null {\n const cached = this.cache.get(key);\n if (!cached) return null;\n\n if (ttl && Date.now() - cached.ts > ttl) {\n // Cache expired, remove it\n this.cache.delete(key);\n return null;\n }\n\n return cached.data as T;\n }\n\n has(key: string, ttl?: number): boolean {\n const cached = this.cache.get(key);\n if (!cached) return false;\n\n if (ttl && Date.now() - cached.ts > ttl) {\n this.cache.delete(key);\n return false;\n }\n\n return true;\n }\n\n set<T>(key: string, data: T): void {\n this.cache.set(key, { data, ts: Date.now() });\n }\n\n getActiveFetch<T>(key: string): Promise<T> | undefined {\n return this.activeFetches.get(key) as Promise<T> | undefined;\n }\n\n setActiveFetch<T>(key: string, promise: Promise<T>): void {\n this.activeFetches.set(key, promise);\n promise.finally(() => {\n this.activeFetches.delete(key);\n });\n }\n\n clear(): void {\n this.cache.clear();\n this.activeFetches.clear();\n }\n}\n\nexport default FetchCache;\n","import type { SlotsChain } from \"@0xslots/sdk\";\nimport type { AdData } from \"@adland/data\";\nimport sdk from \"@farcaster/miniapp-sdk\";\nimport { getAddress } from \"viem\";\n\n// Cache the miniapp check at module level — resolved once, sync thereafter\nlet _isMiniApp: boolean | null = null;\nconst _miniAppPromise = sdk\n .isInMiniApp()\n .then((v) => {\n _isMiniApp = v;\n return v;\n })\n .catch(() => {\n _isMiniApp = false;\n return false;\n });\n\nasync function isMiniApp(): Promise<boolean> {\n if (_isMiniApp !== null) return _isMiniApp;\n return _miniAppPromise;\n}\n\nexport function performAdAction(adData: AdData) {\n try {\n switch (adData.type) {\n case \"link\":\n sdk.actions.openUrl(adData.data.url);\n break;\n case \"cast\":\n sdk.actions.viewCast({ hash: adData.data.hash });\n break;\n case \"miniapp\":\n sdk.actions.openMiniApp({ url: adData.data.url });\n break;\n case \"token\": {\n const address = adData.data.address;\n const chainId = adData.data.chainId;\n const buyToken = `eip155:${chainId}/erc20:${getAddress(address)}`;\n sdk.actions.swapToken({ buyToken });\n break;\n }\n case \"farcasterProfile\":\n sdk.actions.viewProfile({\n fid: Number.parseInt(adData.data.fid, 10),\n });\n break;\n }\n } catch (err) {\n // Fallback for web (non-miniapp) context\n if (adData.type === \"link\" || adData.type === \"miniapp\") {\n window.open(adData.data.url, \"_blank\");\n } else {\n console.error(\"[@adland/react] Failed to perform ad action:\", err);\n }\n }\n}\n\nexport async function performEmptyAdAction(\n slot: string,\n chainId: SlotsChain,\n baseLinkUrl: string,\n) {\n const url = `${baseLinkUrl}/slots/${slot}?chain=${chainId}`;\n if (await isMiniApp()) {\n sdk.actions.openMiniApp({ url });\n } else {\n window.open(url, \"_blank\");\n }\n}\n","import type { AdData, AdType } from \"@adland/data\";\n\nconst IMAGE_KEYS = [\"image\", \"icon\", \"pfpUrl\", \"logoURI\", \"imageUrl\"] as const;\nconst TITLE_KEYS = [\"title\", \"displayName\", \"username\", \"name\", \"symbol\"] as const;\nconst DESC_KEYS = [\"description\", \"bio\", \"text\", \"name\"] as const;\n\nfunction flatFields(data: AdData): Record<string, unknown> {\n return { ...data.data, ...(data.metadata ?? {}) };\n}\n\nexport function getAdImage(data: AdData | null): string | null {\n if (!data) return null;\n const fields = flatFields(data);\n for (const key of IMAGE_KEYS) {\n const v = fields[key];\n if (typeof v === \"string\" && v) return v;\n }\n return null;\n}\n\nexport function getAdTitle(data: AdData | null): string | null {\n if (!data) return null;\n const fields = flatFields(data);\n for (const key of TITLE_KEYS) {\n const v = fields[key];\n if (typeof v === \"string\" && v) return v;\n }\n return null;\n}\n\nexport function getAdDescription(data: AdData | null): string | null {\n if (!data) return null;\n const fields = flatFields(data);\n const title = getAdTitle(data);\n for (const key of DESC_KEYS) {\n const v = fields[key];\n if (typeof v === \"string\" && v && v !== title) return v;\n }\n return null;\n}\n\nexport function getAdType(data: AdData | null): AdType | null {\n if (!data) return null;\n return data.type as AdType;\n}\n","import { AdType } from \"@adland/data\";\nimport { ForwardRefExoticComponent, RefAttributes } from \"react\";\nimport {\n Link,\n MessageCircle,\n LayoutGrid,\n LucideProps,\n Coins,\n User,\n} from \"lucide-react\";\n\nexport const adCardIcon: Record<\n AdType,\n ForwardRefExoticComponent<\n Omit<LucideProps, \"ref\"> & RefAttributes<SVGSVGElement>\n >\n> = {\n link: Link,\n cast: MessageCircle,\n miniapp: LayoutGrid,\n token: Coins,\n farcasterProfile: User,\n};\n\nexport const adCardLabel: Record<AdType, string> = {\n link: \"Link\",\n cast: \"Cast\",\n miniapp: \"Miniapp\",\n token: \"Token\",\n farcasterProfile: \"Profile\",\n};\n\nexport const adlandApiUrl =\n process.env.NODE_ENV === \"development\"\n ? \"http://localhost:3069\"\n : \"https://api.adland.space\";\n","import type { AdAuth } from \"../types\";\n\nconst API_URL = \"https://api.0xslots.org\";\n\ninterface TrackingPayload {\n slot: string;\n chainId: number;\n auth?: AdAuth;\n context?: string;\n cid?: string | null;\n empty?: boolean;\n}\n\n/** Track which slot+url combos have already been counted this session */\nconst tracked = new Set<string>();\n\n/** Cache the auth token per session */\nlet cachedAuthToken: string | null = null;\nlet authAttempted = false;\n\n/**\n * Get a Farcaster Quick Auth token via miniapp SDK.\n */\nasync function getFarcasterToken(): Promise<string | null> {\n if (cachedAuthToken) return cachedAuthToken;\n if (authAttempted) return null;\n\n authAttempted = true;\n try {\n const sdk = await import(\"@farcaster/miniapp-sdk\").then((m) => m.default);\n const isInMiniApp = await sdk.isInMiniApp();\n if (!isInMiniApp) return null;\n\n const result = await sdk.quickAuth.getToken();\n cachedAuthToken = result.token;\n return cachedAuthToken;\n } catch {\n return null;\n }\n}\n\n/**\n * Send a tracking event through the API proxy.\n */\nasync function sendEvent(\n eventName: string,\n data: TrackingPayload,\n): Promise<void> {\n if (typeof window === \"undefined\") return;\n\n const { auth, ...eventData } = data;\n let authToken: string | null = null;\n\n if (auth === \"farcaster\") {\n authToken = await getFarcasterToken();\n }\n\n const headers: Record<string, string> = {\n \"Content-Type\": \"application/json\",\n };\n\n if (authToken) {\n headers[\"Authorization\"] = `Bearer ${authToken}`;\n }\n\n try {\n fetch(`${API_URL}/events/track`, {\n method: \"POST\",\n headers,\n body: JSON.stringify({\n event: eventName,\n url: window.location.href,\n referrer: document.referrer || undefined,\n hostname: window.location.hostname,\n data: eventData,\n authMethod: auth || \"none\",\n }),\n keepalive: true,\n }).catch(() => {});\n } catch {}\n}\n\n/**\n * Track a unique impression (once per slot per page load).\n */\nexport function trackImpression(\n element: HTMLElement | null,\n data: TrackingPayload,\n): (() => void) | undefined {\n if (!element || typeof window === \"undefined\") return;\n\n const key = `impression:${data.slot}:${window.location.href}`;\n if (tracked.has(key)) return;\n\n const observer = new IntersectionObserver(\n (entries) => {\n for (const entry of entries) {\n if (entry.isIntersecting && !tracked.has(key)) {\n tracked.add(key);\n sendEvent(\"impression\", data);\n observer.disconnect();\n }\n }\n },\n { threshold: 0.5 },\n );\n\n observer.observe(element);\n\n return () => observer.disconnect();\n}\n\n/**\n * Track a click event.\n */\nexport function trackClick(eventName: string, data: TrackingPayload): void {\n sendEvent(eventName, data);\n}\n"],"mappings":";AAAA,SAAS,kBAAkB;AAE3B,SAAS,aAAa,aAAAA,YAAW,SAAS,cAAc;;;ACFxD,SAAS,mBAAoC;AAE7C,SAAuB,oBAAoB,YAAY;AACvD,SAAS,MAAM,mBAAmB;;;AC+C3B,IAAK,mBAAL,kBAAKC,sBAAL;AACL,EAAAA,kBAAA,WAAQ;AACR,EAAAA,kBAAA,WAAQ;AAFE,SAAAA;AAAA,GAAA;;;AD3CZ,IAAM,eAAe;AAErB,IAAM,aAA0C;AAAA,EAC9C,MAAM;AAAA,EACN,OAAO;AACT;AAKO,SAAS,iBACd,SACA,QACa;AACb,QAAM,QAAQ,WAAW,OAAO;AAChC,MAAI,CAAC,MAAO,OAAM,IAAI,MAAM,sBAAsB,OAAO,EAAE;AAE3D,QAAM,eAAe,mBAAmB;AAAA,IACtC;AAAA,IACA,WAAW,KAAK,MAAM;AAAA,EACxB,CAAC;AAED,SAAO,IAAI,YAAY,EAAE,SAAS,aAAa,CAAC;AAClD;AAKO,SAAS,WAAW,KAA4B;AACrD,MAAI,IAAI,WAAW,SAAS,EAAG,QAAO,IAAI,MAAM,CAAC;AACjD,MAAI,IAAI,WAAW,IAAI,KAAK,IAAI,WAAW,MAAM,EAAG,QAAO;AAC3D,SAAO;AACT;AAMO,IAAM,iBAAiB,OAC5B,QACkD;AAClD,MAAI,CAAC,IAAK,OAAM,IAAI,yBAA4B;AAEhD,QAAM,MAAM,WAAW,GAAG;AAC1B,QAAM,MAAM,MAAM,GAAG,YAAY,GAAG,GAAG,KAAK;AAE5C,QAAM,MAAM,MAAM,MAAM,KAAK;AAAA,IAC3B,QAAQ;AAAA,IACR,SAAS,EAAE,QAAQ,mBAAmB;AAAA,EACxC,CAAC;AAED,MAAI,CAAC,IAAI,IAAI;AACX,QAAI,IAAI,WAAW,IAAK,OAAM,IAAI,yBAA4B;AAC9D,UAAM,IAAI,yBAA4B;AAAA,EACxC;AAEA,QAAM,OAAO,MAAM,IAAI,KAAK;AAC5B,MAAI,KAAK,MAAO,OAAM,IAAI,MAAM,KAAK,KAAK;AAE1C,SAAO,EAAE,MAAM,IAAI;AACrB;AAKO,IAAM,mBAAmB,OAC9B,QACA,gBACoB;AACpB,QAAM,OAAO,MAAM,OAAO,YAAY,WAAsB;AAC5D,QAAM,gBAAiB,KAA6B;AAEpD,MACE,CAAC,iBACD,kBAAkB,8CAClB;AACA,WAAO;AAAA,EACT;AAEA,SAAO,OAAO,QAAQ,SAAS;AAAA,IAC7B;AAAA,IACA;AAAA,EACF;AACF;;;AExFA,SAAS,eAAe,kBAAkB;AAanC,IAAM,YAAY,cAAqC,IAAI;AAE3D,SAAS,QAAwB;AACtC,QAAM,MAAM,WAAW,SAAS;AAChC,MAAI,CAAC,IAAK,OAAM,IAAI,MAAM,6CAA6C;AACvE,SAAO;AACT;;;ACrBA,SAAS,WAAW,gBAAgB;;;ACApC,IAAM,aAAN,MAAiB;AAAA,EACP,QAAQ,oBAAI,IAA2C;AAAA,EACvD,gBAAgB,oBAAI,IAA8B;AAAA,EAE1D,IAAO,KAAa,KAAwB;AAC1C,UAAM,SAAS,KAAK,MAAM,IAAI,GAAG;AACjC,QAAI,CAAC,OAAQ,QAAO;AAEpB,QAAI,OAAO,KAAK,IAAI,IAAI,OAAO,KAAK,KAAK;AAEvC,WAAK,MAAM,OAAO,GAAG;AACrB,aAAO;AAAA,IACT;AAEA,WAAO,OAAO;AAAA,EAChB;AAAA,EAEA,IAAI,KAAa,KAAuB;AACtC,UAAM,SAAS,KAAK,MAAM,IAAI,GAAG;AACjC,QAAI,CAAC,OAAQ,QAAO;AAEpB,QAAI,OAAO,KAAK,IAAI,IAAI,OAAO,KAAK,KAAK;AACvC,WAAK,MAAM,OAAO,GAAG;AACrB,aAAO;AAAA,IACT;AAEA,WAAO;AAAA,EACT;AAAA,EAEA,IAAO,KAAa,MAAe;AACjC,SAAK,MAAM,IAAI,KAAK,EAAE,MAAM,IAAI,KAAK,IAAI,EAAE,CAAC;AAAA,EAC9C;AAAA,EAEA,eAAkB,KAAqC;AACrD,WAAO,KAAK,cAAc,IAAI,GAAG;AAAA,EACnC;AAAA,EAEA,eAAkB,KAAa,SAA2B;AACxD,SAAK,cAAc,IAAI,KAAK,OAAO;AACnC,YAAQ,QAAQ,MAAM;AACpB,WAAK,cAAc,OAAO,GAAG;AAAA,IAC/B,CAAC;AAAA,EACH;AAAA,EAEA,QAAc;AACZ,SAAK,MAAM,MAAM;AACjB,SAAK,cAAc,MAAM;AAAA,EAC3B;AACF;AAEA,IAAO,qBAAQ;;;AD7Cf,IAAM,cAAc,IAAI,mBAAW;AAM5B,SAAS,SACd,KACA,SACA,MAIA;AACA,QAAM,EAAE,UAAU,MAAM,MAAM,EAAE,IAAI,QAAQ,CAAC;AAG7C,QAAM,gBAAgB,MAAgB;AACpC,WAAO,YAAY,IAAO,KAAK,OAAO,MAAS;AAAA,EACjD;AAEA,QAAM,gBAAgB,MAAM;AAC1B,WAAO,YAAY,IAAI,KAAK,OAAO,MAAS;AAAA,EAC9C;AAGA,QAAM,iBAAiB,MAA8B;AACnD,WAAO,YAAY,eAAkB,GAAG;AAAA,EAC1C;AAEA,QAAM,aAAa,cAAc;AACjC,QAAM,CAAC,MAAM,OAAO,IAAI,SAAmB,UAAU;AACrD,QAAM,CAAC,OAAO,QAAQ,IAAI,SAAkB,IAAI;AAEhD,QAAM,CAAC,QAAQ,SAAS,IAAI,SAAiB,aAAa,YAAY,MAAM;AAE5E,QAAM,UAAU,YAAY;AAE1B,UAAM,SAAS,cAAc;AAC7B,QAAI,QAAQ;AAEV,cAAQ,MAAM;AACd,gBAAU,SAAS;AACnB,aAAO;AAAA,IACT;AAGA,UAAM,cAAc,eAAe;AACnC,QAAI,aAAa;AACf,UAAI;AACF,cAAM,MAAM,MAAM;AAClB,gBAAQ,GAAG;AACX,kBAAU,SAAS;AACnB,eAAO;AAAA,MACT,SAAS,GAAG;AACV,iBAAS,CAAC;AACV,kBAAU,OAAO;AACjB,cAAM;AAAA,MACR;AAAA,IACF;AAGA,cAAU,SAAS;AACnB,aAAS,IAAI;AAEb,QAAI;AAEF,YAAM,eAAe,QAAQ;AAC7B,kBAAY,eAAe,KAAK,YAAY;AAE5C,YAAM,MAAM,MAAM;AAClB,kBAAY,IAAI,KAAK,GAAG;AACxB,cAAQ,GAAG;AACX,gBAAU,SAAS;AACnB,aAAO;AAAA,IACT,SAAS,GAAG;AACV,eAAS,CAAC;AACV,gBAAU,OAAO;AACjB,YAAM;AAAA,IACR;AAAA,EACF;AAEA,YAAU,MAAM;AACd,QAAI,CAAC,QAAS;AAEd,UAAM,SAAS,cAAc;AAE7B,QAAI,QAAQ;AACV,cAAQ,MAAM;AACd,gBAAU,SAAS;AACnB;AAAA,IACF;AAEA,UAAM,cAAc,eAAe;AACnC,QAAI,aAAa;AACf,gBAAU,SAAS;AACnB,kBACG,KAAK,CAAC,QAAQ;AACb,gBAAQ,GAAG;AACX,kBAAU,SAAS;AAAA,MACrB,CAAC,EACA,MAAM,CAAC,MAAM;AACZ,iBAAS,CAAC;AACV,kBAAU,OAAO;AAAA,MACnB,CAAC;AACH;AAAA,IACF;AAEA,QAAI,WAAW,aAAa,WAAW,WAAW;AAChD,cAAQ;AAAA,IACV;AAAA,EAEF,GAAG,CAAC,KAAK,OAAO,CAAC;AAEjB,QAAM,gBAAgB,cAAc;AACpC,QAAM,YAAY,WAAW,aAAa,CAAC;AAE3C,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA,QAAQ,WAAW;AAAA,IACnB;AAAA,IACA,WAAW,WAAW;AAAA,IACtB,SAAS,WAAW;AAAA,IACpB;AAAA,EACF;AACF;;;AElIA,OAAO,SAAS;AAChB,SAAS,kBAAkB;AAG3B,IAAI,aAA6B;AACjC,IAAM,kBAAkB,IACrB,YAAY,EACZ,KAAK,CAAC,MAAM;AACX,eAAa;AACb,SAAO;AACT,CAAC,EACA,MAAM,MAAM;AACX,eAAa;AACb,SAAO;AACT,CAAC;AAEH,eAAe,YAA8B;AAC3C,MAAI,eAAe,KAAM,QAAO;AAChC,SAAO;AACT;AAEO,SAAS,gBAAgB,QAAgB;AAC9C,MAAI;AACF,YAAQ,OAAO,MAAM;AAAA,MACnB,KAAK;AACH,YAAI,QAAQ,QAAQ,OAAO,KAAK,GAAG;AACnC;AAAA,MACF,KAAK;AACH,YAAI,QAAQ,SAAS,EAAE,MAAM,OAAO,KAAK,KAAK,CAAC;AAC/C;AAAA,MACF,KAAK;AACH,YAAI,QAAQ,YAAY,EAAE,KAAK,OAAO,KAAK,IAAI,CAAC;AAChD;AAAA,MACF,KAAK,SAAS;AACZ,cAAM,UAAU,OAAO,KAAK;AAC5B,cAAM,UAAU,OAAO,KAAK;AAC5B,cAAM,WAAW,UAAU,OAAO,UAAU,WAAW,OAAO,CAAC;AAC/D,YAAI,QAAQ,UAAU,EAAE,SAAS,CAAC;AAClC;AAAA,MACF;AAAA,MACA,KAAK;AACH,YAAI,QAAQ,YAAY;AAAA,UACtB,KAAK,OAAO,SAAS,OAAO,KAAK,KAAK,EAAE;AAAA,QAC1C,CAAC;AACD;AAAA,IACJ;AAAA,EACF,SAAS,KAAK;AAEZ,QAAI,OAAO,SAAS,UAAU,OAAO,SAAS,WAAW;AACvD,aAAO,KAAK,OAAO,KAAK,KAAK,QAAQ;AAAA,IACvC,OAAO;AACL,cAAQ,MAAM,gDAAgD,GAAG;AAAA,IACnE;AAAA,EACF;AACF;AAEA,eAAsB,qBACpB,MACA,SACA,aACA;AACA,QAAM,MAAM,GAAG,WAAW,UAAU,IAAI,UAAU,OAAO;AACzD,MAAI,MAAM,UAAU,GAAG;AACrB,QAAI,QAAQ,YAAY,EAAE,IAAI,CAAC;AAAA,EACjC,OAAO;AACL,WAAO,KAAK,KAAK,QAAQ;AAAA,EAC3B;AACF;;;ACnEA,IAAM,aAAa,CAAC,SAAS,QAAQ,UAAU,WAAW,UAAU;AACpE,IAAM,aAAa,CAAC,SAAS,eAAe,YAAY,QAAQ,QAAQ;AACxE,IAAM,YAAY,CAAC,eAAe,OAAO,QAAQ,MAAM;AAEvD,SAAS,WAAW,MAAuC;AACzD,SAAO,EAAE,GAAG,KAAK,MAAM,GAAI,KAAK,YAAY,CAAC,EAAG;AAClD;AAEO,SAAS,WAAW,MAAoC;AAC7D,MAAI,CAAC,KAAM,QAAO;AAClB,QAAM,SAAS,WAAW,IAAI;AAC9B,aAAW,OAAO,YAAY;AAC5B,UAAM,IAAI,OAAO,GAAG;AACpB,QAAI,OAAO,MAAM,YAAY,EAAG,QAAO;AAAA,EACzC;AACA,SAAO;AACT;AAEO,SAAS,WAAW,MAAoC;AAC7D,MAAI,CAAC,KAAM,QAAO;AAClB,QAAM,SAAS,WAAW,IAAI;AAC9B,aAAW,OAAO,YAAY;AAC5B,UAAM,IAAI,OAAO,GAAG;AACpB,QAAI,OAAO,MAAM,YAAY,EAAG,QAAO;AAAA,EACzC;AACA,SAAO;AACT;AAEO,SAAS,iBAAiB,MAAoC;AACnE,MAAI,CAAC,KAAM,QAAO;AAClB,QAAM,SAAS,WAAW,IAAI;AAC9B,QAAM,QAAQ,WAAW,IAAI;AAC7B,aAAW,OAAO,WAAW;AAC3B,UAAM,IAAI,OAAO,GAAG;AACpB,QAAI,OAAO,MAAM,YAAY,KAAK,MAAM,MAAO,QAAO;AAAA,EACxD;AACA,SAAO;AACT;AAEO,SAAS,UAAU,MAAoC;AAC5D,MAAI,CAAC,KAAM,QAAO;AAClB,SAAO,KAAK;AACd;;;AC1CA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EAEA;AAAA,EACA;AAAA,OACK;AAEA,IAAM,aAKT;AAAA,EACF,MAAM;AAAA,EACN,MAAM;AAAA,EACN,SAAS;AAAA,EACT,OAAO;AAAA,EACP,kBAAkB;AACpB;AAEO,IAAM,cAAsC;AAAA,EACjD,MAAM;AAAA,EACN,MAAM;AAAA,EACN,SAAS;AAAA,EACT,OAAO;AAAA,EACP,kBAAkB;AACpB;AAEO,IAAM,eACX,QAAQ,IAAI,aAAa,gBACrB,0BACA;;;ACjCN,IAAM,UAAU;AAYhB,IAAM,UAAU,oBAAI,IAAY;AAGhC,IAAI,kBAAiC;AACrC,IAAI,gBAAgB;AAKpB,eAAe,oBAA4C;AACzD,MAAI,gBAAiB,QAAO;AAC5B,MAAI,cAAe,QAAO;AAE1B,kBAAgB;AAChB,MAAI;AACF,UAAMC,OAAM,MAAM,OAAO,wBAAwB,EAAE,KAAK,CAAC,MAAM,EAAE,OAAO;AACxE,UAAM,cAAc,MAAMA,KAAI,YAAY;AAC1C,QAAI,CAAC,YAAa,QAAO;AAEzB,UAAM,SAAS,MAAMA,KAAI,UAAU,SAAS;AAC5C,sBAAkB,OAAO;AACzB,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAKA,eAAe,UACb,WACA,MACe;AACf,MAAI,OAAO,WAAW,YAAa;AAEnC,QAAM,EAAE,MAAM,GAAG,UAAU,IAAI;AAC/B,MAAI,YAA2B;AAE/B,MAAI,SAAS,aAAa;AACxB,gBAAY,MAAM,kBAAkB;AAAA,EACtC;AAEA,QAAM,UAAkC;AAAA,IACtC,gBAAgB;AAAA,EAClB;AAEA,MAAI,WAAW;AACb,YAAQ,eAAe,IAAI,UAAU,SAAS;AAAA,EAChD;AAEA,MAAI;AACF,UAAM,GAAG,OAAO,iBAAiB;AAAA,MAC/B,QAAQ;AAAA,MACR;AAAA,MACA,MAAM,KAAK,UAAU;AAAA,QACnB,OAAO;AAAA,QACP,KAAK,OAAO,SAAS;AAAA,QACrB,UAAU,SAAS,YAAY;AAAA,QAC/B,UAAU,OAAO,SAAS;AAAA,QAC1B,MAAM;AAAA,QACN,YAAY,QAAQ;AAAA,MACtB,CAAC;AAAA,MACD,WAAW;AAAA,IACb,CAAC,EAAE,MAAM,MAAM;AAAA,IAAC,CAAC;AAAA,EACnB,QAAQ;AAAA,EAAC;AACX;AAKO,SAAS,gBACd,SACA,MAC0B;AAC1B,MAAI,CAAC,WAAW,OAAO,WAAW,YAAa;AAE/C,QAAM,MAAM,cAAc,KAAK,IAAI,IAAI,OAAO,SAAS,IAAI;AAC3D,MAAI,QAAQ,IAAI,GAAG,EAAG;AAEtB,QAAM,WAAW,IAAI;AAAA,IACnB,CAAC,YAAY;AACX,iBAAW,SAAS,SAAS;AAC3B,YAAI,MAAM,kBAAkB,CAAC,QAAQ,IAAI,GAAG,GAAG;AAC7C,kBAAQ,IAAI,GAAG;AACf,oBAAU,cAAc,IAAI;AAC5B,mBAAS,WAAW;AAAA,QACtB;AAAA,MACF;AAAA,IACF;AAAA,IACA,EAAE,WAAW,IAAI;AAAA,EACnB;AAEA,WAAS,QAAQ,OAAO;AAExB,SAAO,MAAM,SAAS,WAAW;AACnC;AAKO,SAAS,WAAW,WAAmB,MAA6B;AACzE,YAAU,WAAW,IAAI;AAC3B;;;ATSM,SAiBwB,UAjBxB,KA4DE,YA5DF;AA7FC,SAAS,GAAG;AAAA,EACjB;AAAA,EACA,MAAM;AAAA,EACN,UAAU,WAAW;AAAA,EACrB;AAAA,EACA,cAAc;AAAA,EACd,OAAO;AAAA,EACP;AAAA,EACA;AAAA,EACA,GAAG;AACL,GAAY;AACV,QAAM,MAAM,OAAuB,IAAI;AAEvC,QAAM,SAAS;AAAA,IACb,MAAO,OAAO,iBAAiB,SAAS,MAAM,IAAI;AAAA,IAClD,CAAC,MAAM,SAAS,MAAM;AAAA,EACxB;AAEA,QAAM;AAAA,IACJ,MAAM;AAAA,IACN;AAAA,IACA;AAAA,EACF,IAAI;AAAA,IACF,WAAW,IAAI;AAAA,IACf,YAAY;AACV,UAAI,CAAC,UAAU,CAAC,KAAM,OAAM,IAAI,yBAA4B;AAC5D,YAAM,MAAM,MAAM,iBAAiB,QAAQ,IAAI;AAC/C,UAAI,CAAC,KAAK;AACR,gBAAQ,KAAK,uCAAuC,IAAI;AACxD,eAAO;AAAA,MACT;AACA,aAAO,eAAe,GAAG;AAAA,IAC3B;AAAA,IACA,EAAE,SAAS,CAAC,CAAC,QAAQ,CAAC,WAAW;AAAA,EACnC;AAEA,QAAM,SAAS,cAAc,aAAa,QAAQ;AAClD,QAAM,MAAM,aAAa,OAAO;AAEhC,QAAM,UACJ,CAAC,UACD,CAAC,cACA,iBAAiB,QACd,MAAM,kCACN,CAAC;AAGP,EAAAC,WAAU,MAAM;AACd,QAAI,CAAC,QAAS,CAAC,UAAU,CAAC,QAAU;AACpC,WAAO,gBAAgB,IAAI,SAAS;AAAA,MAClC;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,OAAO;AAAA,IACT,CAAC;AAAA,EACH,GAAG,CAAC,QAAQ,SAAS,MAAM,SAAS,MAAM,SAAS,GAAG,CAAC;AAEvD,QAAM,UAAU;AAAA,IACd,CAAC,MAAwC;AACvC,YAAM,SAAS,EAAE;AACjB,YAAM,gBACJ,OAAO,YAAY,OACnB,OAAO,YAAY,YACnB,OAAO,QAAQ,GAAG,MAAM,QACxB,OAAO,QAAQ,QAAQ,MAAM;AAC/B,UAAI,cAAe;AAEnB,UAAI,QAAQ;AACV,YAAI,KAAM,YAAW,SAAS,EAAE,MAAM,SAAS,MAAM,SAAS,IAAI,CAAC;AACnE,wBAAgB,MAAM;AAAA,MACxB,WAAW,WAAW,MAAM;AAC1B,mBAAW,eAAe,EAAE,MAAM,SAAS,MAAM,QAAQ,CAAC;AAC1D,6BAAqB,MAAM,SAAS,WAAW;AAAA,MACjD;AAAA,IACF;AAAA,IACA,CAAC,QAAQ,SAAS,MAAM,SAAS,aAAa,GAAG;AAAA,EACnD;AAEA,SACE;AAAA,IAAC,UAAU;AAAA,IAAV;AAAA,MACC,OAAO;AAAA,QACL,MAAM,UAAU;AAAA,QAChB;AAAA,QACA,WAAW,CAAC,CAAC,QAAQ,CAAC,cAAc;AAAA,QACpC;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAAA,MAEA,8BAAC,SAAI,KAAU,SAAmB,GAAG,OAClC,UACH;AAAA;AAAA,EACF;AAEJ;AASO,SAAS,QAAQ,EAAE,UAAU,GAAG,MAAM,GAAiB;AAC5D,QAAM,EAAE,KAAK,IAAI,MAAM;AACvB,QAAM,MAAM,WAAW,IAAI;AAC3B,MAAI,CAAC,IAAK,QAAO,WAAW,gCAAG,oBAAS,IAAM;AAC9C,SAAO,oBAAC,SAAI,KAAU,KAAI,IAAI,GAAG,OAAO;AAC1C;AAOO,SAAS,QAAQ,EAAE,UAAU,UAAU,GAAG,MAAM,GAAiB;AACtE,QAAM,EAAE,KAAK,IAAI,MAAM;AACvB,QAAM,QAAQ,WAAW,IAAI;AAC7B,MAAI,CAAC,MAAO,QAAO,WAAW,gCAAG,oBAAS,IAAM;AAChD,SAAO,oBAAC,OAAG,GAAG,OAAQ,sBAAY,OAAM;AAC1C;AAOO,SAAS,cAAc;AAAA,EAC5B;AAAA,EACA;AAAA,EACA,GAAG;AACL,GAAuB;AACrB,QAAM,EAAE,KAAK,IAAI,MAAM;AACvB,QAAM,cAAc,iBAAiB,IAAI;AACzC,MAAI,CAAC,YAAa,QAAO,WAAW,gCAAG,oBAAS,IAAM;AACtD,SAAO,oBAAC,OAAG,GAAG,OAAQ,sBAAY,aAAY;AAChD;AAIO,SAAS,QAAQ,EAAE,UAAU,GAAG,MAAM,GAAiB;AAC5D,QAAM,EAAE,KAAK,IAAI,MAAM;AACvB,QAAM,OAAO,UAAU,IAAI;AAC3B,MAAI,CAAC,KAAM,QAAO;AAClB,QAAM,OAAO,WAAW,IAAI;AAC5B,QAAM,QAAQ,YAAY,IAAI;AAC9B,SACE,oBAAC,UAAM,GAAG,OACP,sBACC,iCACG;AAAA,YAAQ,oBAAC,QAAK,WAAU,UAAS;AAAA,IACjC;AAAA,KACH,GAEJ;AAEJ;AAIO,SAAS,QAAQ,EAAE,UAAU,GAAG,MAAM,GAAiB;AAC5D,SAAO,oBAAC,UAAM,GAAG,OAAQ,sBAAY,MAAK;AAC5C;AAQO,SAAS,UAAU,EAAE,UAAU,GAAG,MAAM,GAAkB;AAC/D,QAAM,EAAE,UAAU,IAAI,MAAM;AAC5B,MAAI,CAAC,UAAW,QAAO;AACvB,SAAO,oBAAC,SAAK,GAAG,OAAQ,sBAAY,cAAa;AACnD;AAEO,SAAS,QAAQ,EAAE,UAAU,GAAG,MAAM,GAAkB;AAC7D,QAAM,EAAE,QAAQ,IAAI,MAAM;AAC1B,MAAI,CAAC,QAAS,QAAO;AACrB,SAAO,oBAAC,SAAK,GAAG,OAAQ,sBAAY,gBAAe;AACrD;AAEO,SAAS,QAAQ,EAAE,UAAU,GAAG,MAAM,GAAkB;AAC7D,QAAM,EAAE,OAAO,QAAQ,IAAI,MAAM;AACjC,MAAI,CAAC,SAAS,QAAS,QAAO;AAC9B,SAAO,oBAAC,SAAK,GAAG,OAAQ,sBAAY,oBAAmB;AACzD;AAEO,SAAS,SAAS,EAAE,UAAU,GAAG,MAAM,GAAkB;AAC9D,QAAM,EAAE,KAAK,IAAI,MAAM;AACvB,MAAI,CAAC,KAAM,QAAO;AAClB,SAAO,oBAAC,SAAK,GAAG,OAAQ,UAAS;AACnC;","names":["useEffect","AdDataQueryError","sdk","useEffect"]}
|