ani-ads-sdk 2.0.1 → 2.0.2
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.js +1 -24
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1 -24
- package/dist/index.mjs.map +1 -1
- package/package.json +45 -45
package/dist/index.js
CHANGED
|
@@ -141,30 +141,7 @@ var AniAds = ({
|
|
|
141
141
|
cancelled = true;
|
|
142
142
|
};
|
|
143
143
|
}, [creator_wallet, app_name, normalizedApiUrl, trackImpression]);
|
|
144
|
-
if (loading) {
|
|
145
|
-
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: {
|
|
146
|
-
display: "flex",
|
|
147
|
-
width: "100%",
|
|
148
|
-
minHeight: "100px",
|
|
149
|
-
backgroundColor: "#f3f4f6",
|
|
150
|
-
borderRadius: "8px",
|
|
151
|
-
alignItems: "center",
|
|
152
|
-
justifyContent: "center"
|
|
153
|
-
}, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { color: "#6b7280" }, children: "Loading ad..." }) });
|
|
154
|
-
}
|
|
155
|
-
if (error) {
|
|
156
|
-
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: {
|
|
157
|
-
display: "flex",
|
|
158
|
-
width: "100%",
|
|
159
|
-
minHeight: "100px",
|
|
160
|
-
backgroundColor: "#fee2e2",
|
|
161
|
-
borderRadius: "8px",
|
|
162
|
-
alignItems: "center",
|
|
163
|
-
justifyContent: "center",
|
|
164
|
-
padding: "12px"
|
|
165
|
-
}, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { color: "#dc2626", fontSize: "14px" }, children: error }) });
|
|
166
|
-
}
|
|
167
|
-
if (!ad) {
|
|
144
|
+
if (loading || error || !ad) {
|
|
168
145
|
return null;
|
|
169
146
|
}
|
|
170
147
|
const imageUrl = getBestImage(ad);
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.tsx"],"sourcesContent":["import React, { useEffect, useState, useCallback } from 'react'\r\n\r\nexport interface AniAdsProps {\r\n creator_wallet: string\r\n app_name: string\r\n user_wallet_address: string\r\n api_url?: string // Optional API URL, defaults to production\r\n onAdClick?: (adId: string, destinationUrl: string) => void // Optional click handler\r\n}\r\n\r\ninterface Ad {\r\n id: string\r\n title: string\r\n destination_url: string\r\n image_square: string\r\n image_portrait: string\r\n image_landscape: string\r\n status: 'active' | 'paused' | 'deleting'\r\n remaining_budget: number\r\n}\r\n\r\nexport const AniAds: React.FC<AniAdsProps> = ({\r\n creator_wallet,\r\n app_name,\r\n user_wallet_address,\r\n api_url = 'https://ani-ads.vercel.app', // Default API URL\r\n onAdClick,\r\n}) => {\r\n // Normalize API URL - ensure it doesn't have trailing slash for proper path joining\r\n const normalizedApiUrl = api_url.replace(/\\/+$/, '')\r\n \r\n const [ad, setAd] = useState<Ad | null>(null)\r\n const [loading, setLoading] = useState(true)\r\n const [error, setError] = useState<string | null>(null)\r\n\r\n // Get image URL - always use image_square (380x90 banner format)\r\n const getImageUrl = (ad: Ad): string | null => {\r\n return ad.image_square || null\r\n }\r\n\r\n // Track ad click\r\n const handleClick = useCallback(async () => {\r\n if (!ad || !user_wallet_address) return\r\n\r\n try {\r\n // Track click on backend\r\n const response = await fetch(`${normalizedApiUrl}/api/ads/click`, {\r\n method: 'POST',\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n },\r\n body: JSON.stringify({\r\n ad_id: ad.id,\r\n creator_wallet: creator_wallet.toLowerCase(),\r\n app_name,\r\n user_wallet_address: user_wallet_address.toLowerCase(),\r\n }),\r\n })\r\n\r\n if (response.ok) {\r\n // Call optional click handler\r\n if (onAdClick) {\r\n onAdClick(ad.id, ad.destination_url)\r\n }\r\n\r\n // Open destination URL\r\n window.open(ad.destination_url, '_blank', 'noopener,noreferrer')\r\n\r\n // Don't fetch new ad after click - keep showing the same ad until page reload\r\n }\r\n } catch (err) {\r\n console.error('Error tracking click:', err)\r\n // Still open the URL even if tracking fails\r\n window.open(ad.destination_url, '_blank', 'noopener,noreferrer')\r\n }\r\n }, [ad, creator_wallet, app_name, user_wallet_address, normalizedApiUrl, onAdClick])\r\n\r\n // Get image - always use banner format (380x90)\r\n const getBestImage = (ad: Ad): string | null => {\r\n return getImageUrl(ad)\r\n }\r\n\r\n // Track impression when ad is displayed\r\n const trackImpression = useCallback(async (adId: string) => {\r\n if (!adId || !creator_wallet || !app_name || !user_wallet_address) return\r\n\r\n try {\r\n await fetch(`${normalizedApiUrl}/api/ads/impression`, {\r\n method: 'POST',\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n },\r\n body: JSON.stringify({\r\n ad_id: adId,\r\n creator_wallet: creator_wallet.toLowerCase(),\r\n app_name,\r\n user_wallet_address: user_wallet_address.toLowerCase(),\r\n }),\r\n })\r\n // Don't show errors to user - impression tracking is not critical\r\n } catch (err) {\r\n console.error('Error tracking impression:', err)\r\n // Silently fail - impression tracking should not break the ad display\r\n }\r\n }, [creator_wallet, app_name, user_wallet_address, normalizedApiUrl])\r\n\r\n // Fetch ad only once when component mounts or when creator_wallet/app_name changes\r\n useEffect(() => {\r\n let cancelled = false\r\n \r\n const loadAd = async () => {\r\n if (!creator_wallet || !app_name) {\r\n setLoading(false)\r\n return\r\n }\r\n\r\n try {\r\n setLoading(true)\r\n setError(null)\r\n\r\n const response = await fetch(`${normalizedApiUrl}/api/ads/sdk`, {\r\n method: 'POST',\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n },\r\n body: JSON.stringify({\r\n creator_wallet: creator_wallet.toLowerCase(),\r\n app_name,\r\n }),\r\n })\r\n\r\n if (cancelled) return\r\n\r\n if (!response.ok) {\r\n const errorData = await response.json().catch(() => ({}))\r\n throw new Error(errorData.error || `HTTP ${response.status}: Failed to fetch ads`)\r\n }\r\n\r\n const data = await response.json()\r\n \r\n if (cancelled) return\r\n \r\n if (data.ad) {\r\n setAd(data.ad)\r\n setError(null) // Clear any previous errors\r\n \r\n // Track impression when ad is loaded and displayed\r\n trackImpression(data.ad.id)\r\n } else {\r\n // No ad available, but not an error - just don't show anything\r\n setAd(null)\r\n setError(null)\r\n }\r\n } catch (err) {\r\n if (cancelled) return\r\n console.error('Error fetching ad:', err)\r\n const errorMessage = err instanceof Error ? err.message : 'Failed to load ad'\r\n setError(errorMessage)\r\n setAd(null)\r\n } finally {\r\n if (!cancelled) {\r\n setLoading(false)\r\n }\r\n }\r\n }\r\n\r\n loadAd()\r\n\r\n return () => {\r\n cancelled = true\r\n }\r\n }, [creator_wallet, app_name, normalizedApiUrl, trackImpression]) // Only depend on props, not derived values\r\n\r\n if (loading) {\r\n return (\r\n <div style={{\r\n display: 'flex',\r\n width: '100%',\r\n minHeight: '100px',\r\n backgroundColor: '#f3f4f6',\r\n borderRadius: '8px',\r\n alignItems: 'center',\r\n justifyContent: 'center',\r\n }}>\r\n <span style={{ color: '#6b7280' }}>Loading ad...</span>\r\n </div>\r\n )\r\n }\r\n\r\n if (error) {\r\n return (\r\n <div style={{\r\n display: 'flex',\r\n width: '100%',\r\n minHeight: '100px',\r\n backgroundColor: '#fee2e2',\r\n borderRadius: '8px',\r\n alignItems: 'center',\r\n justifyContent: 'center',\r\n padding: '12px',\r\n }}>\r\n <span style={{ color: '#dc2626', fontSize: '14px' }}>{error}</span>\r\n </div>\r\n )\r\n }\r\n\r\n if (!ad) {\r\n return null // Don't render anything if no ad available\r\n }\r\n\r\n const imageUrl = getBestImage(ad)\r\n\r\n if (!imageUrl) {\r\n return null\r\n }\r\n\r\n return (\r\n <div\r\n style={{\r\n display: 'inline-block',\r\n width: '100%',\r\n cursor: 'pointer',\r\n borderRadius: '8px',\r\n overflow: 'hidden',\r\n transition: 'transform 0.2s, box-shadow 0.2s',\r\n }}\r\n onClick={handleClick}\r\n onMouseEnter={(e) => {\r\n e.currentTarget.style.transform = 'scale(1.02)'\r\n e.currentTarget.style.boxShadow = '0 4px 12px rgba(0,0,0,0.15)'\r\n }}\r\n onMouseLeave={(e) => {\r\n e.currentTarget.style.transform = 'scale(1)'\r\n e.currentTarget.style.boxShadow = 'none'\r\n }}\r\n >\r\n <img\r\n src={imageUrl}\r\n alt={ad.title}\r\n style={{\r\n width: '100%',\r\n height: 'auto',\r\n display: 'block',\r\n }}\r\n loading=\"lazy\"\r\n />\r\n </div>\r\n )\r\n}\r\n\r\nexport default AniAds\r\n"],"mappings":"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","names":["ad"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.tsx"],"sourcesContent":["import React, { useEffect, useState, useCallback } from 'react'\r\n\r\nexport interface AniAdsProps {\r\n creator_wallet: string\r\n app_name: string\r\n user_wallet_address: string\r\n api_url?: string // Optional API URL, defaults to production\r\n onAdClick?: (adId: string, destinationUrl: string) => void // Optional click handler\r\n}\r\n\r\ninterface Ad {\r\n id: string\r\n title: string\r\n destination_url: string\r\n image_square: string\r\n image_portrait: string\r\n image_landscape: string\r\n status: 'active' | 'paused' | 'deleting'\r\n remaining_budget: number\r\n}\r\n\r\nexport const AniAds: React.FC<AniAdsProps> = ({\r\n creator_wallet,\r\n app_name,\r\n user_wallet_address,\r\n api_url = 'https://ani-ads.vercel.app', // Default API URL\r\n onAdClick,\r\n}) => {\r\n // Normalize API URL - ensure it doesn't have trailing slash for proper path joining\r\n const normalizedApiUrl = api_url.replace(/\\/+$/, '')\r\n \r\n const [ad, setAd] = useState<Ad | null>(null)\r\n const [loading, setLoading] = useState(true)\r\n const [error, setError] = useState<string | null>(null)\r\n\r\n // Get image URL - always use image_square (380x90 banner format)\r\n const getImageUrl = (ad: Ad): string | null => {\r\n return ad.image_square || null\r\n }\r\n\r\n // Track ad click\r\n const handleClick = useCallback(async () => {\r\n if (!ad || !user_wallet_address) return\r\n\r\n try {\r\n // Track click on backend\r\n const response = await fetch(`${normalizedApiUrl}/api/ads/click`, {\r\n method: 'POST',\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n },\r\n body: JSON.stringify({\r\n ad_id: ad.id,\r\n creator_wallet: creator_wallet.toLowerCase(),\r\n app_name,\r\n user_wallet_address: user_wallet_address.toLowerCase(),\r\n }),\r\n })\r\n\r\n if (response.ok) {\r\n // Call optional click handler\r\n if (onAdClick) {\r\n onAdClick(ad.id, ad.destination_url)\r\n }\r\n\r\n // Open destination URL\r\n window.open(ad.destination_url, '_blank', 'noopener,noreferrer')\r\n\r\n // Don't fetch new ad after click - keep showing the same ad until page reload\r\n }\r\n } catch (err) {\r\n console.error('Error tracking click:', err)\r\n // Still open the URL even if tracking fails\r\n window.open(ad.destination_url, '_blank', 'noopener,noreferrer')\r\n }\r\n }, [ad, creator_wallet, app_name, user_wallet_address, normalizedApiUrl, onAdClick])\r\n\r\n // Get image - always use banner format (380x90)\r\n const getBestImage = (ad: Ad): string | null => {\r\n return getImageUrl(ad)\r\n }\r\n\r\n // Track impression when ad is displayed\r\n const trackImpression = useCallback(async (adId: string) => {\r\n if (!adId || !creator_wallet || !app_name || !user_wallet_address) return\r\n\r\n try {\r\n await fetch(`${normalizedApiUrl}/api/ads/impression`, {\r\n method: 'POST',\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n },\r\n body: JSON.stringify({\r\n ad_id: adId,\r\n creator_wallet: creator_wallet.toLowerCase(),\r\n app_name,\r\n user_wallet_address: user_wallet_address.toLowerCase(),\r\n }),\r\n })\r\n // Don't show errors to user - impression tracking is not critical\r\n } catch (err) {\r\n console.error('Error tracking impression:', err)\r\n // Silently fail - impression tracking should not break the ad display\r\n }\r\n }, [creator_wallet, app_name, user_wallet_address, normalizedApiUrl])\r\n\r\n // Fetch ad only once when component mounts or when creator_wallet/app_name changes\r\n useEffect(() => {\r\n let cancelled = false\r\n \r\n const loadAd = async () => {\r\n if (!creator_wallet || !app_name) {\r\n setLoading(false)\r\n return\r\n }\r\n\r\n try {\r\n setLoading(true)\r\n setError(null)\r\n\r\n const response = await fetch(`${normalizedApiUrl}/api/ads/sdk`, {\r\n method: 'POST',\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n },\r\n body: JSON.stringify({\r\n creator_wallet: creator_wallet.toLowerCase(),\r\n app_name,\r\n }),\r\n })\r\n\r\n if (cancelled) return\r\n\r\n if (!response.ok) {\r\n const errorData = await response.json().catch(() => ({}))\r\n throw new Error(errorData.error || `HTTP ${response.status}: Failed to fetch ads`)\r\n }\r\n\r\n const data = await response.json()\r\n \r\n if (cancelled) return\r\n \r\n if (data.ad) {\r\n setAd(data.ad)\r\n setError(null) // Clear any previous errors\r\n \r\n // Track impression when ad is loaded and displayed\r\n trackImpression(data.ad.id)\r\n } else {\r\n // No ad available, but not an error - just don't show anything\r\n setAd(null)\r\n setError(null)\r\n }\r\n } catch (err) {\r\n if (cancelled) return\r\n console.error('Error fetching ad:', err)\r\n const errorMessage = err instanceof Error ? err.message : 'Failed to load ad'\r\n setError(errorMessage)\r\n setAd(null)\r\n } finally {\r\n if (!cancelled) {\r\n setLoading(false)\r\n }\r\n }\r\n }\r\n\r\n loadAd()\r\n\r\n return () => {\r\n cancelled = true\r\n }\r\n }, [creator_wallet, app_name, normalizedApiUrl, trackImpression]) // Only depend on props, not derived values\r\n\r\n // Don't show anything while loading, on error, or when no ad - only render when ad is ready\r\n if (loading || error || !ad) {\r\n return null\r\n }\r\n\r\n const imageUrl = getBestImage(ad)\r\n\r\n if (!imageUrl) {\r\n return null\r\n }\r\n\r\n return (\r\n <div\r\n style={{\r\n display: 'inline-block',\r\n width: '100%',\r\n cursor: 'pointer',\r\n borderRadius: '8px',\r\n overflow: 'hidden',\r\n transition: 'transform 0.2s, box-shadow 0.2s',\r\n }}\r\n onClick={handleClick}\r\n onMouseEnter={(e) => {\r\n e.currentTarget.style.transform = 'scale(1.02)'\r\n e.currentTarget.style.boxShadow = '0 4px 12px rgba(0,0,0,0.15)'\r\n }}\r\n onMouseLeave={(e) => {\r\n e.currentTarget.style.transform = 'scale(1)'\r\n e.currentTarget.style.boxShadow = 'none'\r\n }}\r\n >\r\n <img\r\n src={imageUrl}\r\n alt={ad.title}\r\n style={{\r\n width: '100%',\r\n height: 'auto',\r\n display: 'block',\r\n }}\r\n loading=\"lazy\"\r\n />\r\n </div>\r\n )\r\n}\r\n\r\nexport default AniAds\r\n"],"mappings":"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","names":["ad"]}
|
package/dist/index.mjs
CHANGED
|
@@ -116,30 +116,7 @@ var AniAds = ({
|
|
|
116
116
|
cancelled = true;
|
|
117
117
|
};
|
|
118
118
|
}, [creator_wallet, app_name, normalizedApiUrl, trackImpression]);
|
|
119
|
-
if (loading) {
|
|
120
|
-
return /* @__PURE__ */ jsx("div", { style: {
|
|
121
|
-
display: "flex",
|
|
122
|
-
width: "100%",
|
|
123
|
-
minHeight: "100px",
|
|
124
|
-
backgroundColor: "#f3f4f6",
|
|
125
|
-
borderRadius: "8px",
|
|
126
|
-
alignItems: "center",
|
|
127
|
-
justifyContent: "center"
|
|
128
|
-
}, children: /* @__PURE__ */ jsx("span", { style: { color: "#6b7280" }, children: "Loading ad..." }) });
|
|
129
|
-
}
|
|
130
|
-
if (error) {
|
|
131
|
-
return /* @__PURE__ */ jsx("div", { style: {
|
|
132
|
-
display: "flex",
|
|
133
|
-
width: "100%",
|
|
134
|
-
minHeight: "100px",
|
|
135
|
-
backgroundColor: "#fee2e2",
|
|
136
|
-
borderRadius: "8px",
|
|
137
|
-
alignItems: "center",
|
|
138
|
-
justifyContent: "center",
|
|
139
|
-
padding: "12px"
|
|
140
|
-
}, children: /* @__PURE__ */ jsx("span", { style: { color: "#dc2626", fontSize: "14px" }, children: error }) });
|
|
141
|
-
}
|
|
142
|
-
if (!ad) {
|
|
119
|
+
if (loading || error || !ad) {
|
|
143
120
|
return null;
|
|
144
121
|
}
|
|
145
122
|
const imageUrl = getBestImage(ad);
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.tsx"],"sourcesContent":["import React, { useEffect, useState, useCallback } from 'react'\r\n\r\nexport interface AniAdsProps {\r\n creator_wallet: string\r\n app_name: string\r\n user_wallet_address: string\r\n api_url?: string // Optional API URL, defaults to production\r\n onAdClick?: (adId: string, destinationUrl: string) => void // Optional click handler\r\n}\r\n\r\ninterface Ad {\r\n id: string\r\n title: string\r\n destination_url: string\r\n image_square: string\r\n image_portrait: string\r\n image_landscape: string\r\n status: 'active' | 'paused' | 'deleting'\r\n remaining_budget: number\r\n}\r\n\r\nexport const AniAds: React.FC<AniAdsProps> = ({\r\n creator_wallet,\r\n app_name,\r\n user_wallet_address,\r\n api_url = 'https://ani-ads.vercel.app', // Default API URL\r\n onAdClick,\r\n}) => {\r\n // Normalize API URL - ensure it doesn't have trailing slash for proper path joining\r\n const normalizedApiUrl = api_url.replace(/\\/+$/, '')\r\n \r\n const [ad, setAd] = useState<Ad | null>(null)\r\n const [loading, setLoading] = useState(true)\r\n const [error, setError] = useState<string | null>(null)\r\n\r\n // Get image URL - always use image_square (380x90 banner format)\r\n const getImageUrl = (ad: Ad): string | null => {\r\n return ad.image_square || null\r\n }\r\n\r\n // Track ad click\r\n const handleClick = useCallback(async () => {\r\n if (!ad || !user_wallet_address) return\r\n\r\n try {\r\n // Track click on backend\r\n const response = await fetch(`${normalizedApiUrl}/api/ads/click`, {\r\n method: 'POST',\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n },\r\n body: JSON.stringify({\r\n ad_id: ad.id,\r\n creator_wallet: creator_wallet.toLowerCase(),\r\n app_name,\r\n user_wallet_address: user_wallet_address.toLowerCase(),\r\n }),\r\n })\r\n\r\n if (response.ok) {\r\n // Call optional click handler\r\n if (onAdClick) {\r\n onAdClick(ad.id, ad.destination_url)\r\n }\r\n\r\n // Open destination URL\r\n window.open(ad.destination_url, '_blank', 'noopener,noreferrer')\r\n\r\n // Don't fetch new ad after click - keep showing the same ad until page reload\r\n }\r\n } catch (err) {\r\n console.error('Error tracking click:', err)\r\n // Still open the URL even if tracking fails\r\n window.open(ad.destination_url, '_blank', 'noopener,noreferrer')\r\n }\r\n }, [ad, creator_wallet, app_name, user_wallet_address, normalizedApiUrl, onAdClick])\r\n\r\n // Get image - always use banner format (380x90)\r\n const getBestImage = (ad: Ad): string | null => {\r\n return getImageUrl(ad)\r\n }\r\n\r\n // Track impression when ad is displayed\r\n const trackImpression = useCallback(async (adId: string) => {\r\n if (!adId || !creator_wallet || !app_name || !user_wallet_address) return\r\n\r\n try {\r\n await fetch(`${normalizedApiUrl}/api/ads/impression`, {\r\n method: 'POST',\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n },\r\n body: JSON.stringify({\r\n ad_id: adId,\r\n creator_wallet: creator_wallet.toLowerCase(),\r\n app_name,\r\n user_wallet_address: user_wallet_address.toLowerCase(),\r\n }),\r\n })\r\n // Don't show errors to user - impression tracking is not critical\r\n } catch (err) {\r\n console.error('Error tracking impression:', err)\r\n // Silently fail - impression tracking should not break the ad display\r\n }\r\n }, [creator_wallet, app_name, user_wallet_address, normalizedApiUrl])\r\n\r\n // Fetch ad only once when component mounts or when creator_wallet/app_name changes\r\n useEffect(() => {\r\n let cancelled = false\r\n \r\n const loadAd = async () => {\r\n if (!creator_wallet || !app_name) {\r\n setLoading(false)\r\n return\r\n }\r\n\r\n try {\r\n setLoading(true)\r\n setError(null)\r\n\r\n const response = await fetch(`${normalizedApiUrl}/api/ads/sdk`, {\r\n method: 'POST',\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n },\r\n body: JSON.stringify({\r\n creator_wallet: creator_wallet.toLowerCase(),\r\n app_name,\r\n }),\r\n })\r\n\r\n if (cancelled) return\r\n\r\n if (!response.ok) {\r\n const errorData = await response.json().catch(() => ({}))\r\n throw new Error(errorData.error || `HTTP ${response.status}: Failed to fetch ads`)\r\n }\r\n\r\n const data = await response.json()\r\n \r\n if (cancelled) return\r\n \r\n if (data.ad) {\r\n setAd(data.ad)\r\n setError(null) // Clear any previous errors\r\n \r\n // Track impression when ad is loaded and displayed\r\n trackImpression(data.ad.id)\r\n } else {\r\n // No ad available, but not an error - just don't show anything\r\n setAd(null)\r\n setError(null)\r\n }\r\n } catch (err) {\r\n if (cancelled) return\r\n console.error('Error fetching ad:', err)\r\n const errorMessage = err instanceof Error ? err.message : 'Failed to load ad'\r\n setError(errorMessage)\r\n setAd(null)\r\n } finally {\r\n if (!cancelled) {\r\n setLoading(false)\r\n }\r\n }\r\n }\r\n\r\n loadAd()\r\n\r\n return () => {\r\n cancelled = true\r\n }\r\n }, [creator_wallet, app_name, normalizedApiUrl, trackImpression]) // Only depend on props, not derived values\r\n\r\n if (loading) {\r\n return (\r\n <div style={{\r\n display: 'flex',\r\n width: '100%',\r\n minHeight: '100px',\r\n backgroundColor: '#f3f4f6',\r\n borderRadius: '8px',\r\n alignItems: 'center',\r\n justifyContent: 'center',\r\n }}>\r\n <span style={{ color: '#6b7280' }}>Loading ad...</span>\r\n </div>\r\n )\r\n }\r\n\r\n if (error) {\r\n return (\r\n <div style={{\r\n display: 'flex',\r\n width: '100%',\r\n minHeight: '100px',\r\n backgroundColor: '#fee2e2',\r\n borderRadius: '8px',\r\n alignItems: 'center',\r\n justifyContent: 'center',\r\n padding: '12px',\r\n }}>\r\n <span style={{ color: '#dc2626', fontSize: '14px' }}>{error}</span>\r\n </div>\r\n )\r\n }\r\n\r\n if (!ad) {\r\n return null // Don't render anything if no ad available\r\n }\r\n\r\n const imageUrl = getBestImage(ad)\r\n\r\n if (!imageUrl) {\r\n return null\r\n }\r\n\r\n return (\r\n <div\r\n style={{\r\n display: 'inline-block',\r\n width: '100%',\r\n cursor: 'pointer',\r\n borderRadius: '8px',\r\n overflow: 'hidden',\r\n transition: 'transform 0.2s, box-shadow 0.2s',\r\n }}\r\n onClick={handleClick}\r\n onMouseEnter={(e) => {\r\n e.currentTarget.style.transform = 'scale(1.02)'\r\n e.currentTarget.style.boxShadow = '0 4px 12px rgba(0,0,0,0.15)'\r\n }}\r\n onMouseLeave={(e) => {\r\n e.currentTarget.style.transform = 'scale(1)'\r\n e.currentTarget.style.boxShadow = 'none'\r\n }}\r\n >\r\n <img\r\n src={imageUrl}\r\n alt={ad.title}\r\n style={{\r\n width: '100%',\r\n height: 'auto',\r\n display: 'block',\r\n }}\r\n loading=\"lazy\"\r\n />\r\n </div>\r\n )\r\n}\r\n\r\nexport default AniAds\r\n"],"mappings":"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","names":["ad"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.tsx"],"sourcesContent":["import React, { useEffect, useState, useCallback } from 'react'\r\n\r\nexport interface AniAdsProps {\r\n creator_wallet: string\r\n app_name: string\r\n user_wallet_address: string\r\n api_url?: string // Optional API URL, defaults to production\r\n onAdClick?: (adId: string, destinationUrl: string) => void // Optional click handler\r\n}\r\n\r\ninterface Ad {\r\n id: string\r\n title: string\r\n destination_url: string\r\n image_square: string\r\n image_portrait: string\r\n image_landscape: string\r\n status: 'active' | 'paused' | 'deleting'\r\n remaining_budget: number\r\n}\r\n\r\nexport const AniAds: React.FC<AniAdsProps> = ({\r\n creator_wallet,\r\n app_name,\r\n user_wallet_address,\r\n api_url = 'https://ani-ads.vercel.app', // Default API URL\r\n onAdClick,\r\n}) => {\r\n // Normalize API URL - ensure it doesn't have trailing slash for proper path joining\r\n const normalizedApiUrl = api_url.replace(/\\/+$/, '')\r\n \r\n const [ad, setAd] = useState<Ad | null>(null)\r\n const [loading, setLoading] = useState(true)\r\n const [error, setError] = useState<string | null>(null)\r\n\r\n // Get image URL - always use image_square (380x90 banner format)\r\n const getImageUrl = (ad: Ad): string | null => {\r\n return ad.image_square || null\r\n }\r\n\r\n // Track ad click\r\n const handleClick = useCallback(async () => {\r\n if (!ad || !user_wallet_address) return\r\n\r\n try {\r\n // Track click on backend\r\n const response = await fetch(`${normalizedApiUrl}/api/ads/click`, {\r\n method: 'POST',\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n },\r\n body: JSON.stringify({\r\n ad_id: ad.id,\r\n creator_wallet: creator_wallet.toLowerCase(),\r\n app_name,\r\n user_wallet_address: user_wallet_address.toLowerCase(),\r\n }),\r\n })\r\n\r\n if (response.ok) {\r\n // Call optional click handler\r\n if (onAdClick) {\r\n onAdClick(ad.id, ad.destination_url)\r\n }\r\n\r\n // Open destination URL\r\n window.open(ad.destination_url, '_blank', 'noopener,noreferrer')\r\n\r\n // Don't fetch new ad after click - keep showing the same ad until page reload\r\n }\r\n } catch (err) {\r\n console.error('Error tracking click:', err)\r\n // Still open the URL even if tracking fails\r\n window.open(ad.destination_url, '_blank', 'noopener,noreferrer')\r\n }\r\n }, [ad, creator_wallet, app_name, user_wallet_address, normalizedApiUrl, onAdClick])\r\n\r\n // Get image - always use banner format (380x90)\r\n const getBestImage = (ad: Ad): string | null => {\r\n return getImageUrl(ad)\r\n }\r\n\r\n // Track impression when ad is displayed\r\n const trackImpression = useCallback(async (adId: string) => {\r\n if (!adId || !creator_wallet || !app_name || !user_wallet_address) return\r\n\r\n try {\r\n await fetch(`${normalizedApiUrl}/api/ads/impression`, {\r\n method: 'POST',\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n },\r\n body: JSON.stringify({\r\n ad_id: adId,\r\n creator_wallet: creator_wallet.toLowerCase(),\r\n app_name,\r\n user_wallet_address: user_wallet_address.toLowerCase(),\r\n }),\r\n })\r\n // Don't show errors to user - impression tracking is not critical\r\n } catch (err) {\r\n console.error('Error tracking impression:', err)\r\n // Silently fail - impression tracking should not break the ad display\r\n }\r\n }, [creator_wallet, app_name, user_wallet_address, normalizedApiUrl])\r\n\r\n // Fetch ad only once when component mounts or when creator_wallet/app_name changes\r\n useEffect(() => {\r\n let cancelled = false\r\n \r\n const loadAd = async () => {\r\n if (!creator_wallet || !app_name) {\r\n setLoading(false)\r\n return\r\n }\r\n\r\n try {\r\n setLoading(true)\r\n setError(null)\r\n\r\n const response = await fetch(`${normalizedApiUrl}/api/ads/sdk`, {\r\n method: 'POST',\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n },\r\n body: JSON.stringify({\r\n creator_wallet: creator_wallet.toLowerCase(),\r\n app_name,\r\n }),\r\n })\r\n\r\n if (cancelled) return\r\n\r\n if (!response.ok) {\r\n const errorData = await response.json().catch(() => ({}))\r\n throw new Error(errorData.error || `HTTP ${response.status}: Failed to fetch ads`)\r\n }\r\n\r\n const data = await response.json()\r\n \r\n if (cancelled) return\r\n \r\n if (data.ad) {\r\n setAd(data.ad)\r\n setError(null) // Clear any previous errors\r\n \r\n // Track impression when ad is loaded and displayed\r\n trackImpression(data.ad.id)\r\n } else {\r\n // No ad available, but not an error - just don't show anything\r\n setAd(null)\r\n setError(null)\r\n }\r\n } catch (err) {\r\n if (cancelled) return\r\n console.error('Error fetching ad:', err)\r\n const errorMessage = err instanceof Error ? err.message : 'Failed to load ad'\r\n setError(errorMessage)\r\n setAd(null)\r\n } finally {\r\n if (!cancelled) {\r\n setLoading(false)\r\n }\r\n }\r\n }\r\n\r\n loadAd()\r\n\r\n return () => {\r\n cancelled = true\r\n }\r\n }, [creator_wallet, app_name, normalizedApiUrl, trackImpression]) // Only depend on props, not derived values\r\n\r\n // Don't show anything while loading, on error, or when no ad - only render when ad is ready\r\n if (loading || error || !ad) {\r\n return null\r\n }\r\n\r\n const imageUrl = getBestImage(ad)\r\n\r\n if (!imageUrl) {\r\n return null\r\n }\r\n\r\n return (\r\n <div\r\n style={{\r\n display: 'inline-block',\r\n width: '100%',\r\n cursor: 'pointer',\r\n borderRadius: '8px',\r\n overflow: 'hidden',\r\n transition: 'transform 0.2s, box-shadow 0.2s',\r\n }}\r\n onClick={handleClick}\r\n onMouseEnter={(e) => {\r\n e.currentTarget.style.transform = 'scale(1.02)'\r\n e.currentTarget.style.boxShadow = '0 4px 12px rgba(0,0,0,0.15)'\r\n }}\r\n onMouseLeave={(e) => {\r\n e.currentTarget.style.transform = 'scale(1)'\r\n e.currentTarget.style.boxShadow = 'none'\r\n }}\r\n >\r\n <img\r\n src={imageUrl}\r\n alt={ad.title}\r\n style={{\r\n width: '100%',\r\n height: 'auto',\r\n display: 'block',\r\n }}\r\n loading=\"lazy\"\r\n />\r\n </div>\r\n )\r\n}\r\n\r\nexport default AniAds\r\n"],"mappings":"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","names":["ad"]}
|
package/package.json
CHANGED
|
@@ -1,45 +1,45 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "ani-ads-sdk",
|
|
3
|
-
"version": "2.0.
|
|
4
|
-
"description": "Ani Ads SDK for Mini App Creators to display ads in their apps",
|
|
5
|
-
"main": "dist/index.js",
|
|
6
|
-
"module": "dist/index.mjs",
|
|
7
|
-
"types": "dist/index.d.ts",
|
|
8
|
-
"exports": {
|
|
9
|
-
".": {
|
|
10
|
-
"types": "./dist/index.d.ts",
|
|
11
|
-
"import": "./dist/index.mjs",
|
|
12
|
-
"require": "./dist/index.js"
|
|
13
|
-
}
|
|
14
|
-
},
|
|
15
|
-
"files": [
|
|
16
|
-
"dist"
|
|
17
|
-
],
|
|
18
|
-
"scripts": {
|
|
19
|
-
"build": "tsup",
|
|
20
|
-
"dev": "tsup --watch"
|
|
21
|
-
},
|
|
22
|
-
"keywords": [
|
|
23
|
-
"ads",
|
|
24
|
-
"worldchain",
|
|
25
|
-
"world-app",
|
|
26
|
-
"mini-app"
|
|
27
|
-
],
|
|
28
|
-
"author": "",
|
|
29
|
-
"license": "MIT",
|
|
30
|
-
"devDependencies": {
|
|
31
|
-
"@types/react": "^19",
|
|
32
|
-
"@types/react-dom": "^19",
|
|
33
|
-
"eruda": "^3.4.3",
|
|
34
|
-
"tsup": "^8.0.0",
|
|
35
|
-
"typescript": "^5"
|
|
36
|
-
},
|
|
37
|
-
"peerDependencies": {
|
|
38
|
-
"react": "^18.0.0 || ^19.0.0",
|
|
39
|
-
"react-dom": "^18.0.0 || ^19.0.0"
|
|
40
|
-
},
|
|
41
|
-
"dependencies": {
|
|
42
|
-
"react": "^19.2.0",
|
|
43
|
-
"react-dom": "^19.2.0"
|
|
44
|
-
}
|
|
45
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "ani-ads-sdk",
|
|
3
|
+
"version": "2.0.2",
|
|
4
|
+
"description": "Ani Ads SDK for Mini App Creators to display ads in their apps",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"module": "dist/index.mjs",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.mjs",
|
|
12
|
+
"require": "./dist/index.js"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist"
|
|
17
|
+
],
|
|
18
|
+
"scripts": {
|
|
19
|
+
"build": "tsup",
|
|
20
|
+
"dev": "tsup --watch"
|
|
21
|
+
},
|
|
22
|
+
"keywords": [
|
|
23
|
+
"ads",
|
|
24
|
+
"worldchain",
|
|
25
|
+
"world-app",
|
|
26
|
+
"mini-app"
|
|
27
|
+
],
|
|
28
|
+
"author": "",
|
|
29
|
+
"license": "MIT",
|
|
30
|
+
"devDependencies": {
|
|
31
|
+
"@types/react": "^19",
|
|
32
|
+
"@types/react-dom": "^19",
|
|
33
|
+
"eruda": "^3.4.3",
|
|
34
|
+
"tsup": "^8.0.0",
|
|
35
|
+
"typescript": "^5"
|
|
36
|
+
},
|
|
37
|
+
"peerDependencies": {
|
|
38
|
+
"react": "^18.0.0 || ^19.0.0",
|
|
39
|
+
"react-dom": "^18.0.0 || ^19.0.0"
|
|
40
|
+
},
|
|
41
|
+
"dependencies": {
|
|
42
|
+
"react": "^19.2.0",
|
|
43
|
+
"react-dom": "^19.2.0"
|
|
44
|
+
}
|
|
45
|
+
}
|