@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/{client-Ug3b8v1t.d.mts → ads-BOUIrN73.d.mts} +29 -5
- package/dist/{client-Ug3b8v1t.d.ts → ads-BOUIrN73.d.ts} +29 -5
- package/dist/index.d.mts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +100 -54
- package/dist/index.mjs +100 -54
- package/dist/react.d.mts +48 -2
- package/dist/react.d.ts +48 -2
- package/dist/react.js +303 -0
- package/dist/react.mjs +302 -2
- package/package.json +10 -3
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.
|
|
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
|
}
|