ani-ads-sdk 2.0.2 → 2.0.4

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 CHANGED
@@ -41,10 +41,26 @@ var AniAds = ({
41
41
  const getImageUrl = (ad2) => {
42
42
  return ad2.image_square || null;
43
43
  };
44
- const handleClick = (0, import_react.useCallback)(async () => {
45
- if (!ad || !user_wallet_address) return;
44
+ const trackClick = (0, import_react.useCallback)(async () => {
45
+ console.log("[AniAds SDK] Click detected", {
46
+ hasAd: !!ad,
47
+ hasWalletAddress: !!user_wallet_address,
48
+ destinationUrl: ad?.destination_url,
49
+ adId: ad?.id
50
+ });
51
+ if (!ad || !user_wallet_address) {
52
+ console.warn("[AniAds SDK] Click tracking skipped - missing ad or wallet address");
53
+ return;
54
+ }
46
55
  try {
47
- const response = await fetch(`${normalizedApiUrl}/api/ads/click`, {
56
+ console.log("[AniAds SDK] Sending click tracking request", {
57
+ ad_id: ad.id,
58
+ creator_wallet: creator_wallet.toLowerCase(),
59
+ app_name,
60
+ user_wallet_address: user_wallet_address.toLowerCase(),
61
+ api_url: `${normalizedApiUrl}/api/ads/click`
62
+ });
63
+ fetch(`${normalizedApiUrl}/api/ads/click`, {
48
64
  method: "POST",
49
65
  headers: {
50
66
  "Content-Type": "application/json"
@@ -55,16 +71,20 @@ var AniAds = ({
55
71
  app_name,
56
72
  user_wallet_address: user_wallet_address.toLowerCase()
57
73
  })
58
- });
59
- if (response.ok) {
60
- if (onAdClick) {
74
+ }).then((response) => {
75
+ console.log("[AniAds SDK] Click tracking response", {
76
+ status: response.status,
77
+ ok: response.ok
78
+ });
79
+ if (response.ok && onAdClick) {
80
+ console.log("[AniAds SDK] Calling onAdClick callback");
61
81
  onAdClick(ad.id, ad.destination_url);
62
82
  }
63
- window.open(ad.destination_url, "_blank", "noopener,noreferrer");
64
- }
83
+ }).catch((err) => {
84
+ console.error("[AniAds SDK] Error tracking click:", err);
85
+ });
65
86
  } catch (err) {
66
- console.error("Error tracking click:", err);
67
- window.open(ad.destination_url, "_blank", "noopener,noreferrer");
87
+ console.error("[AniAds SDK] Error in trackClick:", err);
68
88
  }
69
89
  }, [ad, creator_wallet, app_name, user_wallet_address, normalizedApiUrl, onAdClick]);
70
90
  const getBestImage = (ad2) => {
@@ -149,17 +169,27 @@ var AniAds = ({
149
169
  return null;
150
170
  }
151
171
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
152
- "div",
172
+ "a",
153
173
  {
174
+ href: ad.destination_url,
175
+ target: "_blank",
176
+ rel: "noopener noreferrer",
177
+ onClick: (e) => {
178
+ console.log("[AniAds SDK] Anchor tag onClick fired", {
179
+ destinationUrl: ad.destination_url,
180
+ href: e.currentTarget.href
181
+ });
182
+ trackClick();
183
+ },
154
184
  style: {
155
185
  display: "inline-block",
156
186
  width: "100%",
157
187
  cursor: "pointer",
158
188
  borderRadius: "8px",
159
189
  overflow: "hidden",
160
- transition: "transform 0.2s, box-shadow 0.2s"
190
+ transition: "transform 0.2s, box-shadow 0.2s",
191
+ textDecoration: "none"
161
192
  },
162
- onClick: handleClick,
163
193
  onMouseEnter: (e) => {
164
194
  e.currentTarget.style.transform = "scale(1.02)";
165
195
  e.currentTarget.style.boxShadow = "0 4px 12px rgba(0,0,0,0.15)";
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 // 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"]}
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 - fire and forget, don't block navigation\r\n const trackClick = useCallback(async () => {\r\n console.log('[AniAds SDK] Click detected', {\r\n hasAd: !!ad,\r\n hasWalletAddress: !!user_wallet_address,\r\n destinationUrl: ad?.destination_url,\r\n adId: ad?.id\r\n })\r\n\r\n if (!ad || !user_wallet_address) {\r\n console.warn('[AniAds SDK] Click tracking skipped - missing ad or wallet address')\r\n return\r\n }\r\n\r\n try {\r\n console.log('[AniAds SDK] Sending click tracking request', {\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 api_url: `${normalizedApiUrl}/api/ads/click`\r\n })\r\n\r\n // Track click on backend (fire and forget - don't wait for response)\r\n 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 .then((response) => {\r\n console.log('[AniAds SDK] Click tracking response', {\r\n status: response.status,\r\n ok: response.ok\r\n })\r\n if (response.ok && onAdClick) {\r\n console.log('[AniAds SDK] Calling onAdClick callback')\r\n onAdClick(ad.id, ad.destination_url)\r\n }\r\n })\r\n .catch((err) => {\r\n console.error('[AniAds SDK] Error tracking click:', err)\r\n })\r\n } catch (err) {\r\n console.error('[AniAds SDK] Error in trackClick:', err)\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 // Use an anchor tag for webview compatibility\r\n // Let the link navigate normally, track click in parallel\r\n return (\r\n <a\r\n href={ad.destination_url}\r\n target=\"_blank\"\r\n rel=\"noopener noreferrer\"\r\n onClick={(e) => {\r\n console.log('[AniAds SDK] Anchor tag onClick fired', {\r\n destinationUrl: ad.destination_url,\r\n href: e.currentTarget.href\r\n })\r\n trackClick()\r\n // Don't prevent default - let the link navigate normally\r\n }}\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 textDecoration: 'none',\r\n }}\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 </a>\r\n )\r\n}\r\n\r\nexport default AniAds\r\n"],"mappings":"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","names":["ad"]}
package/dist/index.mjs CHANGED
@@ -16,10 +16,26 @@ var AniAds = ({
16
16
  const getImageUrl = (ad2) => {
17
17
  return ad2.image_square || null;
18
18
  };
19
- const handleClick = useCallback(async () => {
20
- if (!ad || !user_wallet_address) return;
19
+ const trackClick = useCallback(async () => {
20
+ console.log("[AniAds SDK] Click detected", {
21
+ hasAd: !!ad,
22
+ hasWalletAddress: !!user_wallet_address,
23
+ destinationUrl: ad?.destination_url,
24
+ adId: ad?.id
25
+ });
26
+ if (!ad || !user_wallet_address) {
27
+ console.warn("[AniAds SDK] Click tracking skipped - missing ad or wallet address");
28
+ return;
29
+ }
21
30
  try {
22
- const response = await fetch(`${normalizedApiUrl}/api/ads/click`, {
31
+ console.log("[AniAds SDK] Sending click tracking request", {
32
+ ad_id: ad.id,
33
+ creator_wallet: creator_wallet.toLowerCase(),
34
+ app_name,
35
+ user_wallet_address: user_wallet_address.toLowerCase(),
36
+ api_url: `${normalizedApiUrl}/api/ads/click`
37
+ });
38
+ fetch(`${normalizedApiUrl}/api/ads/click`, {
23
39
  method: "POST",
24
40
  headers: {
25
41
  "Content-Type": "application/json"
@@ -30,16 +46,20 @@ var AniAds = ({
30
46
  app_name,
31
47
  user_wallet_address: user_wallet_address.toLowerCase()
32
48
  })
33
- });
34
- if (response.ok) {
35
- if (onAdClick) {
49
+ }).then((response) => {
50
+ console.log("[AniAds SDK] Click tracking response", {
51
+ status: response.status,
52
+ ok: response.ok
53
+ });
54
+ if (response.ok && onAdClick) {
55
+ console.log("[AniAds SDK] Calling onAdClick callback");
36
56
  onAdClick(ad.id, ad.destination_url);
37
57
  }
38
- window.open(ad.destination_url, "_blank", "noopener,noreferrer");
39
- }
58
+ }).catch((err) => {
59
+ console.error("[AniAds SDK] Error tracking click:", err);
60
+ });
40
61
  } catch (err) {
41
- console.error("Error tracking click:", err);
42
- window.open(ad.destination_url, "_blank", "noopener,noreferrer");
62
+ console.error("[AniAds SDK] Error in trackClick:", err);
43
63
  }
44
64
  }, [ad, creator_wallet, app_name, user_wallet_address, normalizedApiUrl, onAdClick]);
45
65
  const getBestImage = (ad2) => {
@@ -124,17 +144,27 @@ var AniAds = ({
124
144
  return null;
125
145
  }
126
146
  return /* @__PURE__ */ jsx(
127
- "div",
147
+ "a",
128
148
  {
149
+ href: ad.destination_url,
150
+ target: "_blank",
151
+ rel: "noopener noreferrer",
152
+ onClick: (e) => {
153
+ console.log("[AniAds SDK] Anchor tag onClick fired", {
154
+ destinationUrl: ad.destination_url,
155
+ href: e.currentTarget.href
156
+ });
157
+ trackClick();
158
+ },
129
159
  style: {
130
160
  display: "inline-block",
131
161
  width: "100%",
132
162
  cursor: "pointer",
133
163
  borderRadius: "8px",
134
164
  overflow: "hidden",
135
- transition: "transform 0.2s, box-shadow 0.2s"
165
+ transition: "transform 0.2s, box-shadow 0.2s",
166
+ textDecoration: "none"
136
167
  },
137
- onClick: handleClick,
138
168
  onMouseEnter: (e) => {
139
169
  e.currentTarget.style.transform = "scale(1.02)";
140
170
  e.currentTarget.style.boxShadow = "0 4px 12px rgba(0,0,0,0.15)";
@@ -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 // 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"]}
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 - fire and forget, don't block navigation\r\n const trackClick = useCallback(async () => {\r\n console.log('[AniAds SDK] Click detected', {\r\n hasAd: !!ad,\r\n hasWalletAddress: !!user_wallet_address,\r\n destinationUrl: ad?.destination_url,\r\n adId: ad?.id\r\n })\r\n\r\n if (!ad || !user_wallet_address) {\r\n console.warn('[AniAds SDK] Click tracking skipped - missing ad or wallet address')\r\n return\r\n }\r\n\r\n try {\r\n console.log('[AniAds SDK] Sending click tracking request', {\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 api_url: `${normalizedApiUrl}/api/ads/click`\r\n })\r\n\r\n // Track click on backend (fire and forget - don't wait for response)\r\n 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 .then((response) => {\r\n console.log('[AniAds SDK] Click tracking response', {\r\n status: response.status,\r\n ok: response.ok\r\n })\r\n if (response.ok && onAdClick) {\r\n console.log('[AniAds SDK] Calling onAdClick callback')\r\n onAdClick(ad.id, ad.destination_url)\r\n }\r\n })\r\n .catch((err) => {\r\n console.error('[AniAds SDK] Error tracking click:', err)\r\n })\r\n } catch (err) {\r\n console.error('[AniAds SDK] Error in trackClick:', err)\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 // Use an anchor tag for webview compatibility\r\n // Let the link navigate normally, track click in parallel\r\n return (\r\n <a\r\n href={ad.destination_url}\r\n target=\"_blank\"\r\n rel=\"noopener noreferrer\"\r\n onClick={(e) => {\r\n console.log('[AniAds SDK] Anchor tag onClick fired', {\r\n destinationUrl: ad.destination_url,\r\n href: e.currentTarget.href\r\n })\r\n trackClick()\r\n // Don't prevent default - let the link navigate normally\r\n }}\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 textDecoration: 'none',\r\n }}\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 </a>\r\n )\r\n}\r\n\r\nexport default AniAds\r\n"],"mappings":"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","names":["ad"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ani-ads-sdk",
3
- "version": "2.0.2",
3
+ "version": "2.0.4",
4
4
  "description": "Ani Ads SDK for Mini App Creators to display ads in their apps",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",