@cimplify/sdk 0.4.0 → 0.5.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/dist/react.js CHANGED
@@ -17,6 +17,306 @@ var EVENT_TYPES = {
17
17
  REQUIRES_OTP: "requires_otp",
18
18
  ERROR: "error",
19
19
  CHANGE: "change"};
20
+
21
+ // src/ads/identity.ts
22
+ var COOKIE_NAME = "_cimplify_uid";
23
+ var COOKIE_MAX_AGE = 60 * 60 * 24 * 365;
24
+ function getOrCreateUserId() {
25
+ if (typeof document === "undefined") return generateId();
26
+ const existing = getCookie(COOKIE_NAME);
27
+ if (existing) return existing;
28
+ const newId = generateId();
29
+ setCookie(COOKIE_NAME, newId, COOKIE_MAX_AGE);
30
+ return newId;
31
+ }
32
+ function collectDeviceSignals() {
33
+ if (typeof window === "undefined") {
34
+ return getEmptySignals();
35
+ }
36
+ return {
37
+ userAgent: navigator.userAgent,
38
+ language: navigator.language,
39
+ languages: Array.from(navigator.languages || []),
40
+ platform: navigator.platform,
41
+ screenWidth: screen.width,
42
+ screenHeight: screen.height,
43
+ colorDepth: screen.colorDepth,
44
+ pixelRatio: window.devicePixelRatio || 1,
45
+ timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
46
+ timezoneOffset: (/* @__PURE__ */ new Date()).getTimezoneOffset(),
47
+ touchSupport: "ontouchstart" in window || navigator.maxTouchPoints > 0,
48
+ cookiesEnabled: navigator.cookieEnabled,
49
+ doNotTrack: navigator.doNotTrack === "1"
50
+ };
51
+ }
52
+ function generateFingerprint(signals) {
53
+ const components = [
54
+ signals.userAgent,
55
+ signals.language,
56
+ signals.platform,
57
+ `${signals.screenWidth}x${signals.screenHeight}`,
58
+ signals.colorDepth.toString(),
59
+ signals.pixelRatio.toString(),
60
+ signals.timezone,
61
+ signals.touchSupport.toString()
62
+ ];
63
+ return hashString(components.join("|"));
64
+ }
65
+ function getUserIdentity(authenticatedAccountId) {
66
+ const signals = collectDeviceSignals();
67
+ const fingerprint = generateFingerprint(signals);
68
+ const cookieId = getOrCreateUserId();
69
+ return {
70
+ uid: authenticatedAccountId || cookieId,
71
+ isAuthenticated: !!authenticatedAccountId,
72
+ fingerprint,
73
+ signals
74
+ };
75
+ }
76
+ function getDeviceType(signals) {
77
+ const ua = signals.userAgent.toLowerCase();
78
+ const width = signals.screenWidth;
79
+ if (/mobile|android|iphone|ipod/.test(ua) || width < 768) {
80
+ return "mobile";
81
+ }
82
+ if (/tablet|ipad/.test(ua) || width >= 768 && width < 1024) {
83
+ return "tablet";
84
+ }
85
+ return "desktop";
86
+ }
87
+ function getBrowserInfo(signals) {
88
+ const ua = signals.userAgent;
89
+ const browsers = [
90
+ { name: "Chrome", regex: /Chrome\/(\d+)/ },
91
+ { name: "Firefox", regex: /Firefox\/(\d+)/ },
92
+ { name: "Safari", regex: /Version\/(\d+).*Safari/ },
93
+ { name: "Edge", regex: /Edg\/(\d+)/ },
94
+ { name: "Opera", regex: /OPR\/(\d+)/ }
95
+ ];
96
+ for (const browser of browsers) {
97
+ const match = ua.match(browser.regex);
98
+ if (match) {
99
+ return { name: browser.name, version: match[1] };
100
+ }
101
+ }
102
+ return { name: "Unknown", version: "0" };
103
+ }
104
+ function getOSInfo(signals) {
105
+ const ua = signals.userAgent;
106
+ if (/Windows NT 10/.test(ua)) return { name: "Windows", version: "10" };
107
+ if (/Windows NT 6.3/.test(ua)) return { name: "Windows", version: "8.1" };
108
+ if (/Mac OS X (\d+[._]\d+)/.test(ua)) {
109
+ const match = ua.match(/Mac OS X (\d+[._]\d+)/);
110
+ return { name: "macOS", version: match?.[1].replace("_", ".") || "" };
111
+ }
112
+ if (/Android (\d+)/.test(ua)) {
113
+ const match = ua.match(/Android (\d+)/);
114
+ return { name: "Android", version: match?.[1] || "" };
115
+ }
116
+ if (/iPhone OS (\d+)/.test(ua)) {
117
+ const match = ua.match(/iPhone OS (\d+)/);
118
+ return { name: "iOS", version: match?.[1] || "" };
119
+ }
120
+ if (/Linux/.test(ua)) return { name: "Linux", version: "" };
121
+ return { name: "Unknown", version: "" };
122
+ }
123
+ function generateId() {
124
+ const timestamp = Date.now().toString(36);
125
+ const random = Math.random().toString(36).substring(2, 10);
126
+ return `${timestamp}-${random}`;
127
+ }
128
+ function hashString(str) {
129
+ let hash = 0;
130
+ for (let i = 0; i < str.length; i++) {
131
+ const char = str.charCodeAt(i);
132
+ hash = (hash << 5) - hash + char;
133
+ hash = hash & hash;
134
+ }
135
+ return Math.abs(hash).toString(36);
136
+ }
137
+ function getCookie(name) {
138
+ if (typeof document === "undefined") return null;
139
+ const match = document.cookie.match(new RegExp(`(^| )${name}=([^;]+)`));
140
+ return match ? match[2] : null;
141
+ }
142
+ function setCookie(name, value, maxAge) {
143
+ if (typeof document === "undefined") return;
144
+ document.cookie = `${name}=${value};max-age=${maxAge};path=/;SameSite=Lax`;
145
+ }
146
+ function getEmptySignals() {
147
+ return {
148
+ userAgent: "",
149
+ language: "",
150
+ languages: [],
151
+ platform: "",
152
+ screenWidth: 0,
153
+ screenHeight: 0,
154
+ colorDepth: 0,
155
+ pixelRatio: 1,
156
+ timezone: "",
157
+ timezoneOffset: 0,
158
+ touchSupport: false,
159
+ cookiesEnabled: false,
160
+ doNotTrack: false
161
+ };
162
+ }
163
+ function deriveAdsApiUrl() {
164
+ if (typeof window === "undefined") return "https://api.cimplify.io";
165
+ const hostname = window.location.hostname;
166
+ if (hostname === "localhost" || hostname === "127.0.0.1") {
167
+ return `${window.location.protocol}//${hostname}:8080`;
168
+ }
169
+ return "https://api.cimplify.io";
170
+ }
171
+ var AdContext = react.createContext({
172
+ siteId: null,
173
+ config: null,
174
+ isLoading: true,
175
+ identity: null,
176
+ apiBase: "https://api.cimplify.io"
177
+ });
178
+ function useAds() {
179
+ return react.useContext(AdContext);
180
+ }
181
+ function AdProvider({
182
+ siteId,
183
+ apiBase,
184
+ authenticatedAccountId,
185
+ children
186
+ }) {
187
+ const resolvedApiBase = apiBase || deriveAdsApiUrl();
188
+ const [config, setConfig] = react.useState(null);
189
+ const [isLoading, setIsLoading] = react.useState(true);
190
+ const [identity, setIdentity] = react.useState(null);
191
+ react.useEffect(() => {
192
+ const userIdentity = getUserIdentity(authenticatedAccountId);
193
+ setIdentity(userIdentity);
194
+ fetch(`${resolvedApiBase}/ads/config/${siteId}`).then((r) => r.json()).then((data) => {
195
+ setConfig(data);
196
+ setIsLoading(false);
197
+ }).catch(() => {
198
+ setConfig({ enabled: false, theme: "auto" });
199
+ setIsLoading(false);
200
+ });
201
+ }, [siteId, resolvedApiBase, authenticatedAccountId]);
202
+ return /* @__PURE__ */ jsxRuntime.jsx(AdContext.Provider, { value: { siteId, config, isLoading, identity, apiBase: resolvedApiBase }, children });
203
+ }
204
+ function Ad({
205
+ slot,
206
+ position = "static",
207
+ className,
208
+ style,
209
+ fallback,
210
+ onImpression,
211
+ onClick
212
+ }) {
213
+ const { siteId, config, isLoading, identity, apiBase } = useAds();
214
+ const [ad, setAd] = react.useState(null);
215
+ const [error, setError] = react.useState(false);
216
+ const impressionTracked = react.useRef(false);
217
+ const containerRef = react.useRef(null);
218
+ react.useEffect(() => {
219
+ if (isLoading || !config?.enabled || !siteId || !identity) return;
220
+ const path = typeof window !== "undefined" ? window.location.pathname : "/";
221
+ const referrer = typeof document !== "undefined" ? document.referrer : "";
222
+ if (config.excludePaths?.some((p) => new RegExp(p.replace("*", ".*")).test(path))) {
223
+ return;
224
+ }
225
+ const deviceType = getDeviceType(identity.signals);
226
+ const browser = getBrowserInfo(identity.signals);
227
+ const os = getOSInfo(identity.signals);
228
+ fetch(`${apiBase}/ads/get`, {
229
+ method: "POST",
230
+ headers: { "Content-Type": "application/json" },
231
+ body: JSON.stringify({
232
+ siteId,
233
+ slot,
234
+ path,
235
+ referrer,
236
+ user: {
237
+ uid: identity.uid,
238
+ fingerprint: identity.fingerprint,
239
+ isAuthenticated: identity.isAuthenticated
240
+ },
241
+ device: {
242
+ type: deviceType,
243
+ browser: browser.name,
244
+ browserVersion: browser.version,
245
+ os: os.name,
246
+ osVersion: os.version,
247
+ screenWidth: identity.signals.screenWidth,
248
+ screenHeight: identity.signals.screenHeight,
249
+ language: identity.signals.language,
250
+ timezone: identity.signals.timezone
251
+ }
252
+ })
253
+ }).then((r) => r.json()).then((data) => {
254
+ if (data?.html) {
255
+ setAd(data);
256
+ } else {
257
+ setError(true);
258
+ }
259
+ }).catch(() => setError(true));
260
+ }, [siteId, config, isLoading, slot, identity, apiBase]);
261
+ react.useEffect(() => {
262
+ if (!ad || impressionTracked.current || typeof window === "undefined" || !identity) return;
263
+ const observer = new IntersectionObserver(
264
+ ([entry]) => {
265
+ if (entry.isIntersecting && !impressionTracked.current) {
266
+ impressionTracked.current = true;
267
+ const impressionData = {
268
+ adId: ad.id,
269
+ siteId,
270
+ slot,
271
+ uid: identity.uid,
272
+ fingerprint: identity.fingerprint,
273
+ timestamp: Date.now()
274
+ };
275
+ if (navigator.sendBeacon) {
276
+ navigator.sendBeacon(
277
+ `${apiBase}/ads/impression`,
278
+ JSON.stringify(impressionData)
279
+ );
280
+ }
281
+ onImpression?.(ad.id);
282
+ }
283
+ },
284
+ { threshold: 0.5 }
285
+ );
286
+ if (containerRef.current) {
287
+ observer.observe(containerRef.current);
288
+ }
289
+ return () => observer.disconnect();
290
+ }, [ad, siteId, slot, onImpression, identity, apiBase]);
291
+ if (isLoading) {
292
+ return /* @__PURE__ */ jsxRuntime.jsx(
293
+ "div",
294
+ {
295
+ className: `cimplify-ad cimplify-ad--loading ${className || ""}`,
296
+ style,
297
+ "aria-hidden": "true"
298
+ }
299
+ );
300
+ }
301
+ if (!config?.enabled) return null;
302
+ if (error) return fallback ? /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: fallback }) : null;
303
+ if (!ad) return null;
304
+ const handleClick = () => {
305
+ onClick?.(ad.id);
306
+ };
307
+ const positionStyles = position === "fixed" ? { position: "fixed", bottom: 0, left: 0, right: 0, zIndex: 9999 } : position === "sticky" ? { position: "sticky", top: 20 } : {};
308
+ return /* @__PURE__ */ jsxRuntime.jsx(
309
+ "div",
310
+ {
311
+ ref: containerRef,
312
+ id: `cimplify-ad-${ad.id}`,
313
+ className: `cimplify-ad cimplify-ad--${slot} cimplify-ad--${position} cimplify-ad--${config.theme} ${className || ""}`,
314
+ style: { ...positionStyles, ...style },
315
+ onClick: handleClick,
316
+ dangerouslySetInnerHTML: { __html: ad.html }
317
+ }
318
+ );
319
+ }
20
320
  var ElementsContext = react.createContext({
21
321
  elements: null,
22
322
  isReady: false
@@ -135,10 +435,13 @@ function useCheckout() {
135
435
  return { submit, isLoading };
136
436
  }
137
437
 
438
+ exports.Ad = Ad;
439
+ exports.AdProvider = AdProvider;
138
440
  exports.AddressElement = AddressElement;
139
441
  exports.AuthElement = AuthElement;
140
442
  exports.ElementsProvider = ElementsProvider;
141
443
  exports.PaymentElement = PaymentElement;
444
+ exports.useAds = useAds;
142
445
  exports.useCheckout = useCheckout;
143
446
  exports.useElements = useElements;
144
447
  exports.useElementsReady = useElementsReady;
package/dist/react.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  import { createContext, useContext, useState, useEffect, useRef, useCallback } from 'react';
2
- import { jsx } from 'react/jsx-runtime';
2
+ import { jsx, Fragment } from 'react/jsx-runtime';
3
3
 
4
4
  // src/react/index.tsx
5
5
 
@@ -15,6 +15,306 @@ var EVENT_TYPES = {
15
15
  REQUIRES_OTP: "requires_otp",
16
16
  ERROR: "error",
17
17
  CHANGE: "change"};
18
+
19
+ // src/ads/identity.ts
20
+ var COOKIE_NAME = "_cimplify_uid";
21
+ var COOKIE_MAX_AGE = 60 * 60 * 24 * 365;
22
+ function getOrCreateUserId() {
23
+ if (typeof document === "undefined") return generateId();
24
+ const existing = getCookie(COOKIE_NAME);
25
+ if (existing) return existing;
26
+ const newId = generateId();
27
+ setCookie(COOKIE_NAME, newId, COOKIE_MAX_AGE);
28
+ return newId;
29
+ }
30
+ function collectDeviceSignals() {
31
+ if (typeof window === "undefined") {
32
+ return getEmptySignals();
33
+ }
34
+ return {
35
+ userAgent: navigator.userAgent,
36
+ language: navigator.language,
37
+ languages: Array.from(navigator.languages || []),
38
+ platform: navigator.platform,
39
+ screenWidth: screen.width,
40
+ screenHeight: screen.height,
41
+ colorDepth: screen.colorDepth,
42
+ pixelRatio: window.devicePixelRatio || 1,
43
+ timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
44
+ timezoneOffset: (/* @__PURE__ */ new Date()).getTimezoneOffset(),
45
+ touchSupport: "ontouchstart" in window || navigator.maxTouchPoints > 0,
46
+ cookiesEnabled: navigator.cookieEnabled,
47
+ doNotTrack: navigator.doNotTrack === "1"
48
+ };
49
+ }
50
+ function generateFingerprint(signals) {
51
+ const components = [
52
+ signals.userAgent,
53
+ signals.language,
54
+ signals.platform,
55
+ `${signals.screenWidth}x${signals.screenHeight}`,
56
+ signals.colorDepth.toString(),
57
+ signals.pixelRatio.toString(),
58
+ signals.timezone,
59
+ signals.touchSupport.toString()
60
+ ];
61
+ return hashString(components.join("|"));
62
+ }
63
+ function getUserIdentity(authenticatedAccountId) {
64
+ const signals = collectDeviceSignals();
65
+ const fingerprint = generateFingerprint(signals);
66
+ const cookieId = getOrCreateUserId();
67
+ return {
68
+ uid: authenticatedAccountId || cookieId,
69
+ isAuthenticated: !!authenticatedAccountId,
70
+ fingerprint,
71
+ signals
72
+ };
73
+ }
74
+ function getDeviceType(signals) {
75
+ const ua = signals.userAgent.toLowerCase();
76
+ const width = signals.screenWidth;
77
+ if (/mobile|android|iphone|ipod/.test(ua) || width < 768) {
78
+ return "mobile";
79
+ }
80
+ if (/tablet|ipad/.test(ua) || width >= 768 && width < 1024) {
81
+ return "tablet";
82
+ }
83
+ return "desktop";
84
+ }
85
+ function getBrowserInfo(signals) {
86
+ const ua = signals.userAgent;
87
+ const browsers = [
88
+ { name: "Chrome", regex: /Chrome\/(\d+)/ },
89
+ { name: "Firefox", regex: /Firefox\/(\d+)/ },
90
+ { name: "Safari", regex: /Version\/(\d+).*Safari/ },
91
+ { name: "Edge", regex: /Edg\/(\d+)/ },
92
+ { name: "Opera", regex: /OPR\/(\d+)/ }
93
+ ];
94
+ for (const browser of browsers) {
95
+ const match = ua.match(browser.regex);
96
+ if (match) {
97
+ return { name: browser.name, version: match[1] };
98
+ }
99
+ }
100
+ return { name: "Unknown", version: "0" };
101
+ }
102
+ function getOSInfo(signals) {
103
+ const ua = signals.userAgent;
104
+ if (/Windows NT 10/.test(ua)) return { name: "Windows", version: "10" };
105
+ if (/Windows NT 6.3/.test(ua)) return { name: "Windows", version: "8.1" };
106
+ if (/Mac OS X (\d+[._]\d+)/.test(ua)) {
107
+ const match = ua.match(/Mac OS X (\d+[._]\d+)/);
108
+ return { name: "macOS", version: match?.[1].replace("_", ".") || "" };
109
+ }
110
+ if (/Android (\d+)/.test(ua)) {
111
+ const match = ua.match(/Android (\d+)/);
112
+ return { name: "Android", version: match?.[1] || "" };
113
+ }
114
+ if (/iPhone OS (\d+)/.test(ua)) {
115
+ const match = ua.match(/iPhone OS (\d+)/);
116
+ return { name: "iOS", version: match?.[1] || "" };
117
+ }
118
+ if (/Linux/.test(ua)) return { name: "Linux", version: "" };
119
+ return { name: "Unknown", version: "" };
120
+ }
121
+ function generateId() {
122
+ const timestamp = Date.now().toString(36);
123
+ const random = Math.random().toString(36).substring(2, 10);
124
+ return `${timestamp}-${random}`;
125
+ }
126
+ function hashString(str) {
127
+ let hash = 0;
128
+ for (let i = 0; i < str.length; i++) {
129
+ const char = str.charCodeAt(i);
130
+ hash = (hash << 5) - hash + char;
131
+ hash = hash & hash;
132
+ }
133
+ return Math.abs(hash).toString(36);
134
+ }
135
+ function getCookie(name) {
136
+ if (typeof document === "undefined") return null;
137
+ const match = document.cookie.match(new RegExp(`(^| )${name}=([^;]+)`));
138
+ return match ? match[2] : null;
139
+ }
140
+ function setCookie(name, value, maxAge) {
141
+ if (typeof document === "undefined") return;
142
+ document.cookie = `${name}=${value};max-age=${maxAge};path=/;SameSite=Lax`;
143
+ }
144
+ function getEmptySignals() {
145
+ return {
146
+ userAgent: "",
147
+ language: "",
148
+ languages: [],
149
+ platform: "",
150
+ screenWidth: 0,
151
+ screenHeight: 0,
152
+ colorDepth: 0,
153
+ pixelRatio: 1,
154
+ timezone: "",
155
+ timezoneOffset: 0,
156
+ touchSupport: false,
157
+ cookiesEnabled: false,
158
+ doNotTrack: false
159
+ };
160
+ }
161
+ function deriveAdsApiUrl() {
162
+ if (typeof window === "undefined") return "https://api.cimplify.io";
163
+ const hostname = window.location.hostname;
164
+ if (hostname === "localhost" || hostname === "127.0.0.1") {
165
+ return `${window.location.protocol}//${hostname}:8080`;
166
+ }
167
+ return "https://api.cimplify.io";
168
+ }
169
+ var AdContext = createContext({
170
+ siteId: null,
171
+ config: null,
172
+ isLoading: true,
173
+ identity: null,
174
+ apiBase: "https://api.cimplify.io"
175
+ });
176
+ function useAds() {
177
+ return useContext(AdContext);
178
+ }
179
+ function AdProvider({
180
+ siteId,
181
+ apiBase,
182
+ authenticatedAccountId,
183
+ children
184
+ }) {
185
+ const resolvedApiBase = apiBase || deriveAdsApiUrl();
186
+ const [config, setConfig] = useState(null);
187
+ const [isLoading, setIsLoading] = useState(true);
188
+ const [identity, setIdentity] = useState(null);
189
+ useEffect(() => {
190
+ const userIdentity = getUserIdentity(authenticatedAccountId);
191
+ setIdentity(userIdentity);
192
+ fetch(`${resolvedApiBase}/ads/config/${siteId}`).then((r) => r.json()).then((data) => {
193
+ setConfig(data);
194
+ setIsLoading(false);
195
+ }).catch(() => {
196
+ setConfig({ enabled: false, theme: "auto" });
197
+ setIsLoading(false);
198
+ });
199
+ }, [siteId, resolvedApiBase, authenticatedAccountId]);
200
+ return /* @__PURE__ */ jsx(AdContext.Provider, { value: { siteId, config, isLoading, identity, apiBase: resolvedApiBase }, children });
201
+ }
202
+ function Ad({
203
+ slot,
204
+ position = "static",
205
+ className,
206
+ style,
207
+ fallback,
208
+ onImpression,
209
+ onClick
210
+ }) {
211
+ const { siteId, config, isLoading, identity, apiBase } = useAds();
212
+ const [ad, setAd] = useState(null);
213
+ const [error, setError] = useState(false);
214
+ const impressionTracked = useRef(false);
215
+ const containerRef = useRef(null);
216
+ useEffect(() => {
217
+ if (isLoading || !config?.enabled || !siteId || !identity) return;
218
+ const path = typeof window !== "undefined" ? window.location.pathname : "/";
219
+ const referrer = typeof document !== "undefined" ? document.referrer : "";
220
+ if (config.excludePaths?.some((p) => new RegExp(p.replace("*", ".*")).test(path))) {
221
+ return;
222
+ }
223
+ const deviceType = getDeviceType(identity.signals);
224
+ const browser = getBrowserInfo(identity.signals);
225
+ const os = getOSInfo(identity.signals);
226
+ fetch(`${apiBase}/ads/get`, {
227
+ method: "POST",
228
+ headers: { "Content-Type": "application/json" },
229
+ body: JSON.stringify({
230
+ siteId,
231
+ slot,
232
+ path,
233
+ referrer,
234
+ user: {
235
+ uid: identity.uid,
236
+ fingerprint: identity.fingerprint,
237
+ isAuthenticated: identity.isAuthenticated
238
+ },
239
+ device: {
240
+ type: deviceType,
241
+ browser: browser.name,
242
+ browserVersion: browser.version,
243
+ os: os.name,
244
+ osVersion: os.version,
245
+ screenWidth: identity.signals.screenWidth,
246
+ screenHeight: identity.signals.screenHeight,
247
+ language: identity.signals.language,
248
+ timezone: identity.signals.timezone
249
+ }
250
+ })
251
+ }).then((r) => r.json()).then((data) => {
252
+ if (data?.html) {
253
+ setAd(data);
254
+ } else {
255
+ setError(true);
256
+ }
257
+ }).catch(() => setError(true));
258
+ }, [siteId, config, isLoading, slot, identity, apiBase]);
259
+ useEffect(() => {
260
+ if (!ad || impressionTracked.current || typeof window === "undefined" || !identity) return;
261
+ const observer = new IntersectionObserver(
262
+ ([entry]) => {
263
+ if (entry.isIntersecting && !impressionTracked.current) {
264
+ impressionTracked.current = true;
265
+ const impressionData = {
266
+ adId: ad.id,
267
+ siteId,
268
+ slot,
269
+ uid: identity.uid,
270
+ fingerprint: identity.fingerprint,
271
+ timestamp: Date.now()
272
+ };
273
+ if (navigator.sendBeacon) {
274
+ navigator.sendBeacon(
275
+ `${apiBase}/ads/impression`,
276
+ JSON.stringify(impressionData)
277
+ );
278
+ }
279
+ onImpression?.(ad.id);
280
+ }
281
+ },
282
+ { threshold: 0.5 }
283
+ );
284
+ if (containerRef.current) {
285
+ observer.observe(containerRef.current);
286
+ }
287
+ return () => observer.disconnect();
288
+ }, [ad, siteId, slot, onImpression, identity, apiBase]);
289
+ if (isLoading) {
290
+ return /* @__PURE__ */ jsx(
291
+ "div",
292
+ {
293
+ className: `cimplify-ad cimplify-ad--loading ${className || ""}`,
294
+ style,
295
+ "aria-hidden": "true"
296
+ }
297
+ );
298
+ }
299
+ if (!config?.enabled) return null;
300
+ if (error) return fallback ? /* @__PURE__ */ jsx(Fragment, { children: fallback }) : null;
301
+ if (!ad) return null;
302
+ const handleClick = () => {
303
+ onClick?.(ad.id);
304
+ };
305
+ const positionStyles = position === "fixed" ? { position: "fixed", bottom: 0, left: 0, right: 0, zIndex: 9999 } : position === "sticky" ? { position: "sticky", top: 20 } : {};
306
+ return /* @__PURE__ */ jsx(
307
+ "div",
308
+ {
309
+ ref: containerRef,
310
+ id: `cimplify-ad-${ad.id}`,
311
+ className: `cimplify-ad cimplify-ad--${slot} cimplify-ad--${position} cimplify-ad--${config.theme} ${className || ""}`,
312
+ style: { ...positionStyles, ...style },
313
+ onClick: handleClick,
314
+ dangerouslySetInnerHTML: { __html: ad.html }
315
+ }
316
+ );
317
+ }
18
318
  var ElementsContext = createContext({
19
319
  elements: null,
20
320
  isReady: false
@@ -133,4 +433,4 @@ function useCheckout() {
133
433
  return { submit, isLoading };
134
434
  }
135
435
 
136
- export { AddressElement, AuthElement, ElementsProvider, PaymentElement, useCheckout, useElements, useElementsReady };
436
+ export { Ad, AdProvider, AddressElement, AuthElement, ElementsProvider, PaymentElement, useAds, useCheckout, useElements, useElementsReady };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cimplify/sdk",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "Cimplify Commerce SDK for storefronts",
5
5
  "keywords": [
6
6
  "cimplify",
@@ -34,7 +34,10 @@
34
34
  "clean": "rm -rf dist",
35
35
  "lint:ox": "oxlint --fix .",
36
36
  "format": "oxfmt . --write",
37
- "format:check": "oxfmt . --check"
37
+ "format:check": "oxfmt . --check",
38
+ "test": "vitest",
39
+ "test:run": "vitest run",
40
+ "test:coverage": "vitest run --coverage"
38
41
  },
39
42
  "peerDependencies": {
40
43
  "react": ">=17.0.0"
@@ -45,10 +48,14 @@
45
48
  }
46
49
  },
47
50
  "devDependencies": {
51
+ "@testing-library/dom": "^10.0.0",
52
+ "@testing-library/react": "^16.0.0",
48
53
  "@types/react": "^19.2.7",
49
54
  "@typescript/native-preview": "^7.0.0-dev.20260109.1",
55
+ "jsdom": "^25.0.0",
50
56
  "react": "^19.2.3",
51
57
  "tsup": "^8.5.1",
52
- "typescript": "5.9.3"
58
+ "typescript": "5.9.3",
59
+ "vitest": "^2.1.0"
53
60
  }
54
61
  }