@adland/react 0.16.6 → 0.16.8
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 +4 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +4 -4
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -372,7 +372,7 @@ var adCardLabel = {
|
|
|
372
372
|
token: "Token",
|
|
373
373
|
farcasterProfile: "Profile"
|
|
374
374
|
};
|
|
375
|
-
var adlandApiUrl = process.env.NODE_ENV === "development" ? "http://localhost:3069" : "https://api.
|
|
375
|
+
var adlandApiUrl = process.env.NODE_ENV === "development" ? "http://localhost:3069" : "https://api.0xslots.org";
|
|
376
376
|
|
|
377
377
|
// src/utils/tracking.ts
|
|
378
378
|
var tracked = /* @__PURE__ */ new Set();
|
|
@@ -437,7 +437,7 @@ function trackImpression(element, data) {
|
|
|
437
437
|
return () => observer.disconnect();
|
|
438
438
|
}
|
|
439
439
|
function trackClick(_eventName, data) {
|
|
440
|
-
sendEvent("click", data);
|
|
440
|
+
return sendEvent("click", data);
|
|
441
441
|
}
|
|
442
442
|
|
|
443
443
|
// src/components/Ad.tsx
|
|
@@ -490,7 +490,7 @@ function Ad({
|
|
|
490
490
|
});
|
|
491
491
|
}, [adData, isEmpty, slot, chainId, auth, context, cid]);
|
|
492
492
|
const onClick = (0, import_react3.useCallback)(
|
|
493
|
-
(e) => {
|
|
493
|
+
async (e) => {
|
|
494
494
|
const target = e.target;
|
|
495
495
|
const isInteractive = target.tagName === "A" || target.tagName === "BUTTON" || target.closest("a") !== null || target.closest("button") !== null;
|
|
496
496
|
if (isInteractive) return;
|
|
@@ -498,7 +498,7 @@ function Ad({
|
|
|
498
498
|
if (slot) trackClick("click", { slot, chainId, auth, context, cid });
|
|
499
499
|
performAdAction(adData);
|
|
500
500
|
} else if (isEmpty && slot) {
|
|
501
|
-
trackClick("click-empty", { slot, chainId, auth, context });
|
|
501
|
+
await trackClick("click-empty", { slot, chainId, auth, context });
|
|
502
502
|
performEmptyAdAction(slot, chainId, baseLinkUrl);
|
|
503
503
|
}
|
|
504
504
|
},
|
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: 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\";\nimport { adlandApiUrl } from \"./constants\";\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 Farcaster auth token per session */\nlet cachedAuthToken: string | null = null;\nlet authAttempted = false;\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 to the API.\n */\nasync function sendEvent(\n eventType: \"view\" | \"click\",\n data: TrackingPayload,\n): Promise<void> {\n if (typeof window === \"undefined\") return;\n\n let token: string | null = null;\n if (data.auth === \"farcaster\") {\n token = await getFarcasterToken();\n }\n\n try {\n fetch(`${adlandApiUrl}/track`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n type: eventType,\n slot: data.slot,\n cid: data.cid ?? null,\n domain: window.location.hostname,\n auth: data.auth ?? \"none\",\n ...(token ? { token } : {}),\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(\"view\", 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(\"click\", 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 async (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 await 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.0xslots.org\";\n","import type { AdAuth } from \"../types\";\nimport { adlandApiUrl } from \"./constants\";\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 Farcaster auth token per session */\nlet cachedAuthToken: string | null = null;\nlet authAttempted = false;\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 to the API.\n */\nasync function sendEvent(\n eventType: \"view\" | \"click\",\n data: TrackingPayload,\n): Promise<void> {\n if (typeof window === \"undefined\") return;\n\n let token: string | null = null;\n if (data.auth === \"farcaster\") {\n token = await getFarcasterToken();\n }\n\n try {\n fetch(`${adlandApiUrl}/track`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n type: eventType,\n slot: data.slot,\n cid: data.cid ?? null,\n domain: window.location.hostname,\n auth: data.auth ?? \"none\",\n ...(token ? { token } : {}),\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(\"view\", 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(\n _eventName: string,\n data: TrackingPayload,\n): Promise<void> {\n return sendEvent(\"click\", data);\n}\n"],"mappings":"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","names":["import_sdk","import_react","AdDataQueryError","import_react","import_viem","sdk","sdk"]}
|
package/dist/index.js
CHANGED
|
@@ -325,7 +325,7 @@ var adCardLabel = {
|
|
|
325
325
|
token: "Token",
|
|
326
326
|
farcasterProfile: "Profile"
|
|
327
327
|
};
|
|
328
|
-
var adlandApiUrl = process.env.NODE_ENV === "development" ? "http://localhost:3069" : "https://api.
|
|
328
|
+
var adlandApiUrl = process.env.NODE_ENV === "development" ? "http://localhost:3069" : "https://api.0xslots.org";
|
|
329
329
|
|
|
330
330
|
// src/utils/tracking.ts
|
|
331
331
|
var tracked = /* @__PURE__ */ new Set();
|
|
@@ -390,7 +390,7 @@ function trackImpression(element, data) {
|
|
|
390
390
|
return () => observer.disconnect();
|
|
391
391
|
}
|
|
392
392
|
function trackClick(_eventName, data) {
|
|
393
|
-
sendEvent("click", data);
|
|
393
|
+
return sendEvent("click", data);
|
|
394
394
|
}
|
|
395
395
|
|
|
396
396
|
// src/components/Ad.tsx
|
|
@@ -443,7 +443,7 @@ function Ad({
|
|
|
443
443
|
});
|
|
444
444
|
}, [adData, isEmpty, slot, chainId, auth, context, cid]);
|
|
445
445
|
const onClick = useCallback(
|
|
446
|
-
(e) => {
|
|
446
|
+
async (e) => {
|
|
447
447
|
const target = e.target;
|
|
448
448
|
const isInteractive = target.tagName === "A" || target.tagName === "BUTTON" || target.closest("a") !== null || target.closest("button") !== null;
|
|
449
449
|
if (isInteractive) return;
|
|
@@ -451,7 +451,7 @@ function Ad({
|
|
|
451
451
|
if (slot) trackClick("click", { slot, chainId, auth, context, cid });
|
|
452
452
|
performAdAction(adData);
|
|
453
453
|
} else if (isEmpty && slot) {
|
|
454
|
-
trackClick("click-empty", { slot, chainId, auth, context });
|
|
454
|
+
await trackClick("click-empty", { slot, chainId, auth, context });
|
|
455
455
|
performEmptyAdAction(slot, chainId, baseLinkUrl);
|
|
456
456
|
}
|
|
457
457
|
},
|
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: 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\";\nimport { adlandApiUrl } from \"./constants\";\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 Farcaster auth token per session */\nlet cachedAuthToken: string | null = null;\nlet authAttempted = false;\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 to the API.\n */\nasync function sendEvent(\n eventType: \"view\" | \"click\",\n data: TrackingPayload,\n): Promise<void> {\n if (typeof window === \"undefined\") return;\n\n let token: string | null = null;\n if (data.auth === \"farcaster\") {\n token = await getFarcasterToken();\n }\n\n try {\n fetch(`${adlandApiUrl}/track`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n type: eventType,\n slot: data.slot,\n cid: data.cid ?? null,\n domain: window.location.hostname,\n auth: data.auth ?? \"none\",\n ...(token ? { token } : {}),\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(\"view\", 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(\"click\", 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;;;ACtBN,IAAM,UAAU,oBAAI,IAAY;AAGhC,IAAI,kBAAiC;AACrC,IAAI,gBAAgB;AAEpB,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,MAAI,QAAuB;AAC3B,MAAI,KAAK,SAAS,aAAa;AAC7B,YAAQ,MAAM,kBAAkB;AAAA,EAClC;AAEA,MAAI;AACF,UAAM,GAAG,YAAY,UAAU;AAAA,MAC7B,QAAQ;AAAA,MACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,MAC9C,MAAM,KAAK,UAAU;AAAA,QACnB,MAAM;AAAA,QACN,MAAM,KAAK;AAAA,QACX,KAAK,KAAK,OAAO;AAAA,QACjB,QAAQ,OAAO,SAAS;AAAA,QACxB,MAAM,KAAK,QAAQ;AAAA,QACnB,GAAI,QAAQ,EAAE,MAAM,IAAI,CAAC;AAAA,MAC3B,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,QAAQ,IAAI;AACtB,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,YAAoB,MAA6B;AAC1E,YAAU,SAAS,IAAI;AACzB;;;ATuBM,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"]}
|
|
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 async (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 await 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.0xslots.org\";\n","import type { AdAuth } from \"../types\";\nimport { adlandApiUrl } from \"./constants\";\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 Farcaster auth token per session */\nlet cachedAuthToken: string | null = null;\nlet authAttempted = false;\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 to the API.\n */\nasync function sendEvent(\n eventType: \"view\" | \"click\",\n data: TrackingPayload,\n): Promise<void> {\n if (typeof window === \"undefined\") return;\n\n let token: string | null = null;\n if (data.auth === \"farcaster\") {\n token = await getFarcasterToken();\n }\n\n try {\n fetch(`${adlandApiUrl}/track`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n type: eventType,\n slot: data.slot,\n cid: data.cid ?? null,\n domain: window.location.hostname,\n auth: data.auth ?? \"none\",\n ...(token ? { token } : {}),\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(\"view\", 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(\n _eventName: string,\n data: TrackingPayload,\n): Promise<void> {\n return sendEvent(\"click\", data);\n}\n"],"mappings":"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","names":["useEffect","AdDataQueryError","sdk","useEffect"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adland/react",
|
|
3
|
-
"version": "0.16.
|
|
3
|
+
"version": "0.16.8",
|
|
4
4
|
"private": false,
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
"lucide-react": "0.561.0",
|
|
38
38
|
"tsup": "^8.0.1",
|
|
39
39
|
"viem": "^2.0.0",
|
|
40
|
-
"@adland/data": "0.
|
|
40
|
+
"@adland/data": "0.15.0",
|
|
41
41
|
"@0xslots/sdk": "0.11.1"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|