@adtogether/web-sdk 0.1.0 → 0.1.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.
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  AdTogether
3
- } from "../chunk-IDJUJZAL.mjs";
3
+ } from "../chunk-7IJIBJD4.mjs";
4
4
 
5
5
  // src/react/AdTogetherBanner.tsx
6
6
  import { useEffect, useRef, useState } from "react";
@@ -58,7 +58,7 @@ var AdTogetherBanner = ({
58
58
  (entries) => {
59
59
  if (entries[0].isIntersecting && entries[0].intersectionRatio >= 0.5 && !impressionTrackedRef.current) {
60
60
  impressionTrackedRef.current = true;
61
- AdTogether.trackImpression(adData.id);
61
+ AdTogether.trackImpression(adData.id, adData.token);
62
62
  observer.disconnect();
63
63
  }
64
64
  },
@@ -71,7 +71,7 @@ var AdTogetherBanner = ({
71
71
  }, [adData, isLoading, hasError]);
72
72
  const handleContainerClick = () => {
73
73
  if (!adData) return;
74
- AdTogether.trackClick(adData.id);
74
+ AdTogether.trackClick(adData.id, adData.token);
75
75
  if (adData.clickUrl) {
76
76
  window.open(adData.clickUrl, "_blank", "noopener,noreferrer");
77
77
  }
@@ -147,6 +147,290 @@ var AdTogetherBanner = ({
147
147
  }
148
148
  );
149
149
  };
150
+
151
+ // src/react/AdTogetherInterstitial.tsx
152
+ import { useEffect as useEffect2, useRef as useRef2, useState as useState2, useCallback } from "react";
153
+ import { createPortal } from "react-dom";
154
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
155
+ var AdTogetherInterstitial = ({
156
+ adUnitId,
157
+ isOpen,
158
+ onClose,
159
+ onAdLoaded,
160
+ onAdFailedToLoad,
161
+ theme = "auto",
162
+ closeDelay = 3
163
+ }) => {
164
+ const [adData, setAdData] = useState2(null);
165
+ const [isLoading, setIsLoading] = useState2(false);
166
+ const [hasError, setHasError] = useState2(false);
167
+ const [isDarkMode, setIsDarkMode] = useState2(theme === "dark");
168
+ const [canClose, setCanClose] = useState2(false);
169
+ const [countdown, setCountdown] = useState2(closeDelay);
170
+ const impressionTrackedRef = useRef2(false);
171
+ const closeTimerRef = useRef2(null);
172
+ const countdownRef = useRef2(null);
173
+ useEffect2(() => {
174
+ if (theme === "auto") {
175
+ const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
176
+ setIsDarkMode(mediaQuery.matches);
177
+ const handler = (e) => setIsDarkMode(e.matches);
178
+ mediaQuery.addEventListener("change", handler);
179
+ return () => mediaQuery.removeEventListener("change", handler);
180
+ } else {
181
+ setIsDarkMode(theme === "dark");
182
+ }
183
+ }, [theme]);
184
+ useEffect2(() => {
185
+ if (!isOpen) {
186
+ setAdData(null);
187
+ setIsLoading(false);
188
+ setHasError(false);
189
+ setCanClose(false);
190
+ setCountdown(closeDelay);
191
+ impressionTrackedRef.current = false;
192
+ if (closeTimerRef.current) clearTimeout(closeTimerRef.current);
193
+ if (countdownRef.current) clearInterval(countdownRef.current);
194
+ return;
195
+ }
196
+ let isMounted = true;
197
+ setIsLoading(true);
198
+ AdTogether.fetchAd(adUnitId, "interstitial").then((ad) => {
199
+ if (isMounted) {
200
+ setAdData(ad);
201
+ setIsLoading(false);
202
+ onAdLoaded?.();
203
+ }
204
+ }).catch((err) => {
205
+ if (isMounted) {
206
+ console.error("AdTogether Failed to load interstitial:", err);
207
+ setHasError(true);
208
+ setIsLoading(false);
209
+ onAdFailedToLoad?.(err);
210
+ onClose();
211
+ }
212
+ });
213
+ return () => {
214
+ isMounted = false;
215
+ };
216
+ }, [isOpen, adUnitId]);
217
+ useEffect2(() => {
218
+ if (!isOpen || !adData) return;
219
+ setCountdown(closeDelay);
220
+ countdownRef.current = setInterval(() => {
221
+ setCountdown((prev) => {
222
+ if (prev <= 1) {
223
+ if (countdownRef.current) clearInterval(countdownRef.current);
224
+ setCanClose(true);
225
+ return 0;
226
+ }
227
+ return prev - 1;
228
+ });
229
+ }, 1e3);
230
+ return () => {
231
+ if (countdownRef.current) clearInterval(countdownRef.current);
232
+ };
233
+ }, [isOpen, adData, closeDelay]);
234
+ useEffect2(() => {
235
+ if (!adData || !isOpen || impressionTrackedRef.current) return;
236
+ impressionTrackedRef.current = true;
237
+ AdTogether.trackImpression(adData.id, adData.token);
238
+ }, [adData, isOpen]);
239
+ const handleAdClick = useCallback(() => {
240
+ if (!adData) return;
241
+ AdTogether.trackClick(adData.id, adData.token);
242
+ if (adData.clickUrl) {
243
+ window.open(adData.clickUrl, "_blank", "noopener,noreferrer");
244
+ }
245
+ }, [adData]);
246
+ const handleClose = useCallback(() => {
247
+ if (canClose) {
248
+ onClose();
249
+ }
250
+ }, [canClose, onClose]);
251
+ useEffect2(() => {
252
+ if (isOpen) {
253
+ document.body.style.overflow = "hidden";
254
+ } else {
255
+ document.body.style.overflow = "";
256
+ }
257
+ return () => {
258
+ document.body.style.overflow = "";
259
+ };
260
+ }, [isOpen]);
261
+ if (!isOpen) return null;
262
+ const bgOverlay = "rgba(0, 0, 0, 0.7)";
263
+ const cardBg = isDarkMode ? "#1F2937" : "#ffffff";
264
+ const borderColor = isDarkMode ? "rgba(255,255,255,0.1)" : "rgba(0,0,0,0.1)";
265
+ const textColor = isDarkMode ? "#F9FAFB" : "#111827";
266
+ const descColor = isDarkMode ? "#9CA3AF" : "#6B7280";
267
+ const content = /* @__PURE__ */ jsxs2(
268
+ "div",
269
+ {
270
+ className: "adtogether-interstitial-overlay",
271
+ style: {
272
+ position: "fixed",
273
+ top: 0,
274
+ left: 0,
275
+ width: "100vw",
276
+ height: "100vh",
277
+ backgroundColor: bgOverlay,
278
+ display: "flex",
279
+ alignItems: "center",
280
+ justifyContent: "center",
281
+ zIndex: 1e5,
282
+ backdropFilter: "blur(8px)",
283
+ animation: "adtogether-fade-in 0.3s ease-out"
284
+ },
285
+ onClick: (e) => {
286
+ if (e.target === e.currentTarget && canClose) handleClose();
287
+ },
288
+ children: [
289
+ /* @__PURE__ */ jsx2("style", { children: `
290
+ @keyframes adtogether-fade-in {
291
+ from { opacity: 0; }
292
+ to { opacity: 1; }
293
+ }
294
+ @keyframes adtogether-scale-in {
295
+ from { opacity: 0; transform: scale(0.9); }
296
+ to { opacity: 1; transform: scale(1); }
297
+ }
298
+ ` }),
299
+ isLoading ? /* @__PURE__ */ jsx2("div", { style: { color: "#fff", fontSize: "18px" }, children: "Loading Ad..." }) : adData ? /* @__PURE__ */ jsxs2(
300
+ "div",
301
+ {
302
+ style: {
303
+ position: "relative",
304
+ maxWidth: "800px",
305
+ width: "95%",
306
+ backgroundColor: cardBg,
307
+ borderRadius: "24px",
308
+ border: `1px solid ${borderColor}`,
309
+ overflow: "hidden",
310
+ boxShadow: "0 25px 50px rgba(0, 0, 0, 0.5)",
311
+ animation: "adtogether-scale-in 0.3s ease-out"
312
+ },
313
+ children: [
314
+ /* @__PURE__ */ jsx2("div", { style: { position: "absolute", top: "12px", right: "12px", zIndex: 10 }, children: canClose ? /* @__PURE__ */ jsx2(
315
+ "button",
316
+ {
317
+ onClick: handleClose,
318
+ style: {
319
+ width: "36px",
320
+ height: "36px",
321
+ borderRadius: "50%",
322
+ backgroundColor: "rgba(0,0,0,0.6)",
323
+ border: "none",
324
+ color: "#fff",
325
+ fontSize: "18px",
326
+ cursor: "pointer",
327
+ display: "flex",
328
+ alignItems: "center",
329
+ justifyContent: "center",
330
+ backdropFilter: "blur(4px)"
331
+ },
332
+ "aria-label": "Close ad",
333
+ children: "\u2715"
334
+ }
335
+ ) : /* @__PURE__ */ jsx2(
336
+ "div",
337
+ {
338
+ style: {
339
+ width: "36px",
340
+ height: "36px",
341
+ borderRadius: "50%",
342
+ backgroundColor: "rgba(0,0,0,0.6)",
343
+ color: "#fff",
344
+ fontSize: "14px",
345
+ fontWeight: "bold",
346
+ display: "flex",
347
+ alignItems: "center",
348
+ justifyContent: "center",
349
+ backdropFilter: "blur(4px)"
350
+ },
351
+ children: countdown
352
+ }
353
+ ) }),
354
+ adData.imageUrl && /* @__PURE__ */ jsx2(
355
+ "div",
356
+ {
357
+ style: { cursor: "pointer", position: "relative" },
358
+ onClick: handleAdClick,
359
+ children: /* @__PURE__ */ jsx2(
360
+ "img",
361
+ {
362
+ src: adData.imageUrl,
363
+ alt: adData.title,
364
+ style: {
365
+ width: "100%",
366
+ aspectRatio: "16/9",
367
+ objectFit: "cover",
368
+ display: "block"
369
+ }
370
+ }
371
+ )
372
+ }
373
+ ),
374
+ /* @__PURE__ */ jsxs2(
375
+ "div",
376
+ {
377
+ style: { padding: "20px", cursor: "pointer" },
378
+ onClick: handleAdClick,
379
+ children: [
380
+ /* @__PURE__ */ jsxs2("div", { style: { display: "flex", justifyContent: "space-between", alignItems: "flex-start", marginBottom: "8px" }, children: [
381
+ /* @__PURE__ */ jsx2("span", { style: { fontWeight: "bold", fontSize: "18px", color: textColor }, children: adData.title }),
382
+ /* @__PURE__ */ jsx2(
383
+ "span",
384
+ {
385
+ style: {
386
+ backgroundColor: "#FBBF24",
387
+ color: "#000",
388
+ fontSize: "10px",
389
+ fontWeight: "bold",
390
+ padding: "3px 6px",
391
+ borderRadius: "4px",
392
+ marginLeft: "8px",
393
+ flexShrink: 0
394
+ },
395
+ children: "AD"
396
+ }
397
+ )
398
+ ] }),
399
+ /* @__PURE__ */ jsx2("p", { style: { fontSize: "14px", color: descColor, margin: 0, lineHeight: 1.5 }, children: adData.description }),
400
+ /* @__PURE__ */ jsx2(
401
+ "button",
402
+ {
403
+ style: {
404
+ marginTop: "16px",
405
+ width: "100%",
406
+ padding: "12px",
407
+ backgroundColor: "#F59E0B",
408
+ color: "#000",
409
+ fontWeight: "bold",
410
+ fontSize: "14px",
411
+ border: "none",
412
+ borderRadius: "12px",
413
+ cursor: "pointer"
414
+ },
415
+ onClick: (e) => {
416
+ e.stopPropagation();
417
+ handleAdClick();
418
+ },
419
+ children: "Learn More \u2192"
420
+ }
421
+ )
422
+ ]
423
+ }
424
+ )
425
+ ]
426
+ }
427
+ ) : null
428
+ ]
429
+ }
430
+ );
431
+ return createPortal(content, document.body);
432
+ };
150
433
  export {
151
- AdTogetherBanner
434
+ AdTogetherBanner,
435
+ AdTogetherInterstitial
152
436
  };
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "@adtogether/web-sdk",
3
- "version": "0.1.0",
4
- "description": "The official AdTogether Web SDK for monetizing applications.",
3
+ "version": "0.1.4",
4
+ "description": "The official AdTogether Web SDK reciprocal ad exchange to increase conversions and grow your audience.",
5
5
  "repository": {
6
6
  "type": "git",
7
- "url": "https://github.com/AdTogether/AdTogether.git",
7
+ "url": "git+https://github.com/undecided2003/AdTogether.git",
8
8
  "directory": "sdk/web-sdk"
9
9
  },
10
10
  "publishConfig": {
@@ -1,9 +1,10 @@
1
- import { AdModel, AdTogetherOptions } from './types';
1
+ import { AdModel, AdType, AdTogetherOptions } from './types';
2
2
 
3
3
  export class AdTogether {
4
4
  private static instance: AdTogether;
5
5
  private appId?: string;
6
- public baseUrl: string = 'https://adtogether.com';
6
+ private allowSelfAds: boolean = true;
7
+ public baseUrl: string = 'https://adtogether.relaxsoftwareapps.com';
7
8
 
8
9
  private constructor() {}
9
10
 
@@ -16,11 +17,20 @@ export class AdTogether {
16
17
 
17
18
  static initialize(options: AdTogetherOptions) {
18
19
  const sdk = AdTogether.shared;
19
- sdk.appId = options.appId;
20
+ sdk.appId = options.apiKey || options.appId;
21
+
22
+ if (options.allowSelfAds !== undefined) {
23
+ sdk.allowSelfAds = options.allowSelfAds;
24
+ }
25
+
20
26
  if (options.baseUrl) {
21
27
  sdk.baseUrl = options.baseUrl;
28
+ } else if (typeof window !== 'undefined') {
29
+ // In browser, default to relative paths if no base URL provided
30
+ sdk.baseUrl = '';
22
31
  }
23
- console.log(`AdTogether SDK Initialized with App ID: ${options.appId}`);
32
+
33
+ console.log(`AdTogether SDK Initialized with App ID: ${sdk.appId}`);
24
34
  }
25
35
 
26
36
  assertInitialized() {
@@ -31,27 +41,51 @@ export class AdTogether {
31
41
  return true;
32
42
  }
33
43
 
34
- static async fetchAd(adUnitId: string): Promise<AdModel> {
44
+ private lastAdId?: string;
45
+
46
+ static async fetchAd(adUnitId: string, adType?: AdType): Promise<AdModel> {
35
47
  if (!AdTogether.shared.assertInitialized()) {
36
48
  throw new Error('AdTogether not initialized');
37
49
  }
38
50
 
39
- const response = await fetch(`${AdTogether.shared.baseUrl}/api/ads/serve?country=global&adUnitId=${adUnitId}`);
40
- if (!response.ok) {
51
+ try {
52
+ const sdk = AdTogether.shared;
53
+ let url = `${sdk.baseUrl}/api/ads/serve?country=global&adUnitId=${adUnitId}&apiKey=${sdk.appId}`;
54
+ if (adType) {
55
+ url += `&adType=${adType}`;
56
+ }
57
+ if (sdk.lastAdId) {
58
+ url += `&exclude=${sdk.lastAdId}`;
59
+ }
60
+
61
+ // Pass allowSelfAds and source URL for smart serving
62
+ url += `&allowSelfAds=${sdk.allowSelfAds}`;
63
+ if (typeof window !== 'undefined') {
64
+ url += `&sourceUrl=${encodeURIComponent(window.location.href)}`;
65
+ }
66
+
67
+ const response = await fetch(url);
68
+ if (response.ok) {
69
+ const ad = await response.json();
70
+ sdk.lastAdId = ad.id;
71
+ return ad;
72
+ }
73
+
41
74
  throw new Error(`Failed to fetch ad. Status: ${response.status}`);
75
+ } catch (err) {
76
+ throw err;
42
77
  }
43
- return response.json();
44
78
  }
45
79
 
46
- static trackImpression(adId: string) {
47
- this.trackEvent('/api/ads/impression', adId);
80
+ static trackImpression(adId: string, token?: string) {
81
+ this.trackEvent('/api/ads/impression', adId, token);
48
82
  }
49
83
 
50
- static trackClick(adId: string) {
51
- this.trackEvent('/api/ads/click', adId);
84
+ static trackClick(adId: string, token?: string) {
85
+ this.trackEvent('/api/ads/click', adId, token);
52
86
  }
53
87
 
54
- private static trackEvent(endpoint: string, adId: string) {
88
+ private static trackEvent(endpoint: string, adId: string, token?: string) {
55
89
  if (!AdTogether.shared.assertInitialized()) return;
56
90
 
57
91
  fetch(`${AdTogether.shared.baseUrl}${endpoint}`, {
@@ -59,7 +93,11 @@ export class AdTogether {
59
93
  headers: {
60
94
  'Content-Type': 'application/json',
61
95
  },
62
- body: JSON.stringify({ adId }),
96
+ body: JSON.stringify({
97
+ adId,
98
+ token,
99
+ apiKey: AdTogether.shared.appId
100
+ }),
63
101
  }).catch(console.error);
64
102
  }
65
103
  }
package/src/core/types.ts CHANGED
@@ -1,12 +1,19 @@
1
+ export type AdType = 'banner' | 'interstitial';
2
+
1
3
  export interface AdModel {
2
4
  id: string;
3
5
  title: string;
4
6
  description: string;
5
7
  clickUrl?: string;
6
8
  imageUrl?: string;
9
+ token?: string;
10
+ adType?: AdType;
7
11
  }
8
12
 
9
13
  export interface AdTogetherOptions {
10
- appId: string;
14
+ appId?: string;
15
+ apiKey?: string;
11
16
  baseUrl?: string;
17
+ /** Whether to show your own ads as fallback if no external ads are available. Defaults to true. */
18
+ allowSelfAds?: boolean;
12
19
  }
@@ -82,7 +82,7 @@ export const AdTogetherBanner: React.FC<AdTogetherBannerProps> = ({
82
82
  (entries) => {
83
83
  if (entries[0].isIntersecting && entries[0].intersectionRatio >= 0.5 && !impressionTrackedRef.current) {
84
84
  impressionTrackedRef.current = true;
85
- AdTogether.trackImpression(adData.id);
85
+ AdTogether.trackImpression(adData.id, adData.token);
86
86
  observer.disconnect();
87
87
  }
88
88
  },
@@ -98,7 +98,7 @@ export const AdTogetherBanner: React.FC<AdTogetherBannerProps> = ({
98
98
 
99
99
  const handleContainerClick = () => {
100
100
  if (!adData) return;
101
- AdTogether.trackClick(adData.id);
101
+ AdTogether.trackClick(adData.id, adData.token);
102
102
  if (adData.clickUrl) {
103
103
  window.open(adData.clickUrl, '_blank', 'noopener,noreferrer');
104
104
  }