@zerocost/sdk 0.14.0 → 0.16.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/core/config.d.ts +1 -0
- package/dist/core/consent-ui.d.ts +0 -9
- package/dist/core/consent.d.ts +2 -0
- package/dist/core/constants.d.ts +4 -0
- package/dist/index.cjs +97 -24
- package/dist/index.d.ts +2 -0
- package/dist/index.js +97 -24
- package/dist/types/index.d.ts +2 -0
- package/package.json +1 -1
package/dist/core/config.d.ts
CHANGED
|
@@ -1,12 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* consent-ui.ts — Pure DOM consent popup for the Zerocost SDK.
|
|
3
|
-
*
|
|
4
|
-
* - Desktop: centered card modal (max 480px)
|
|
5
|
-
* - Mobile (≤640px): bottom-sheet modal
|
|
6
|
-
* - Themes: light / dark / auto
|
|
7
|
-
* - Non-dismissable (no Escape, no backdrop click)
|
|
8
|
-
* - Returns a Promise that resolves with the user's toggle selections
|
|
9
|
-
*/
|
|
10
1
|
export interface ConsentUIOptions {
|
|
11
2
|
appName: string;
|
|
12
3
|
theme: 'light' | 'dark' | 'auto';
|
package/dist/core/consent.d.ts
CHANGED
package/dist/index.cjs
CHANGED
|
@@ -28,6 +28,14 @@ __export(index_exports, {
|
|
|
28
28
|
});
|
|
29
29
|
module.exports = __toCommonJS(index_exports);
|
|
30
30
|
|
|
31
|
+
// src/core/constants.ts
|
|
32
|
+
var ZEROCOST_DOMAINS = {
|
|
33
|
+
MAIN: "https://zerocost.lovable.app"
|
|
34
|
+
//APP: 'https://app.zerocost.com',
|
|
35
|
+
//DOCS: 'https://docs.zerocost.com',
|
|
36
|
+
};
|
|
37
|
+
var ZEROCOST_BASE_URL = ZEROCOST_DOMAINS.MAIN;
|
|
38
|
+
|
|
31
39
|
// src/core/config.ts
|
|
32
40
|
var EDGE_FUNCTION_BASE = "https://mwbgzpbuoojqsuxduieo.supabase.co/functions/v1";
|
|
33
41
|
function getBaseUrl(custom) {
|
|
@@ -70,7 +78,7 @@ var ZerocostClient = class {
|
|
|
70
78
|
...body || {},
|
|
71
79
|
app_id: this.config.appId
|
|
72
80
|
};
|
|
73
|
-
this.log(`\u2192 ${
|
|
81
|
+
this.log(`\u2192 ${path}`, payload);
|
|
74
82
|
const res = await fetch(url, {
|
|
75
83
|
method: "POST",
|
|
76
84
|
headers: {
|
|
@@ -89,7 +97,8 @@ var ZerocostClient = class {
|
|
|
89
97
|
}
|
|
90
98
|
log(message, data) {
|
|
91
99
|
if (this.config.debug) {
|
|
92
|
-
|
|
100
|
+
const sanitizedMessage = typeof message === "string" ? message.replace(/https:\/\/[a-z0-9.-]+\.supabase\.co/gi, "[INFRA]") : message;
|
|
101
|
+
console.log(`[Zerocost] ${sanitizedMessage}`, data ?? "");
|
|
93
102
|
}
|
|
94
103
|
}
|
|
95
104
|
};
|
|
@@ -141,6 +150,12 @@ var TrackModule = class {
|
|
|
141
150
|
|
|
142
151
|
// src/core/widget-render.ts
|
|
143
152
|
var SDK_WIDGET_REFRESH_MS = 2e4;
|
|
153
|
+
var CLOSE_ICON_SVG = `
|
|
154
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" style="width:100%;height:100%;">
|
|
155
|
+
<line x1="18" y1="6" x2="6" y2="18"></line>
|
|
156
|
+
<line x1="6" y1="6" x2="18" y2="18"></line>
|
|
157
|
+
</svg>
|
|
158
|
+
`;
|
|
144
159
|
function escapeHtml(value) {
|
|
145
160
|
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
146
161
|
}
|
|
@@ -194,7 +209,7 @@ function renderVideoWidget(ad, theme) {
|
|
|
194
209
|
<div style="position:relative;width:200px;aspect-ratio:9/16;border-radius:16px;overflow:hidden;background:${palette.bg};border:1px solid rgba(255,255,255,0.12);box-shadow:0 20px 60px rgba(0,0,0,0.45);font-family:Space Grotesk,system-ui,sans-serif;">
|
|
195
210
|
${media}
|
|
196
211
|
<div style="position:absolute;top:10px;right:10px;display:flex;gap:8px;z-index:3;">
|
|
197
|
-
<button type="button" data-zc-close style="width:
|
|
212
|
+
<button type="button" data-zc-close style="width:24px;height:24px;padding:5px;border:none;border-radius:999px;background:rgba(0,0,0,0.52);color:#fff;cursor:pointer;display:flex;align-items:center;justify-content:center;">${CLOSE_ICON_SVG}</button>
|
|
198
213
|
</div>
|
|
199
214
|
<div style="position:absolute;inset:0;background:linear-gradient(180deg,rgba(0,0,0,0.04) 0%,rgba(0,0,0,0.12) 35%,rgba(0,0,0,0.85) 100%);"></div>
|
|
200
215
|
<div style="position:absolute;left:0;right:0;bottom:0;padding:14px;z-index:2;">
|
|
@@ -215,7 +230,7 @@ function renderTooltipWidget(ad, theme) {
|
|
|
215
230
|
<span style="width:6px;height:6px;border-radius:999px;background:${palette.text};display:inline-block;"></span>
|
|
216
231
|
Sponsored
|
|
217
232
|
</div>
|
|
218
|
-
<button type="button" data-zc-close style="background:none;border:none;color:${palette.textFaint};
|
|
233
|
+
<button type="button" data-zc-close style="background:none;border:none;color:${palette.textFaint};width:16px;height:16px;cursor:pointer;padding:0;display:flex;align-items:center;justify-content:center;">${CLOSE_ICON_SVG}</button>
|
|
219
234
|
</div>
|
|
220
235
|
<div style="margin-top:10px;color:${palette.text};font-size:13px;line-height:1.55;">
|
|
221
236
|
${escapeHtml(ad.description || ad.title)} <a href="${escapeHtml(ad.landing_url)}" target="_blank" rel="noreferrer noopener" data-zc-cta style="color:${palette.text};font-weight:700;text-decoration:underline;text-underline-offset:2px;">${escapeHtml(ad.cta_text || "Learn More")}</a>
|
|
@@ -232,7 +247,7 @@ function renderSponsoredCard(ad, theme) {
|
|
|
232
247
|
<div style="padding:12px;">
|
|
233
248
|
<div style="display:flex;align-items:center;justify-content:space-between;gap:8px;">
|
|
234
249
|
<div style="display:inline-flex;align-items:center;padding:4px 6px;border-radius:999px;background:${palette.badgeBg};color:${palette.textFaint};font-size:9px;font-weight:700;letter-spacing:0.12em;text-transform:uppercase;">Sponsored</div>
|
|
235
|
-
<button type="button" data-zc-close style="background:none;border:none;color:${palette.textFaint};
|
|
250
|
+
<button type="button" data-zc-close style="background:none;border:none;color:${palette.textFaint};width:16px;height:16px;cursor:pointer;padding:0;display:flex;align-items:center;justify-content:center;">${CLOSE_ICON_SVG}</button>
|
|
236
251
|
</div>
|
|
237
252
|
<div style="margin-top:10px;color:${palette.text};font-size:13px;font-weight:700;line-height:1.2;">${escapeHtml(ad.title)}</div>
|
|
238
253
|
${ad.description ? `<div style="margin-top:6px;color:${palette.textMuted};font-size:11px;line-height:1.35;">${escapeHtml(ad.description)}</div>` : ""}
|
|
@@ -248,7 +263,7 @@ function renderInlineText(ad, theme) {
|
|
|
248
263
|
<div style="margin:10px 0;border-radius:14px;overflow:hidden;background:${palette.surface};border:1px solid ${palette.border};font-family:Space Grotesk,system-ui,sans-serif;">
|
|
249
264
|
<div style="display:flex;align-items:center;justify-content:space-between;padding:9px 12px;border-bottom:1px solid ${palette.border};background:${palette.surfaceStrong};">
|
|
250
265
|
<span style="color:${palette.textFaint};font-size:9px;font-weight:700;letter-spacing:0.12em;text-transform:uppercase;">Sponsored</span>
|
|
251
|
-
<button type="button" data-zc-close style="background:none;border:none;color:${palette.textFaint};
|
|
266
|
+
<button type="button" data-zc-close style="background:none;border:none;color:${palette.textFaint};width:16px;height:16px;cursor:pointer;padding:0;display:flex;align-items:center;justify-content:center;">${CLOSE_ICON_SVG}</button>
|
|
252
267
|
</div>
|
|
253
268
|
<div style="padding:12px;display:flex;gap:10px;align-items:flex-start;">
|
|
254
269
|
${media}
|
|
@@ -277,15 +292,15 @@ function renderWidgetMarkup(ad, options) {
|
|
|
277
292
|
|
|
278
293
|
// src/modules/widget.ts
|
|
279
294
|
var POSITION_STYLES = {
|
|
280
|
-
"bottom-right": "position:fixed;bottom:24px;right:
|
|
295
|
+
"bottom-right": "position:fixed;bottom:24px;right:40px;z-index:9999;",
|
|
281
296
|
"bottom-left": "position:fixed;bottom:24px;left:80px;z-index:9999;",
|
|
282
|
-
"top-right": "position:fixed;top:24px;right:
|
|
297
|
+
"top-right": "position:fixed;top:24px;right:40px;z-index:9999;",
|
|
283
298
|
"top-left": "position:fixed;top:24px;left:24px;z-index:9999;",
|
|
284
299
|
"bottom-center": "position:fixed;bottom:24px;left:50%;transform:translateX(-50%);z-index:9999;",
|
|
285
300
|
"top-center": "position:fixed;top:24px;left:50%;transform:translateX(-50%);z-index:9999;",
|
|
286
301
|
"center": "position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);z-index:9999;",
|
|
287
302
|
"sidebar-left": "position:fixed;top:50%;left:24px;transform:translateY(-50%);z-index:9999;",
|
|
288
|
-
"sidebar-right": "position:fixed;top:50%;right:
|
|
303
|
+
"sidebar-right": "position:fixed;top:50%;right:40px;transform:translateY(-50%);z-index:9999;"
|
|
289
304
|
};
|
|
290
305
|
var FORMAT_PRIORITY = ["video-widget", "tooltip-ad", "sponsored-card", "sidebar-display", "inline-text"];
|
|
291
306
|
var AUTO_SLOT_ID = "zerocost-auto-slot";
|
|
@@ -1589,6 +1604,44 @@ function injectStyles(theme) {
|
|
|
1589
1604
|
.zc-consent-confirm:active {
|
|
1590
1605
|
opacity: 0.75;
|
|
1591
1606
|
}
|
|
1607
|
+
|
|
1608
|
+
/* Floating settings button */
|
|
1609
|
+
.zc-settings-btn {
|
|
1610
|
+
position: fixed;
|
|
1611
|
+
width: 44px;
|
|
1612
|
+
height: 44px;
|
|
1613
|
+
border-radius: 50%;
|
|
1614
|
+
background: var(--zc-bg);
|
|
1615
|
+
border: 1px solid var(--zc-border);
|
|
1616
|
+
color: var(--zc-text);
|
|
1617
|
+
display: flex;
|
|
1618
|
+
align-items: center;
|
|
1619
|
+
justify-content: center;
|
|
1620
|
+
cursor: pointer;
|
|
1621
|
+
z-index: 999998;
|
|
1622
|
+
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
|
|
1623
|
+
transition: transform 200ms, border-color 200ms;
|
|
1624
|
+
}
|
|
1625
|
+
|
|
1626
|
+
.zc-settings-btn:hover {
|
|
1627
|
+
transform: scale(1.05);
|
|
1628
|
+
border-color: var(--zc-text-secondary);
|
|
1629
|
+
}
|
|
1630
|
+
|
|
1631
|
+
.zc-settings-btn svg {
|
|
1632
|
+
width: 20px;
|
|
1633
|
+
height: 20px;
|
|
1634
|
+
}
|
|
1635
|
+
|
|
1636
|
+
.zc-settings-bottom-left { bottom: 20px; left: 20px; }
|
|
1637
|
+
.zc-settings-bottom-right { bottom: 20px; right: 20px; }
|
|
1638
|
+
.zc-settings-top-left { top: 20px; left: 20px; }
|
|
1639
|
+
.zc-settings-top-right { top: 20px; right: 20px; }
|
|
1640
|
+
|
|
1641
|
+
@media (max-width: 640px) {
|
|
1642
|
+
.zc-settings-btn { width: 40px; height: 40px; }
|
|
1643
|
+
.zc-settings-bottom-left, .zc-settings-bottom-right { bottom: 16px; }
|
|
1644
|
+
}
|
|
1592
1645
|
`;
|
|
1593
1646
|
const style = document.createElement("style");
|
|
1594
1647
|
style.id = STYLE_ID;
|
|
@@ -1671,33 +1724,33 @@ function showConsentUI(options) {
|
|
|
1671
1724
|
card.appendChild(subtitle);
|
|
1672
1725
|
const toggles = document.createElement("div");
|
|
1673
1726
|
toggles.className = "zc-consent-toggles";
|
|
1674
|
-
const
|
|
1727
|
+
const zerocostBaseUrl = ZEROCOST_BASE_URL;
|
|
1675
1728
|
toggles.appendChild(createToggleCard(
|
|
1676
1729
|
"zc-toggle-ads",
|
|
1677
1730
|
"Ads",
|
|
1678
1731
|
"Contextual, non-intrusive ads. No cookies or browsing history used.",
|
|
1679
|
-
`${
|
|
1732
|
+
`${zerocostBaseUrl}/docs/ads`,
|
|
1680
1733
|
defaults.ads
|
|
1681
1734
|
));
|
|
1682
1735
|
toggles.appendChild(createToggleCard(
|
|
1683
1736
|
"zc-toggle-usage",
|
|
1684
1737
|
"Usage data",
|
|
1685
1738
|
"Anonymized usage patterns. No personal information is shared.",
|
|
1686
|
-
`${
|
|
1739
|
+
`${zerocostBaseUrl}/docs/usage-data`,
|
|
1687
1740
|
defaults.usageData
|
|
1688
1741
|
));
|
|
1689
1742
|
toggles.appendChild(createToggleCard(
|
|
1690
1743
|
"zc-toggle-ai",
|
|
1691
1744
|
"AI interactions",
|
|
1692
1745
|
"Anonymized conversation data used for AI research.",
|
|
1693
|
-
`${
|
|
1746
|
+
`${zerocostBaseUrl}/docs/ai-interactions`,
|
|
1694
1747
|
defaults.aiInteractions
|
|
1695
1748
|
));
|
|
1696
1749
|
card.appendChild(toggles);
|
|
1697
1750
|
const footer = document.createElement("div");
|
|
1698
1751
|
footer.className = "zc-consent-footer";
|
|
1699
1752
|
const ppLink = document.createElement("a");
|
|
1700
|
-
ppLink.href = privacyPolicyUrl || `${
|
|
1753
|
+
ppLink.href = privacyPolicyUrl || `${zerocostBaseUrl}/docs/privacy`;
|
|
1701
1754
|
ppLink.target = "_blank";
|
|
1702
1755
|
ppLink.rel = "noopener noreferrer";
|
|
1703
1756
|
ppLink.textContent = "Privacy Policy";
|
|
@@ -1707,7 +1760,7 @@ function showConsentUI(options) {
|
|
|
1707
1760
|
sep1.textContent = "\xB7";
|
|
1708
1761
|
footer.appendChild(sep1);
|
|
1709
1762
|
const termsLink = document.createElement("a");
|
|
1710
|
-
termsLink.href = `${
|
|
1763
|
+
termsLink.href = `${zerocostBaseUrl}/docs/terms`;
|
|
1711
1764
|
termsLink.target = "_blank";
|
|
1712
1765
|
termsLink.rel = "noopener noreferrer";
|
|
1713
1766
|
termsLink.textContent = "Terms";
|
|
@@ -1717,7 +1770,7 @@ function showConsentUI(options) {
|
|
|
1717
1770
|
sep2.textContent = "\xB7";
|
|
1718
1771
|
footer.appendChild(sep2);
|
|
1719
1772
|
const dnsLink = document.createElement("a");
|
|
1720
|
-
dnsLink.href = `${
|
|
1773
|
+
dnsLink.href = `${zerocostBaseUrl}/docs/do-not-sell`;
|
|
1721
1774
|
dnsLink.target = "_blank";
|
|
1722
1775
|
dnsLink.rel = "noopener noreferrer";
|
|
1723
1776
|
dnsLink.textContent = "Do Not Sell My Data";
|
|
@@ -1761,6 +1814,9 @@ var ConsentManager = class {
|
|
|
1761
1814
|
this.appName = opts.appName ?? "";
|
|
1762
1815
|
this.theme = opts.theme ?? "dark";
|
|
1763
1816
|
this.hydrateFromStorage();
|
|
1817
|
+
if (this.consentConfig.showSettingsButton) {
|
|
1818
|
+
this.injectSettingsButton();
|
|
1819
|
+
}
|
|
1764
1820
|
}
|
|
1765
1821
|
// ── Public API (per spec §6.3) ───────────────────────────────────
|
|
1766
1822
|
/** Returns the current consent record, or null if none exists. */
|
|
@@ -1826,6 +1882,9 @@ var ConsentManager = class {
|
|
|
1826
1882
|
};
|
|
1827
1883
|
this.record = record;
|
|
1828
1884
|
this.writeStorage(record);
|
|
1885
|
+
if (this.consentConfig.showSettingsButton) {
|
|
1886
|
+
this.injectSettingsButton();
|
|
1887
|
+
}
|
|
1829
1888
|
if (this.consentConfig.onConsentChange) {
|
|
1830
1889
|
try {
|
|
1831
1890
|
this.consentConfig.onConsentChange(record);
|
|
@@ -1904,6 +1963,23 @@ var ConsentManager = class {
|
|
|
1904
1963
|
this.client.log(`Failed to submit consent to server: ${err}`);
|
|
1905
1964
|
}
|
|
1906
1965
|
}
|
|
1966
|
+
injectSettingsButton() {
|
|
1967
|
+
if (typeof document === "undefined") return;
|
|
1968
|
+
const existing = document.getElementById("zerocost-privacy-settings-btn");
|
|
1969
|
+
if (existing) return;
|
|
1970
|
+
const btn = document.createElement("button");
|
|
1971
|
+
btn.id = "zerocost-privacy-settings-btn";
|
|
1972
|
+
btn.setAttribute("aria-label", "Privacy Settings");
|
|
1973
|
+
btn.title = "Privacy Settings";
|
|
1974
|
+
const pos = this.consentConfig.buttonPosition || "bottom-left";
|
|
1975
|
+
btn.className = `zc-settings-btn zc-settings-${pos}`;
|
|
1976
|
+
btn.innerHTML = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/></svg>`;
|
|
1977
|
+
btn.addEventListener("click", () => this.open());
|
|
1978
|
+
document.body.appendChild(btn);
|
|
1979
|
+
}
|
|
1980
|
+
removeSettingsButton() {
|
|
1981
|
+
document.getElementById("zerocost-privacy-settings-btn")?.remove();
|
|
1982
|
+
}
|
|
1907
1983
|
};
|
|
1908
1984
|
|
|
1909
1985
|
// src/index.ts
|
|
@@ -1937,21 +2013,15 @@ var ZerocostSDK = class {
|
|
|
1937
2013
|
});
|
|
1938
2014
|
}
|
|
1939
2015
|
async init() {
|
|
1940
|
-
this.core.init();
|
|
1941
2016
|
if (typeof document === "undefined") {
|
|
1942
|
-
this.core.log("Running in non-browser environment; skipping DOM injection.");
|
|
1943
2017
|
return;
|
|
1944
2018
|
}
|
|
1945
2019
|
if (window !== window.top) {
|
|
1946
|
-
this.core.log("Running inside an iframe. Ads render if permissions allow.");
|
|
1947
2020
|
}
|
|
1948
|
-
this.core.log("Initializing Zerocost SDK.");
|
|
1949
2021
|
if (this.consent.shouldPrompt()) {
|
|
1950
|
-
this.core.log("Consent required \u2014 showing prompt.");
|
|
1951
2022
|
await this.consent.promptAndWait();
|
|
1952
2023
|
}
|
|
1953
2024
|
if (!this.consent.has("ads")) {
|
|
1954
|
-
this.core.log("Ads consent not granted \u2014 skipping ad injection.");
|
|
1955
2025
|
return;
|
|
1956
2026
|
}
|
|
1957
2027
|
const cachedConfig = this.readCachedConfig();
|
|
@@ -1959,12 +2029,11 @@ var ZerocostSDK = class {
|
|
|
1959
2029
|
this.lastConfigHash = this.configToHash(cachedConfig);
|
|
1960
2030
|
this.syncDataCollection(cachedConfig.dataCollection);
|
|
1961
2031
|
await this.widget.autoInjectWithConfig(cachedConfig.display, cachedConfig.widget);
|
|
1962
|
-
this.core.log("Applied cached config immediately.");
|
|
1963
2032
|
}
|
|
1964
2033
|
this.startConfigSync();
|
|
1965
2034
|
try {
|
|
1966
2035
|
await this.refreshConfig({ force: true, reason: "init" });
|
|
1967
|
-
this.core.log(
|
|
2036
|
+
this.core.log(`Zerocost SDK initialized (${this.core.getConfig().appId})`);
|
|
1968
2037
|
} catch (err) {
|
|
1969
2038
|
this.core.log(`Init error: ${err}. Attempting fallback ad injection.`);
|
|
1970
2039
|
await this.widget.autoInject();
|
|
@@ -2138,6 +2207,10 @@ var ZerocostSDK = class {
|
|
|
2138
2207
|
return { valid: false, error: err.message };
|
|
2139
2208
|
}
|
|
2140
2209
|
}
|
|
2210
|
+
/** Open the consent/privacy settings popup. */
|
|
2211
|
+
async showSettings() {
|
|
2212
|
+
return this.consent.open();
|
|
2213
|
+
}
|
|
2141
2214
|
};
|
|
2142
2215
|
// Annotate the CommonJS export names for ESM import in node:
|
|
2143
2216
|
0 && (module.exports = {
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
// src/core/constants.ts
|
|
2
|
+
var ZEROCOST_DOMAINS = {
|
|
3
|
+
MAIN: "https://zerocost.lovable.app"
|
|
4
|
+
//APP: 'https://app.zerocost.com',
|
|
5
|
+
//DOCS: 'https://docs.zerocost.com',
|
|
6
|
+
};
|
|
7
|
+
var ZEROCOST_BASE_URL = ZEROCOST_DOMAINS.MAIN;
|
|
8
|
+
|
|
1
9
|
// src/core/config.ts
|
|
2
10
|
var EDGE_FUNCTION_BASE = "https://mwbgzpbuoojqsuxduieo.supabase.co/functions/v1";
|
|
3
11
|
function getBaseUrl(custom) {
|
|
@@ -40,7 +48,7 @@ var ZerocostClient = class {
|
|
|
40
48
|
...body || {},
|
|
41
49
|
app_id: this.config.appId
|
|
42
50
|
};
|
|
43
|
-
this.log(`\u2192 ${
|
|
51
|
+
this.log(`\u2192 ${path}`, payload);
|
|
44
52
|
const res = await fetch(url, {
|
|
45
53
|
method: "POST",
|
|
46
54
|
headers: {
|
|
@@ -59,7 +67,8 @@ var ZerocostClient = class {
|
|
|
59
67
|
}
|
|
60
68
|
log(message, data) {
|
|
61
69
|
if (this.config.debug) {
|
|
62
|
-
|
|
70
|
+
const sanitizedMessage = typeof message === "string" ? message.replace(/https:\/\/[a-z0-9.-]+\.supabase\.co/gi, "[INFRA]") : message;
|
|
71
|
+
console.log(`[Zerocost] ${sanitizedMessage}`, data ?? "");
|
|
63
72
|
}
|
|
64
73
|
}
|
|
65
74
|
};
|
|
@@ -111,6 +120,12 @@ var TrackModule = class {
|
|
|
111
120
|
|
|
112
121
|
// src/core/widget-render.ts
|
|
113
122
|
var SDK_WIDGET_REFRESH_MS = 2e4;
|
|
123
|
+
var CLOSE_ICON_SVG = `
|
|
124
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" style="width:100%;height:100%;">
|
|
125
|
+
<line x1="18" y1="6" x2="6" y2="18"></line>
|
|
126
|
+
<line x1="6" y1="6" x2="18" y2="18"></line>
|
|
127
|
+
</svg>
|
|
128
|
+
`;
|
|
114
129
|
function escapeHtml(value) {
|
|
115
130
|
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
116
131
|
}
|
|
@@ -164,7 +179,7 @@ function renderVideoWidget(ad, theme) {
|
|
|
164
179
|
<div style="position:relative;width:200px;aspect-ratio:9/16;border-radius:16px;overflow:hidden;background:${palette.bg};border:1px solid rgba(255,255,255,0.12);box-shadow:0 20px 60px rgba(0,0,0,0.45);font-family:Space Grotesk,system-ui,sans-serif;">
|
|
165
180
|
${media}
|
|
166
181
|
<div style="position:absolute;top:10px;right:10px;display:flex;gap:8px;z-index:3;">
|
|
167
|
-
<button type="button" data-zc-close style="width:
|
|
182
|
+
<button type="button" data-zc-close style="width:24px;height:24px;padding:5px;border:none;border-radius:999px;background:rgba(0,0,0,0.52);color:#fff;cursor:pointer;display:flex;align-items:center;justify-content:center;">${CLOSE_ICON_SVG}</button>
|
|
168
183
|
</div>
|
|
169
184
|
<div style="position:absolute;inset:0;background:linear-gradient(180deg,rgba(0,0,0,0.04) 0%,rgba(0,0,0,0.12) 35%,rgba(0,0,0,0.85) 100%);"></div>
|
|
170
185
|
<div style="position:absolute;left:0;right:0;bottom:0;padding:14px;z-index:2;">
|
|
@@ -185,7 +200,7 @@ function renderTooltipWidget(ad, theme) {
|
|
|
185
200
|
<span style="width:6px;height:6px;border-radius:999px;background:${palette.text};display:inline-block;"></span>
|
|
186
201
|
Sponsored
|
|
187
202
|
</div>
|
|
188
|
-
<button type="button" data-zc-close style="background:none;border:none;color:${palette.textFaint};
|
|
203
|
+
<button type="button" data-zc-close style="background:none;border:none;color:${palette.textFaint};width:16px;height:16px;cursor:pointer;padding:0;display:flex;align-items:center;justify-content:center;">${CLOSE_ICON_SVG}</button>
|
|
189
204
|
</div>
|
|
190
205
|
<div style="margin-top:10px;color:${palette.text};font-size:13px;line-height:1.55;">
|
|
191
206
|
${escapeHtml(ad.description || ad.title)} <a href="${escapeHtml(ad.landing_url)}" target="_blank" rel="noreferrer noopener" data-zc-cta style="color:${palette.text};font-weight:700;text-decoration:underline;text-underline-offset:2px;">${escapeHtml(ad.cta_text || "Learn More")}</a>
|
|
@@ -202,7 +217,7 @@ function renderSponsoredCard(ad, theme) {
|
|
|
202
217
|
<div style="padding:12px;">
|
|
203
218
|
<div style="display:flex;align-items:center;justify-content:space-between;gap:8px;">
|
|
204
219
|
<div style="display:inline-flex;align-items:center;padding:4px 6px;border-radius:999px;background:${palette.badgeBg};color:${palette.textFaint};font-size:9px;font-weight:700;letter-spacing:0.12em;text-transform:uppercase;">Sponsored</div>
|
|
205
|
-
<button type="button" data-zc-close style="background:none;border:none;color:${palette.textFaint};
|
|
220
|
+
<button type="button" data-zc-close style="background:none;border:none;color:${palette.textFaint};width:16px;height:16px;cursor:pointer;padding:0;display:flex;align-items:center;justify-content:center;">${CLOSE_ICON_SVG}</button>
|
|
206
221
|
</div>
|
|
207
222
|
<div style="margin-top:10px;color:${palette.text};font-size:13px;font-weight:700;line-height:1.2;">${escapeHtml(ad.title)}</div>
|
|
208
223
|
${ad.description ? `<div style="margin-top:6px;color:${palette.textMuted};font-size:11px;line-height:1.35;">${escapeHtml(ad.description)}</div>` : ""}
|
|
@@ -218,7 +233,7 @@ function renderInlineText(ad, theme) {
|
|
|
218
233
|
<div style="margin:10px 0;border-radius:14px;overflow:hidden;background:${palette.surface};border:1px solid ${palette.border};font-family:Space Grotesk,system-ui,sans-serif;">
|
|
219
234
|
<div style="display:flex;align-items:center;justify-content:space-between;padding:9px 12px;border-bottom:1px solid ${palette.border};background:${palette.surfaceStrong};">
|
|
220
235
|
<span style="color:${palette.textFaint};font-size:9px;font-weight:700;letter-spacing:0.12em;text-transform:uppercase;">Sponsored</span>
|
|
221
|
-
<button type="button" data-zc-close style="background:none;border:none;color:${palette.textFaint};
|
|
236
|
+
<button type="button" data-zc-close style="background:none;border:none;color:${palette.textFaint};width:16px;height:16px;cursor:pointer;padding:0;display:flex;align-items:center;justify-content:center;">${CLOSE_ICON_SVG}</button>
|
|
222
237
|
</div>
|
|
223
238
|
<div style="padding:12px;display:flex;gap:10px;align-items:flex-start;">
|
|
224
239
|
${media}
|
|
@@ -247,15 +262,15 @@ function renderWidgetMarkup(ad, options) {
|
|
|
247
262
|
|
|
248
263
|
// src/modules/widget.ts
|
|
249
264
|
var POSITION_STYLES = {
|
|
250
|
-
"bottom-right": "position:fixed;bottom:24px;right:
|
|
265
|
+
"bottom-right": "position:fixed;bottom:24px;right:40px;z-index:9999;",
|
|
251
266
|
"bottom-left": "position:fixed;bottom:24px;left:80px;z-index:9999;",
|
|
252
|
-
"top-right": "position:fixed;top:24px;right:
|
|
267
|
+
"top-right": "position:fixed;top:24px;right:40px;z-index:9999;",
|
|
253
268
|
"top-left": "position:fixed;top:24px;left:24px;z-index:9999;",
|
|
254
269
|
"bottom-center": "position:fixed;bottom:24px;left:50%;transform:translateX(-50%);z-index:9999;",
|
|
255
270
|
"top-center": "position:fixed;top:24px;left:50%;transform:translateX(-50%);z-index:9999;",
|
|
256
271
|
"center": "position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);z-index:9999;",
|
|
257
272
|
"sidebar-left": "position:fixed;top:50%;left:24px;transform:translateY(-50%);z-index:9999;",
|
|
258
|
-
"sidebar-right": "position:fixed;top:50%;right:
|
|
273
|
+
"sidebar-right": "position:fixed;top:50%;right:40px;transform:translateY(-50%);z-index:9999;"
|
|
259
274
|
};
|
|
260
275
|
var FORMAT_PRIORITY = ["video-widget", "tooltip-ad", "sponsored-card", "sidebar-display", "inline-text"];
|
|
261
276
|
var AUTO_SLOT_ID = "zerocost-auto-slot";
|
|
@@ -1559,6 +1574,44 @@ function injectStyles(theme) {
|
|
|
1559
1574
|
.zc-consent-confirm:active {
|
|
1560
1575
|
opacity: 0.75;
|
|
1561
1576
|
}
|
|
1577
|
+
|
|
1578
|
+
/* Floating settings button */
|
|
1579
|
+
.zc-settings-btn {
|
|
1580
|
+
position: fixed;
|
|
1581
|
+
width: 44px;
|
|
1582
|
+
height: 44px;
|
|
1583
|
+
border-radius: 50%;
|
|
1584
|
+
background: var(--zc-bg);
|
|
1585
|
+
border: 1px solid var(--zc-border);
|
|
1586
|
+
color: var(--zc-text);
|
|
1587
|
+
display: flex;
|
|
1588
|
+
align-items: center;
|
|
1589
|
+
justify-content: center;
|
|
1590
|
+
cursor: pointer;
|
|
1591
|
+
z-index: 999998;
|
|
1592
|
+
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
|
|
1593
|
+
transition: transform 200ms, border-color 200ms;
|
|
1594
|
+
}
|
|
1595
|
+
|
|
1596
|
+
.zc-settings-btn:hover {
|
|
1597
|
+
transform: scale(1.05);
|
|
1598
|
+
border-color: var(--zc-text-secondary);
|
|
1599
|
+
}
|
|
1600
|
+
|
|
1601
|
+
.zc-settings-btn svg {
|
|
1602
|
+
width: 20px;
|
|
1603
|
+
height: 20px;
|
|
1604
|
+
}
|
|
1605
|
+
|
|
1606
|
+
.zc-settings-bottom-left { bottom: 20px; left: 20px; }
|
|
1607
|
+
.zc-settings-bottom-right { bottom: 20px; right: 20px; }
|
|
1608
|
+
.zc-settings-top-left { top: 20px; left: 20px; }
|
|
1609
|
+
.zc-settings-top-right { top: 20px; right: 20px; }
|
|
1610
|
+
|
|
1611
|
+
@media (max-width: 640px) {
|
|
1612
|
+
.zc-settings-btn { width: 40px; height: 40px; }
|
|
1613
|
+
.zc-settings-bottom-left, .zc-settings-bottom-right { bottom: 16px; }
|
|
1614
|
+
}
|
|
1562
1615
|
`;
|
|
1563
1616
|
const style = document.createElement("style");
|
|
1564
1617
|
style.id = STYLE_ID;
|
|
@@ -1641,33 +1694,33 @@ function showConsentUI(options) {
|
|
|
1641
1694
|
card.appendChild(subtitle);
|
|
1642
1695
|
const toggles = document.createElement("div");
|
|
1643
1696
|
toggles.className = "zc-consent-toggles";
|
|
1644
|
-
const
|
|
1697
|
+
const zerocostBaseUrl = ZEROCOST_BASE_URL;
|
|
1645
1698
|
toggles.appendChild(createToggleCard(
|
|
1646
1699
|
"zc-toggle-ads",
|
|
1647
1700
|
"Ads",
|
|
1648
1701
|
"Contextual, non-intrusive ads. No cookies or browsing history used.",
|
|
1649
|
-
`${
|
|
1702
|
+
`${zerocostBaseUrl}/docs/ads`,
|
|
1650
1703
|
defaults.ads
|
|
1651
1704
|
));
|
|
1652
1705
|
toggles.appendChild(createToggleCard(
|
|
1653
1706
|
"zc-toggle-usage",
|
|
1654
1707
|
"Usage data",
|
|
1655
1708
|
"Anonymized usage patterns. No personal information is shared.",
|
|
1656
|
-
`${
|
|
1709
|
+
`${zerocostBaseUrl}/docs/usage-data`,
|
|
1657
1710
|
defaults.usageData
|
|
1658
1711
|
));
|
|
1659
1712
|
toggles.appendChild(createToggleCard(
|
|
1660
1713
|
"zc-toggle-ai",
|
|
1661
1714
|
"AI interactions",
|
|
1662
1715
|
"Anonymized conversation data used for AI research.",
|
|
1663
|
-
`${
|
|
1716
|
+
`${zerocostBaseUrl}/docs/ai-interactions`,
|
|
1664
1717
|
defaults.aiInteractions
|
|
1665
1718
|
));
|
|
1666
1719
|
card.appendChild(toggles);
|
|
1667
1720
|
const footer = document.createElement("div");
|
|
1668
1721
|
footer.className = "zc-consent-footer";
|
|
1669
1722
|
const ppLink = document.createElement("a");
|
|
1670
|
-
ppLink.href = privacyPolicyUrl || `${
|
|
1723
|
+
ppLink.href = privacyPolicyUrl || `${zerocostBaseUrl}/docs/privacy`;
|
|
1671
1724
|
ppLink.target = "_blank";
|
|
1672
1725
|
ppLink.rel = "noopener noreferrer";
|
|
1673
1726
|
ppLink.textContent = "Privacy Policy";
|
|
@@ -1677,7 +1730,7 @@ function showConsentUI(options) {
|
|
|
1677
1730
|
sep1.textContent = "\xB7";
|
|
1678
1731
|
footer.appendChild(sep1);
|
|
1679
1732
|
const termsLink = document.createElement("a");
|
|
1680
|
-
termsLink.href = `${
|
|
1733
|
+
termsLink.href = `${zerocostBaseUrl}/docs/terms`;
|
|
1681
1734
|
termsLink.target = "_blank";
|
|
1682
1735
|
termsLink.rel = "noopener noreferrer";
|
|
1683
1736
|
termsLink.textContent = "Terms";
|
|
@@ -1687,7 +1740,7 @@ function showConsentUI(options) {
|
|
|
1687
1740
|
sep2.textContent = "\xB7";
|
|
1688
1741
|
footer.appendChild(sep2);
|
|
1689
1742
|
const dnsLink = document.createElement("a");
|
|
1690
|
-
dnsLink.href = `${
|
|
1743
|
+
dnsLink.href = `${zerocostBaseUrl}/docs/do-not-sell`;
|
|
1691
1744
|
dnsLink.target = "_blank";
|
|
1692
1745
|
dnsLink.rel = "noopener noreferrer";
|
|
1693
1746
|
dnsLink.textContent = "Do Not Sell My Data";
|
|
@@ -1731,6 +1784,9 @@ var ConsentManager = class {
|
|
|
1731
1784
|
this.appName = opts.appName ?? "";
|
|
1732
1785
|
this.theme = opts.theme ?? "dark";
|
|
1733
1786
|
this.hydrateFromStorage();
|
|
1787
|
+
if (this.consentConfig.showSettingsButton) {
|
|
1788
|
+
this.injectSettingsButton();
|
|
1789
|
+
}
|
|
1734
1790
|
}
|
|
1735
1791
|
// ── Public API (per spec §6.3) ───────────────────────────────────
|
|
1736
1792
|
/** Returns the current consent record, or null if none exists. */
|
|
@@ -1796,6 +1852,9 @@ var ConsentManager = class {
|
|
|
1796
1852
|
};
|
|
1797
1853
|
this.record = record;
|
|
1798
1854
|
this.writeStorage(record);
|
|
1855
|
+
if (this.consentConfig.showSettingsButton) {
|
|
1856
|
+
this.injectSettingsButton();
|
|
1857
|
+
}
|
|
1799
1858
|
if (this.consentConfig.onConsentChange) {
|
|
1800
1859
|
try {
|
|
1801
1860
|
this.consentConfig.onConsentChange(record);
|
|
@@ -1874,6 +1933,23 @@ var ConsentManager = class {
|
|
|
1874
1933
|
this.client.log(`Failed to submit consent to server: ${err}`);
|
|
1875
1934
|
}
|
|
1876
1935
|
}
|
|
1936
|
+
injectSettingsButton() {
|
|
1937
|
+
if (typeof document === "undefined") return;
|
|
1938
|
+
const existing = document.getElementById("zerocost-privacy-settings-btn");
|
|
1939
|
+
if (existing) return;
|
|
1940
|
+
const btn = document.createElement("button");
|
|
1941
|
+
btn.id = "zerocost-privacy-settings-btn";
|
|
1942
|
+
btn.setAttribute("aria-label", "Privacy Settings");
|
|
1943
|
+
btn.title = "Privacy Settings";
|
|
1944
|
+
const pos = this.consentConfig.buttonPosition || "bottom-left";
|
|
1945
|
+
btn.className = `zc-settings-btn zc-settings-${pos}`;
|
|
1946
|
+
btn.innerHTML = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/></svg>`;
|
|
1947
|
+
btn.addEventListener("click", () => this.open());
|
|
1948
|
+
document.body.appendChild(btn);
|
|
1949
|
+
}
|
|
1950
|
+
removeSettingsButton() {
|
|
1951
|
+
document.getElementById("zerocost-privacy-settings-btn")?.remove();
|
|
1952
|
+
}
|
|
1877
1953
|
};
|
|
1878
1954
|
|
|
1879
1955
|
// src/index.ts
|
|
@@ -1907,21 +1983,15 @@ var ZerocostSDK = class {
|
|
|
1907
1983
|
});
|
|
1908
1984
|
}
|
|
1909
1985
|
async init() {
|
|
1910
|
-
this.core.init();
|
|
1911
1986
|
if (typeof document === "undefined") {
|
|
1912
|
-
this.core.log("Running in non-browser environment; skipping DOM injection.");
|
|
1913
1987
|
return;
|
|
1914
1988
|
}
|
|
1915
1989
|
if (window !== window.top) {
|
|
1916
|
-
this.core.log("Running inside an iframe. Ads render if permissions allow.");
|
|
1917
1990
|
}
|
|
1918
|
-
this.core.log("Initializing Zerocost SDK.");
|
|
1919
1991
|
if (this.consent.shouldPrompt()) {
|
|
1920
|
-
this.core.log("Consent required \u2014 showing prompt.");
|
|
1921
1992
|
await this.consent.promptAndWait();
|
|
1922
1993
|
}
|
|
1923
1994
|
if (!this.consent.has("ads")) {
|
|
1924
|
-
this.core.log("Ads consent not granted \u2014 skipping ad injection.");
|
|
1925
1995
|
return;
|
|
1926
1996
|
}
|
|
1927
1997
|
const cachedConfig = this.readCachedConfig();
|
|
@@ -1929,12 +1999,11 @@ var ZerocostSDK = class {
|
|
|
1929
1999
|
this.lastConfigHash = this.configToHash(cachedConfig);
|
|
1930
2000
|
this.syncDataCollection(cachedConfig.dataCollection);
|
|
1931
2001
|
await this.widget.autoInjectWithConfig(cachedConfig.display, cachedConfig.widget);
|
|
1932
|
-
this.core.log("Applied cached config immediately.");
|
|
1933
2002
|
}
|
|
1934
2003
|
this.startConfigSync();
|
|
1935
2004
|
try {
|
|
1936
2005
|
await this.refreshConfig({ force: true, reason: "init" });
|
|
1937
|
-
this.core.log(
|
|
2006
|
+
this.core.log(`Zerocost SDK initialized (${this.core.getConfig().appId})`);
|
|
1938
2007
|
} catch (err) {
|
|
1939
2008
|
this.core.log(`Init error: ${err}. Attempting fallback ad injection.`);
|
|
1940
2009
|
await this.widget.autoInject();
|
|
@@ -2108,6 +2177,10 @@ var ZerocostSDK = class {
|
|
|
2108
2177
|
return { valid: false, error: err.message };
|
|
2109
2178
|
}
|
|
2110
2179
|
}
|
|
2180
|
+
/** Open the consent/privacy settings popup. */
|
|
2181
|
+
async showSettings() {
|
|
2182
|
+
return this.consent.open();
|
|
2183
|
+
}
|
|
2111
2184
|
};
|
|
2112
2185
|
export {
|
|
2113
2186
|
ConsentManager,
|
package/dist/types/index.d.ts
CHANGED
|
@@ -11,6 +11,8 @@ export interface ZerocostConsentRecord {
|
|
|
11
11
|
}
|
|
12
12
|
export interface ConsentConfig {
|
|
13
13
|
privacyPolicyUrl?: string;
|
|
14
|
+
showSettingsButton?: boolean;
|
|
15
|
+
buttonPosition?: 'bottom-left' | 'bottom-right' | 'top-left' | 'top-right';
|
|
14
16
|
onConsentChange?: (preferences: ZerocostConsentRecord) => void;
|
|
15
17
|
}
|
|
16
18
|
export type ConsentFeature = 'ads' | 'usageData' | 'aiInteractions';
|