@adtogether/web-sdk 0.1.0

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/CHANGELOG.md ADDED
@@ -0,0 +1,3 @@
1
+ ## 0.1.0
2
+
3
+ * Initial release of the AdTogether Web SDK.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 AdTogether
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,57 @@
1
+ // src/core/AdTogether.ts
2
+ var AdTogether = class _AdTogether {
3
+ constructor() {
4
+ this.baseUrl = "https://adtogether.com";
5
+ }
6
+ static get shared() {
7
+ if (!_AdTogether.instance) {
8
+ _AdTogether.instance = new _AdTogether();
9
+ }
10
+ return _AdTogether.instance;
11
+ }
12
+ static initialize(options) {
13
+ const sdk = _AdTogether.shared;
14
+ sdk.appId = options.appId;
15
+ if (options.baseUrl) {
16
+ sdk.baseUrl = options.baseUrl;
17
+ }
18
+ console.log(`AdTogether SDK Initialized with App ID: ${options.appId}`);
19
+ }
20
+ assertInitialized() {
21
+ if (!this.appId) {
22
+ console.error("AdTogether Error: SDK has not been initialized. Please call AdTogether.initialize() before displaying ads.");
23
+ return false;
24
+ }
25
+ return true;
26
+ }
27
+ static async fetchAd(adUnitId) {
28
+ if (!_AdTogether.shared.assertInitialized()) {
29
+ throw new Error("AdTogether not initialized");
30
+ }
31
+ const response = await fetch(`${_AdTogether.shared.baseUrl}/api/ads/serve?country=global&adUnitId=${adUnitId}`);
32
+ if (!response.ok) {
33
+ throw new Error(`Failed to fetch ad. Status: ${response.status}`);
34
+ }
35
+ return response.json();
36
+ }
37
+ static trackImpression(adId) {
38
+ this.trackEvent("/api/ads/impression", adId);
39
+ }
40
+ static trackClick(adId) {
41
+ this.trackEvent("/api/ads/click", adId);
42
+ }
43
+ static trackEvent(endpoint, adId) {
44
+ if (!_AdTogether.shared.assertInitialized()) return;
45
+ fetch(`${_AdTogether.shared.baseUrl}${endpoint}`, {
46
+ method: "POST",
47
+ headers: {
48
+ "Content-Type": "application/json"
49
+ },
50
+ body: JSON.stringify({ adId })
51
+ }).catch(console.error);
52
+ }
53
+ };
54
+
55
+ export {
56
+ AdTogether
57
+ };
@@ -0,0 +1,27 @@
1
+ interface AdModel {
2
+ id: string;
3
+ title: string;
4
+ description: string;
5
+ clickUrl?: string;
6
+ imageUrl?: string;
7
+ }
8
+ interface AdTogetherOptions {
9
+ appId: string;
10
+ baseUrl?: string;
11
+ }
12
+
13
+ declare class AdTogether {
14
+ private static instance;
15
+ private appId?;
16
+ baseUrl: string;
17
+ private constructor();
18
+ static get shared(): AdTogether;
19
+ static initialize(options: AdTogetherOptions): void;
20
+ assertInitialized(): boolean;
21
+ static fetchAd(adUnitId: string): Promise<AdModel>;
22
+ static trackImpression(adId: string): void;
23
+ static trackClick(adId: string): void;
24
+ private static trackEvent;
25
+ }
26
+
27
+ export { type AdModel, AdTogether, type AdTogetherOptions };
@@ -0,0 +1,27 @@
1
+ interface AdModel {
2
+ id: string;
3
+ title: string;
4
+ description: string;
5
+ clickUrl?: string;
6
+ imageUrl?: string;
7
+ }
8
+ interface AdTogetherOptions {
9
+ appId: string;
10
+ baseUrl?: string;
11
+ }
12
+
13
+ declare class AdTogether {
14
+ private static instance;
15
+ private appId?;
16
+ baseUrl: string;
17
+ private constructor();
18
+ static get shared(): AdTogether;
19
+ static initialize(options: AdTogetherOptions): void;
20
+ assertInitialized(): boolean;
21
+ static fetchAd(adUnitId: string): Promise<AdModel>;
22
+ static trackImpression(adId: string): void;
23
+ static trackClick(adId: string): void;
24
+ private static trackEvent;
25
+ }
26
+
27
+ export { type AdModel, AdTogether, type AdTogetherOptions };
package/dist/index.js ADDED
@@ -0,0 +1,83 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ AdTogether: () => AdTogether
24
+ });
25
+ module.exports = __toCommonJS(index_exports);
26
+
27
+ // src/core/AdTogether.ts
28
+ var AdTogether = class _AdTogether {
29
+ constructor() {
30
+ this.baseUrl = "https://adtogether.com";
31
+ }
32
+ static get shared() {
33
+ if (!_AdTogether.instance) {
34
+ _AdTogether.instance = new _AdTogether();
35
+ }
36
+ return _AdTogether.instance;
37
+ }
38
+ static initialize(options) {
39
+ const sdk = _AdTogether.shared;
40
+ sdk.appId = options.appId;
41
+ if (options.baseUrl) {
42
+ sdk.baseUrl = options.baseUrl;
43
+ }
44
+ console.log(`AdTogether SDK Initialized with App ID: ${options.appId}`);
45
+ }
46
+ assertInitialized() {
47
+ if (!this.appId) {
48
+ console.error("AdTogether Error: SDK has not been initialized. Please call AdTogether.initialize() before displaying ads.");
49
+ return false;
50
+ }
51
+ return true;
52
+ }
53
+ static async fetchAd(adUnitId) {
54
+ if (!_AdTogether.shared.assertInitialized()) {
55
+ throw new Error("AdTogether not initialized");
56
+ }
57
+ const response = await fetch(`${_AdTogether.shared.baseUrl}/api/ads/serve?country=global&adUnitId=${adUnitId}`);
58
+ if (!response.ok) {
59
+ throw new Error(`Failed to fetch ad. Status: ${response.status}`);
60
+ }
61
+ return response.json();
62
+ }
63
+ static trackImpression(adId) {
64
+ this.trackEvent("/api/ads/impression", adId);
65
+ }
66
+ static trackClick(adId) {
67
+ this.trackEvent("/api/ads/click", adId);
68
+ }
69
+ static trackEvent(endpoint, adId) {
70
+ if (!_AdTogether.shared.assertInitialized()) return;
71
+ fetch(`${_AdTogether.shared.baseUrl}${endpoint}`, {
72
+ method: "POST",
73
+ headers: {
74
+ "Content-Type": "application/json"
75
+ },
76
+ body: JSON.stringify({ adId })
77
+ }).catch(console.error);
78
+ }
79
+ };
80
+ // Annotate the CommonJS export names for ESM import in node:
81
+ 0 && (module.exports = {
82
+ AdTogether
83
+ });
package/dist/index.mjs ADDED
@@ -0,0 +1,6 @@
1
+ import {
2
+ AdTogether
3
+ } from "./chunk-IDJUJZAL.mjs";
4
+ export {
5
+ AdTogether
6
+ };
@@ -0,0 +1,18 @@
1
+ import React from 'react';
2
+
3
+ interface AdTogetherBannerProps {
4
+ adUnitId: string;
5
+ className?: string;
6
+ style?: React.CSSProperties;
7
+ onAdLoaded?: () => void;
8
+ onAdFailedToLoad?: (error: Error) => void;
9
+ /** Width of the ad element. Defaults to '100%' */
10
+ width?: number | string;
11
+ /** Height of the ad element. Defaults to 'auto' */
12
+ height?: number | string;
13
+ /** Pass 'dark' to use dark mode, 'light' for light mode, or 'auto' (default) to respect system preference */
14
+ theme?: 'dark' | 'light' | 'auto';
15
+ }
16
+ declare const AdTogetherBanner: React.FC<AdTogetherBannerProps>;
17
+
18
+ export { AdTogetherBanner, type AdTogetherBannerProps };
@@ -0,0 +1,18 @@
1
+ import React from 'react';
2
+
3
+ interface AdTogetherBannerProps {
4
+ adUnitId: string;
5
+ className?: string;
6
+ style?: React.CSSProperties;
7
+ onAdLoaded?: () => void;
8
+ onAdFailedToLoad?: (error: Error) => void;
9
+ /** Width of the ad element. Defaults to '100%' */
10
+ width?: number | string;
11
+ /** Height of the ad element. Defaults to 'auto' */
12
+ height?: number | string;
13
+ /** Pass 'dark' to use dark mode, 'light' for light mode, or 'auto' (default) to respect system preference */
14
+ theme?: 'dark' | 'light' | 'auto';
15
+ }
16
+ declare const AdTogetherBanner: React.FC<AdTogetherBannerProps>;
17
+
18
+ export { AdTogetherBanner, type AdTogetherBannerProps };
@@ -0,0 +1,231 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/react/index.ts
21
+ var react_exports = {};
22
+ __export(react_exports, {
23
+ AdTogetherBanner: () => AdTogetherBanner
24
+ });
25
+ module.exports = __toCommonJS(react_exports);
26
+
27
+ // src/react/AdTogetherBanner.tsx
28
+ var import_react = require("react");
29
+
30
+ // src/core/AdTogether.ts
31
+ var AdTogether = class _AdTogether {
32
+ constructor() {
33
+ this.baseUrl = "https://adtogether.com";
34
+ }
35
+ static get shared() {
36
+ if (!_AdTogether.instance) {
37
+ _AdTogether.instance = new _AdTogether();
38
+ }
39
+ return _AdTogether.instance;
40
+ }
41
+ static initialize(options) {
42
+ const sdk = _AdTogether.shared;
43
+ sdk.appId = options.appId;
44
+ if (options.baseUrl) {
45
+ sdk.baseUrl = options.baseUrl;
46
+ }
47
+ console.log(`AdTogether SDK Initialized with App ID: ${options.appId}`);
48
+ }
49
+ assertInitialized() {
50
+ if (!this.appId) {
51
+ console.error("AdTogether Error: SDK has not been initialized. Please call AdTogether.initialize() before displaying ads.");
52
+ return false;
53
+ }
54
+ return true;
55
+ }
56
+ static async fetchAd(adUnitId) {
57
+ if (!_AdTogether.shared.assertInitialized()) {
58
+ throw new Error("AdTogether not initialized");
59
+ }
60
+ const response = await fetch(`${_AdTogether.shared.baseUrl}/api/ads/serve?country=global&adUnitId=${adUnitId}`);
61
+ if (!response.ok) {
62
+ throw new Error(`Failed to fetch ad. Status: ${response.status}`);
63
+ }
64
+ return response.json();
65
+ }
66
+ static trackImpression(adId) {
67
+ this.trackEvent("/api/ads/impression", adId);
68
+ }
69
+ static trackClick(adId) {
70
+ this.trackEvent("/api/ads/click", adId);
71
+ }
72
+ static trackEvent(endpoint, adId) {
73
+ if (!_AdTogether.shared.assertInitialized()) return;
74
+ fetch(`${_AdTogether.shared.baseUrl}${endpoint}`, {
75
+ method: "POST",
76
+ headers: {
77
+ "Content-Type": "application/json"
78
+ },
79
+ body: JSON.stringify({ adId })
80
+ }).catch(console.error);
81
+ }
82
+ };
83
+
84
+ // src/react/AdTogetherBanner.tsx
85
+ var import_jsx_runtime = require("react/jsx-runtime");
86
+ var AdTogetherBanner = ({
87
+ adUnitId,
88
+ className = "",
89
+ style = {},
90
+ onAdLoaded,
91
+ onAdFailedToLoad,
92
+ width = "100%",
93
+ height = "auto",
94
+ theme = "auto"
95
+ }) => {
96
+ const [adData, setAdData] = (0, import_react.useState)(null);
97
+ const [isLoading, setIsLoading] = (0, import_react.useState)(true);
98
+ const [hasError, setHasError] = (0, import_react.useState)(false);
99
+ const [isDarkMode, setIsDarkMode] = (0, import_react.useState)(theme === "dark");
100
+ const containerRef = (0, import_react.useRef)(null);
101
+ const impressionTrackedRef = (0, import_react.useRef)(false);
102
+ (0, import_react.useEffect)(() => {
103
+ if (theme === "auto") {
104
+ const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
105
+ setIsDarkMode(mediaQuery.matches);
106
+ const handler = (e) => setIsDarkMode(e.matches);
107
+ mediaQuery.addEventListener("change", handler);
108
+ return () => mediaQuery.removeEventListener("change", handler);
109
+ } else {
110
+ setIsDarkMode(theme === "dark");
111
+ }
112
+ }, [theme]);
113
+ (0, import_react.useEffect)(() => {
114
+ let isMounted = true;
115
+ AdTogether.fetchAd(adUnitId).then((ad) => {
116
+ if (isMounted) {
117
+ setAdData(ad);
118
+ setIsLoading(false);
119
+ onAdLoaded?.();
120
+ }
121
+ }).catch((err) => {
122
+ if (isMounted) {
123
+ console.error("AdTogether Failed to load ad:", err);
124
+ setHasError(true);
125
+ setIsLoading(false);
126
+ onAdFailedToLoad?.(err);
127
+ }
128
+ });
129
+ return () => {
130
+ isMounted = false;
131
+ };
132
+ }, [adUnitId, onAdLoaded, onAdFailedToLoad]);
133
+ (0, import_react.useEffect)(() => {
134
+ if (!adData || isLoading || hasError || !containerRef.current) return;
135
+ const observer = new IntersectionObserver(
136
+ (entries) => {
137
+ if (entries[0].isIntersecting && entries[0].intersectionRatio >= 0.5 && !impressionTrackedRef.current) {
138
+ impressionTrackedRef.current = true;
139
+ AdTogether.trackImpression(adData.id);
140
+ observer.disconnect();
141
+ }
142
+ },
143
+ { threshold: 0.5 }
144
+ );
145
+ observer.observe(containerRef.current);
146
+ return () => {
147
+ observer.disconnect();
148
+ };
149
+ }, [adData, isLoading, hasError]);
150
+ const handleContainerClick = () => {
151
+ if (!adData) return;
152
+ AdTogether.trackClick(adData.id);
153
+ if (adData.clickUrl) {
154
+ window.open(adData.clickUrl, "_blank", "noopener,noreferrer");
155
+ }
156
+ };
157
+ if (isLoading) {
158
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
159
+ "div",
160
+ {
161
+ style: { width, height, ...style },
162
+ className
163
+ }
164
+ );
165
+ }
166
+ if (hasError || !adData) {
167
+ return null;
168
+ }
169
+ const bgColor = isDarkMode ? "#1F2937" : "#ffffff";
170
+ const borderColor = isDarkMode ? "rgba(255,255,255,0.1)" : "rgba(0,0,0,0.1)";
171
+ const textColor = isDarkMode ? "#F9FAFB" : "#111827";
172
+ const descColor = isDarkMode ? "#9CA3AF" : "#6B7280";
173
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
174
+ "div",
175
+ {
176
+ ref: containerRef,
177
+ className: `adtogether-banner ${className}`,
178
+ onClick: handleContainerClick,
179
+ style: {
180
+ display: "flex",
181
+ flexDirection: "row",
182
+ width,
183
+ height,
184
+ backgroundColor: bgColor,
185
+ borderRadius: "12px",
186
+ border: `1px solid ${borderColor}`,
187
+ boxShadow: "0 4px 10px rgba(0, 0, 0, 0.05)",
188
+ overflow: "hidden",
189
+ cursor: "pointer",
190
+ boxSizing: "border-box",
191
+ ...style
192
+ },
193
+ onMouseOver: (e) => {
194
+ e.currentTarget.style.transform = "scale(1.02)";
195
+ e.currentTarget.style.transition = "transform 0.2s";
196
+ },
197
+ onMouseOut: (e) => {
198
+ e.currentTarget.style.transform = "scale(1)";
199
+ },
200
+ children: [
201
+ adData.imageUrl && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { flexShrink: 0, width: "120px", height: "100%", minHeight: "80px" }, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
202
+ "img",
203
+ {
204
+ src: adData.imageUrl,
205
+ alt: adData.title,
206
+ style: { width: "100%", height: "100%", objectFit: "cover" }
207
+ }
208
+ ) }),
209
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { padding: "12px", display: "flex", flexDirection: "column", justifyContent: "center", flex: 1, minWidth: 0 }, children: [
210
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { display: "flex", justifyContent: "space-between", alignItems: "flex-start", marginBottom: "4px" }, children: [
211
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { fontWeight: "bold", fontSize: "14px", color: textColor, whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }, children: adData.title }),
212
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: {
213
+ backgroundColor: "#FBBF24",
214
+ color: "#000",
215
+ fontSize: "9px",
216
+ fontWeight: "bold",
217
+ padding: "2px 4px",
218
+ borderRadius: "4px",
219
+ marginLeft: "8px"
220
+ }, children: "AD" })
221
+ ] }),
222
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { fontSize: "12px", color: descColor, overflow: "hidden", display: "-webkit-box", WebkitLineClamp: 2, WebkitBoxOrient: "vertical" }, children: adData.description })
223
+ ] })
224
+ ]
225
+ }
226
+ );
227
+ };
228
+ // Annotate the CommonJS export names for ESM import in node:
229
+ 0 && (module.exports = {
230
+ AdTogetherBanner
231
+ });
@@ -0,0 +1,152 @@
1
+ import {
2
+ AdTogether
3
+ } from "../chunk-IDJUJZAL.mjs";
4
+
5
+ // src/react/AdTogetherBanner.tsx
6
+ import { useEffect, useRef, useState } from "react";
7
+ import { jsx, jsxs } from "react/jsx-runtime";
8
+ var AdTogetherBanner = ({
9
+ adUnitId,
10
+ className = "",
11
+ style = {},
12
+ onAdLoaded,
13
+ onAdFailedToLoad,
14
+ width = "100%",
15
+ height = "auto",
16
+ theme = "auto"
17
+ }) => {
18
+ const [adData, setAdData] = useState(null);
19
+ const [isLoading, setIsLoading] = useState(true);
20
+ const [hasError, setHasError] = useState(false);
21
+ const [isDarkMode, setIsDarkMode] = useState(theme === "dark");
22
+ const containerRef = useRef(null);
23
+ const impressionTrackedRef = useRef(false);
24
+ useEffect(() => {
25
+ if (theme === "auto") {
26
+ const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
27
+ setIsDarkMode(mediaQuery.matches);
28
+ const handler = (e) => setIsDarkMode(e.matches);
29
+ mediaQuery.addEventListener("change", handler);
30
+ return () => mediaQuery.removeEventListener("change", handler);
31
+ } else {
32
+ setIsDarkMode(theme === "dark");
33
+ }
34
+ }, [theme]);
35
+ useEffect(() => {
36
+ let isMounted = true;
37
+ AdTogether.fetchAd(adUnitId).then((ad) => {
38
+ if (isMounted) {
39
+ setAdData(ad);
40
+ setIsLoading(false);
41
+ onAdLoaded?.();
42
+ }
43
+ }).catch((err) => {
44
+ if (isMounted) {
45
+ console.error("AdTogether Failed to load ad:", err);
46
+ setHasError(true);
47
+ setIsLoading(false);
48
+ onAdFailedToLoad?.(err);
49
+ }
50
+ });
51
+ return () => {
52
+ isMounted = false;
53
+ };
54
+ }, [adUnitId, onAdLoaded, onAdFailedToLoad]);
55
+ useEffect(() => {
56
+ if (!adData || isLoading || hasError || !containerRef.current) return;
57
+ const observer = new IntersectionObserver(
58
+ (entries) => {
59
+ if (entries[0].isIntersecting && entries[0].intersectionRatio >= 0.5 && !impressionTrackedRef.current) {
60
+ impressionTrackedRef.current = true;
61
+ AdTogether.trackImpression(adData.id);
62
+ observer.disconnect();
63
+ }
64
+ },
65
+ { threshold: 0.5 }
66
+ );
67
+ observer.observe(containerRef.current);
68
+ return () => {
69
+ observer.disconnect();
70
+ };
71
+ }, [adData, isLoading, hasError]);
72
+ const handleContainerClick = () => {
73
+ if (!adData) return;
74
+ AdTogether.trackClick(adData.id);
75
+ if (adData.clickUrl) {
76
+ window.open(adData.clickUrl, "_blank", "noopener,noreferrer");
77
+ }
78
+ };
79
+ if (isLoading) {
80
+ return /* @__PURE__ */ jsx(
81
+ "div",
82
+ {
83
+ style: { width, height, ...style },
84
+ className
85
+ }
86
+ );
87
+ }
88
+ if (hasError || !adData) {
89
+ return null;
90
+ }
91
+ const bgColor = isDarkMode ? "#1F2937" : "#ffffff";
92
+ const borderColor = isDarkMode ? "rgba(255,255,255,0.1)" : "rgba(0,0,0,0.1)";
93
+ const textColor = isDarkMode ? "#F9FAFB" : "#111827";
94
+ const descColor = isDarkMode ? "#9CA3AF" : "#6B7280";
95
+ return /* @__PURE__ */ jsxs(
96
+ "div",
97
+ {
98
+ ref: containerRef,
99
+ className: `adtogether-banner ${className}`,
100
+ onClick: handleContainerClick,
101
+ style: {
102
+ display: "flex",
103
+ flexDirection: "row",
104
+ width,
105
+ height,
106
+ backgroundColor: bgColor,
107
+ borderRadius: "12px",
108
+ border: `1px solid ${borderColor}`,
109
+ boxShadow: "0 4px 10px rgba(0, 0, 0, 0.05)",
110
+ overflow: "hidden",
111
+ cursor: "pointer",
112
+ boxSizing: "border-box",
113
+ ...style
114
+ },
115
+ onMouseOver: (e) => {
116
+ e.currentTarget.style.transform = "scale(1.02)";
117
+ e.currentTarget.style.transition = "transform 0.2s";
118
+ },
119
+ onMouseOut: (e) => {
120
+ e.currentTarget.style.transform = "scale(1)";
121
+ },
122
+ children: [
123
+ adData.imageUrl && /* @__PURE__ */ jsx("div", { style: { flexShrink: 0, width: "120px", height: "100%", minHeight: "80px" }, children: /* @__PURE__ */ jsx(
124
+ "img",
125
+ {
126
+ src: adData.imageUrl,
127
+ alt: adData.title,
128
+ style: { width: "100%", height: "100%", objectFit: "cover" }
129
+ }
130
+ ) }),
131
+ /* @__PURE__ */ jsxs("div", { style: { padding: "12px", display: "flex", flexDirection: "column", justifyContent: "center", flex: 1, minWidth: 0 }, children: [
132
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", justifyContent: "space-between", alignItems: "flex-start", marginBottom: "4px" }, children: [
133
+ /* @__PURE__ */ jsx("span", { style: { fontWeight: "bold", fontSize: "14px", color: textColor, whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }, children: adData.title }),
134
+ /* @__PURE__ */ jsx("span", { style: {
135
+ backgroundColor: "#FBBF24",
136
+ color: "#000",
137
+ fontSize: "9px",
138
+ fontWeight: "bold",
139
+ padding: "2px 4px",
140
+ borderRadius: "4px",
141
+ marginLeft: "8px"
142
+ }, children: "AD" })
143
+ ] }),
144
+ /* @__PURE__ */ jsx("span", { style: { fontSize: "12px", color: descColor, overflow: "hidden", display: "-webkit-box", WebkitLineClamp: 2, WebkitBoxOrient: "vertical" }, children: adData.description })
145
+ ] })
146
+ ]
147
+ }
148
+ );
149
+ };
150
+ export {
151
+ AdTogetherBanner
152
+ };
package/errors.txt ADDED
Binary file
package/package.json ADDED
@@ -0,0 +1,46 @@
1
+ {
2
+ "name": "@adtogether/web-sdk",
3
+ "version": "0.1.0",
4
+ "description": "The official AdTogether Web SDK for monetizing applications.",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "https://github.com/AdTogether/AdTogether.git",
8
+ "directory": "sdk/web-sdk"
9
+ },
10
+ "publishConfig": {
11
+ "access": "public"
12
+ },
13
+ "main": "./dist/index.js",
14
+ "module": "./dist/index.mjs",
15
+ "types": "./dist/index.d.ts",
16
+ "exports": {
17
+ ".": {
18
+ "types": "./dist/index.d.ts",
19
+ "require": "./dist/index.js",
20
+ "import": "./dist/index.mjs"
21
+ },
22
+ "./react": {
23
+ "types": "./dist/react/index.d.ts",
24
+ "require": "./dist/react/index.js",
25
+ "import": "./dist/react/index.mjs"
26
+ }
27
+ },
28
+ "scripts": {
29
+ "build": "tsup",
30
+ "dev": "tsup --watch"
31
+ },
32
+ "keywords": [],
33
+ "author": "",
34
+ "license": "ISC",
35
+ "type": "commonjs",
36
+ "peerDependencies": {
37
+ "react": "^19.2.4",
38
+ "react-dom": "^19.2.4"
39
+ },
40
+ "devDependencies": {
41
+ "@types/react": "^19.2.14",
42
+ "@types/react-dom": "^19.2.3",
43
+ "tsup": "^8.5.1",
44
+ "typescript": "^5.3.3"
45
+ }
46
+ }
@@ -0,0 +1,65 @@
1
+ import { AdModel, AdTogetherOptions } from './types';
2
+
3
+ export class AdTogether {
4
+ private static instance: AdTogether;
5
+ private appId?: string;
6
+ public baseUrl: string = 'https://adtogether.com';
7
+
8
+ private constructor() {}
9
+
10
+ static get shared(): AdTogether {
11
+ if (!AdTogether.instance) {
12
+ AdTogether.instance = new AdTogether();
13
+ }
14
+ return AdTogether.instance;
15
+ }
16
+
17
+ static initialize(options: AdTogetherOptions) {
18
+ const sdk = AdTogether.shared;
19
+ sdk.appId = options.appId;
20
+ if (options.baseUrl) {
21
+ sdk.baseUrl = options.baseUrl;
22
+ }
23
+ console.log(`AdTogether SDK Initialized with App ID: ${options.appId}`);
24
+ }
25
+
26
+ assertInitialized() {
27
+ if (!this.appId) {
28
+ console.error('AdTogether Error: SDK has not been initialized. Please call AdTogether.initialize() before displaying ads.');
29
+ return false;
30
+ }
31
+ return true;
32
+ }
33
+
34
+ static async fetchAd(adUnitId: string): Promise<AdModel> {
35
+ if (!AdTogether.shared.assertInitialized()) {
36
+ throw new Error('AdTogether not initialized');
37
+ }
38
+
39
+ const response = await fetch(`${AdTogether.shared.baseUrl}/api/ads/serve?country=global&adUnitId=${adUnitId}`);
40
+ if (!response.ok) {
41
+ throw new Error(`Failed to fetch ad. Status: ${response.status}`);
42
+ }
43
+ return response.json();
44
+ }
45
+
46
+ static trackImpression(adId: string) {
47
+ this.trackEvent('/api/ads/impression', adId);
48
+ }
49
+
50
+ static trackClick(adId: string) {
51
+ this.trackEvent('/api/ads/click', adId);
52
+ }
53
+
54
+ private static trackEvent(endpoint: string, adId: string) {
55
+ if (!AdTogether.shared.assertInitialized()) return;
56
+
57
+ fetch(`${AdTogether.shared.baseUrl}${endpoint}`, {
58
+ method: 'POST',
59
+ headers: {
60
+ 'Content-Type': 'application/json',
61
+ },
62
+ body: JSON.stringify({ adId }),
63
+ }).catch(console.error);
64
+ }
65
+ }
@@ -0,0 +1,12 @@
1
+ export interface AdModel {
2
+ id: string;
3
+ title: string;
4
+ description: string;
5
+ clickUrl?: string;
6
+ imageUrl?: string;
7
+ }
8
+
9
+ export interface AdTogetherOptions {
10
+ appId: string;
11
+ baseUrl?: string;
12
+ }
package/src/index.ts ADDED
@@ -0,0 +1,2 @@
1
+ export * from './core/AdTogether';
2
+ export * from './core/types';
@@ -0,0 +1,184 @@
1
+ import React, { useEffect, useRef, useState } from 'react';
2
+ import { AdTogether } from '../core/AdTogether';
3
+ import { AdModel } from '../core/types';
4
+
5
+ export interface AdTogetherBannerProps {
6
+ adUnitId: string;
7
+ className?: string;
8
+ style?: React.CSSProperties;
9
+ onAdLoaded?: () => void;
10
+ onAdFailedToLoad?: (error: Error) => void;
11
+ /** Width of the ad element. Defaults to '100%' */
12
+ width?: number | string;
13
+ /** Height of the ad element. Defaults to 'auto' */
14
+ height?: number | string;
15
+ /** Pass 'dark' to use dark mode, 'light' for light mode, or 'auto' (default) to respect system preference */
16
+ theme?: 'dark' | 'light' | 'auto';
17
+ }
18
+
19
+ export const AdTogetherBanner: React.FC<AdTogetherBannerProps> = ({
20
+ adUnitId,
21
+ className = '',
22
+ style = {},
23
+ onAdLoaded,
24
+ onAdFailedToLoad,
25
+ width = '100%',
26
+ height = 'auto',
27
+ theme = 'auto',
28
+ }) => {
29
+ const [adData, setAdData] = useState<AdModel | null>(null);
30
+ const [isLoading, setIsLoading] = useState(true);
31
+ const [hasError, setHasError] = useState(false);
32
+ const [isDarkMode, setIsDarkMode] = useState(theme === 'dark');
33
+
34
+ const containerRef = useRef<HTMLDivElement>(null);
35
+ const impressionTrackedRef = useRef(false);
36
+
37
+ // Handle system theme changes
38
+ useEffect(() => {
39
+ if (theme === 'auto') {
40
+ const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
41
+ setIsDarkMode(mediaQuery.matches);
42
+
43
+ const handler = (e: MediaQueryListEvent) => setIsDarkMode(e.matches);
44
+ mediaQuery.addEventListener('change', handler);
45
+ return () => mediaQuery.removeEventListener('change', handler);
46
+ } else {
47
+ setIsDarkMode(theme === 'dark');
48
+ }
49
+ }, [theme]);
50
+
51
+ // Fetch Ad
52
+ useEffect(() => {
53
+ let isMounted = true;
54
+
55
+ AdTogether.fetchAd(adUnitId)
56
+ .then((ad) => {
57
+ if (isMounted) {
58
+ setAdData(ad);
59
+ setIsLoading(false);
60
+ onAdLoaded?.();
61
+ }
62
+ })
63
+ .catch((err) => {
64
+ if (isMounted) {
65
+ console.error('AdTogether Failed to load ad:', err);
66
+ setHasError(true);
67
+ setIsLoading(false);
68
+ onAdFailedToLoad?.(err);
69
+ }
70
+ });
71
+
72
+ return () => {
73
+ isMounted = false;
74
+ };
75
+ }, [adUnitId, onAdLoaded, onAdFailedToLoad]);
76
+
77
+ // Impression tracking
78
+ useEffect(() => {
79
+ if (!adData || isLoading || hasError || !containerRef.current) return;
80
+
81
+ const observer = new IntersectionObserver(
82
+ (entries) => {
83
+ if (entries[0].isIntersecting && entries[0].intersectionRatio >= 0.5 && !impressionTrackedRef.current) {
84
+ impressionTrackedRef.current = true;
85
+ AdTogether.trackImpression(adData.id);
86
+ observer.disconnect();
87
+ }
88
+ },
89
+ { threshold: 0.5 }
90
+ );
91
+
92
+ observer.observe(containerRef.current);
93
+
94
+ return () => {
95
+ observer.disconnect();
96
+ };
97
+ }, [adData, isLoading, hasError]);
98
+
99
+ const handleContainerClick = () => {
100
+ if (!adData) return;
101
+ AdTogether.trackClick(adData.id);
102
+ if (adData.clickUrl) {
103
+ window.open(adData.clickUrl, '_blank', 'noopener,noreferrer');
104
+ }
105
+ };
106
+
107
+ if (isLoading) {
108
+ return (
109
+ <div
110
+ style={{ width, height, ...style }}
111
+ className={className}
112
+ />
113
+ );
114
+ }
115
+
116
+ if (hasError || !adData) {
117
+ return null;
118
+ }
119
+
120
+ const bgColor = isDarkMode ? '#1F2937' : '#ffffff';
121
+ const borderColor = isDarkMode ? 'rgba(255,255,255,0.1)' : 'rgba(0,0,0,0.1)';
122
+ const textColor = isDarkMode ? '#F9FAFB' : '#111827';
123
+ const descColor = isDarkMode ? '#9CA3AF' : '#6B7280';
124
+
125
+ return (
126
+ <div
127
+ ref={containerRef}
128
+ className={`adtogether-banner ${className}`}
129
+ onClick={handleContainerClick}
130
+ style={{
131
+ display: 'flex',
132
+ flexDirection: 'row',
133
+ width,
134
+ height,
135
+ backgroundColor: bgColor,
136
+ borderRadius: '12px',
137
+ border: `1px solid ${borderColor}`,
138
+ boxShadow: '0 4px 10px rgba(0, 0, 0, 0.05)',
139
+ overflow: 'hidden',
140
+ cursor: 'pointer',
141
+ boxSizing: 'border-box',
142
+ ...style,
143
+ }}
144
+ onMouseOver={(e) => {
145
+ e.currentTarget.style.transform = 'scale(1.02)';
146
+ e.currentTarget.style.transition = 'transform 0.2s';
147
+ }}
148
+ onMouseOut={(e) => {
149
+ e.currentTarget.style.transform = 'scale(1)';
150
+ }}
151
+ >
152
+ {adData.imageUrl && (
153
+ <div style={{ flexShrink: 0, width: '120px', height: '100%', minHeight: '80px' }}>
154
+ <img
155
+ src={adData.imageUrl}
156
+ alt={adData.title}
157
+ style={{ width: '100%', height: '100%', objectFit: 'cover' }}
158
+ />
159
+ </div>
160
+ )}
161
+ <div style={{ padding: '12px', display: 'flex', flexDirection: 'column', justifyContent: 'center', flex: 1, minWidth: 0 }}>
162
+ <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', marginBottom: '4px' }}>
163
+ <span style={{ fontWeight: 'bold', fontSize: '14px', color: textColor, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>
164
+ {adData.title}
165
+ </span>
166
+ <span style={{
167
+ backgroundColor: '#FBBF24',
168
+ color: '#000',
169
+ fontSize: '9px',
170
+ fontWeight: 'bold',
171
+ padding: '2px 4px',
172
+ borderRadius: '4px',
173
+ marginLeft: '8px'
174
+ }}>
175
+ AD
176
+ </span>
177
+ </div>
178
+ <span style={{ fontSize: '12px', color: descColor, overflow: 'hidden', display: '-webkit-box', WebkitLineClamp: 2, WebkitBoxOrient: 'vertical' }}>
179
+ {adData.description}
180
+ </span>
181
+ </div>
182
+ </div>
183
+ );
184
+ };
@@ -0,0 +1 @@
1
+ export * from './AdTogetherBanner';
package/tsconfig.json ADDED
@@ -0,0 +1,19 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "es2020",
4
+ "module": "esnext",
5
+ "lib": ["dom", "dom.iterable", "esnext"],
6
+ "declaration": true,
7
+ "declarationMap": true,
8
+ "sourceMap": true,
9
+ "outDir": "dist",
10
+ "strict": true,
11
+ "esModuleInterop": true,
12
+ "skipLibCheck": true,
13
+ "forceConsistentCasingInFileNames": true,
14
+ "jsx": "react-jsx",
15
+ "moduleResolution": "bundler"
16
+ },
17
+ "include": ["src"],
18
+ "exclude": ["node_modules", "dist"]
19
+ }
package/tsup.config.ts ADDED
@@ -0,0 +1,9 @@
1
+ import { defineConfig } from 'tsup';
2
+
3
+ export default defineConfig({
4
+ entry: ['src/index.ts', 'src/react/index.ts'],
5
+ format: ['cjs', 'esm'],
6
+ dts: true,
7
+ clean: true,
8
+ external: ['react', 'react-dom'],
9
+ });