@zerocost/sdk 0.13.0 → 0.15.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 +6 -0
- package/dist/index.cjs +93 -25
- package/dist/index.d.ts +2 -0
- package/dist/index.js +93 -25
- 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
|
};
|
|
@@ -1354,13 +1363,14 @@ function injectStyles(theme) {
|
|
|
1354
1363
|
`;
|
|
1355
1364
|
}
|
|
1356
1365
|
const css = `
|
|
1366
|
+
@import url('https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;600;700&display=swap');
|
|
1357
1367
|
${themeRule}
|
|
1358
1368
|
|
|
1359
1369
|
.zc-consent-root * {
|
|
1360
1370
|
box-sizing: border-box;
|
|
1361
1371
|
margin: 0;
|
|
1362
1372
|
padding: 0;
|
|
1363
|
-
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI',
|
|
1373
|
+
font-family: 'Space Grotesk', -apple-system, BlinkMacSystemFont, 'Segoe UI', Inter, Roboto, sans-serif;
|
|
1364
1374
|
}
|
|
1365
1375
|
|
|
1366
1376
|
.zc-consent-backdrop {
|
|
@@ -1389,10 +1399,10 @@ function injectStyles(theme) {
|
|
|
1389
1399
|
border: 1px solid var(--zc-border);
|
|
1390
1400
|
border-radius: 16px;
|
|
1391
1401
|
width: 100%;
|
|
1392
|
-
max-width:
|
|
1402
|
+
max-width: 440px;
|
|
1393
1403
|
max-height: 90vh;
|
|
1394
1404
|
overflow-y: auto;
|
|
1395
|
-
padding:
|
|
1405
|
+
padding: 24px 20px 20px;
|
|
1396
1406
|
animation: zc-fade-in 200ms ease;
|
|
1397
1407
|
}
|
|
1398
1408
|
|
|
@@ -1416,7 +1426,7 @@ function injectStyles(theme) {
|
|
|
1416
1426
|
display: flex;
|
|
1417
1427
|
align-items: center;
|
|
1418
1428
|
gap: 10px;
|
|
1419
|
-
margin-bottom:
|
|
1429
|
+
margin-bottom: 4px;
|
|
1420
1430
|
}
|
|
1421
1431
|
|
|
1422
1432
|
.zc-consent-logo {
|
|
@@ -1446,21 +1456,21 @@ function injectStyles(theme) {
|
|
|
1446
1456
|
font-size: 13px;
|
|
1447
1457
|
color: var(--zc-text-secondary);
|
|
1448
1458
|
line-height: 1.5;
|
|
1449
|
-
margin-bottom:
|
|
1459
|
+
margin-bottom: 16px;
|
|
1450
1460
|
}
|
|
1451
1461
|
|
|
1452
1462
|
.zc-consent-toggles {
|
|
1453
1463
|
display: flex;
|
|
1454
1464
|
flex-direction: column;
|
|
1455
1465
|
gap: 10px;
|
|
1456
|
-
margin-bottom:
|
|
1466
|
+
margin-bottom: 16px;
|
|
1457
1467
|
}
|
|
1458
1468
|
|
|
1459
1469
|
.zc-consent-toggle-card {
|
|
1460
1470
|
background: var(--zc-surface);
|
|
1461
1471
|
border: 1px solid var(--zc-border);
|
|
1462
1472
|
border-radius: 12px;
|
|
1463
|
-
padding: 14px
|
|
1473
|
+
padding: 12px 14px;
|
|
1464
1474
|
}
|
|
1465
1475
|
|
|
1466
1476
|
.zc-consent-toggle-row {
|
|
@@ -1545,7 +1555,7 @@ function injectStyles(theme) {
|
|
|
1545
1555
|
flex-wrap: wrap;
|
|
1546
1556
|
gap: 4px 12px;
|
|
1547
1557
|
justify-content: center;
|
|
1548
|
-
margin-bottom:
|
|
1558
|
+
margin-bottom: 14px;
|
|
1549
1559
|
}
|
|
1550
1560
|
|
|
1551
1561
|
.zc-consent-footer a {
|
|
@@ -1588,6 +1598,44 @@ function injectStyles(theme) {
|
|
|
1588
1598
|
.zc-consent-confirm:active {
|
|
1589
1599
|
opacity: 0.75;
|
|
1590
1600
|
}
|
|
1601
|
+
|
|
1602
|
+
/* Floating settings button */
|
|
1603
|
+
.zc-settings-btn {
|
|
1604
|
+
position: fixed;
|
|
1605
|
+
width: 44px;
|
|
1606
|
+
height: 44px;
|
|
1607
|
+
border-radius: 50%;
|
|
1608
|
+
background: var(--zc-bg);
|
|
1609
|
+
border: 1px solid var(--zc-border);
|
|
1610
|
+
color: var(--zc-text);
|
|
1611
|
+
display: flex;
|
|
1612
|
+
align-items: center;
|
|
1613
|
+
justify-content: center;
|
|
1614
|
+
cursor: pointer;
|
|
1615
|
+
z-index: 999998;
|
|
1616
|
+
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
|
|
1617
|
+
transition: transform 200ms, border-color 200ms;
|
|
1618
|
+
}
|
|
1619
|
+
|
|
1620
|
+
.zc-settings-btn:hover {
|
|
1621
|
+
transform: scale(1.05);
|
|
1622
|
+
border-color: var(--zc-text-secondary);
|
|
1623
|
+
}
|
|
1624
|
+
|
|
1625
|
+
.zc-settings-btn svg {
|
|
1626
|
+
width: 20px;
|
|
1627
|
+
height: 20px;
|
|
1628
|
+
}
|
|
1629
|
+
|
|
1630
|
+
.zc-settings-bottom-left { bottom: 20px; left: 20px; }
|
|
1631
|
+
.zc-settings-bottom-right { bottom: 20px; right: 20px; }
|
|
1632
|
+
.zc-settings-top-left { top: 20px; left: 20px; }
|
|
1633
|
+
.zc-settings-top-right { top: 20px; right: 20px; }
|
|
1634
|
+
|
|
1635
|
+
@media (max-width: 640px) {
|
|
1636
|
+
.zc-settings-btn { width: 40px; height: 40px; }
|
|
1637
|
+
.zc-settings-bottom-left, .zc-settings-bottom-right { bottom: 16px; }
|
|
1638
|
+
}
|
|
1591
1639
|
`;
|
|
1592
1640
|
const style = document.createElement("style");
|
|
1593
1641
|
style.id = STYLE_ID;
|
|
@@ -1670,33 +1718,33 @@ function showConsentUI(options) {
|
|
|
1670
1718
|
card.appendChild(subtitle);
|
|
1671
1719
|
const toggles = document.createElement("div");
|
|
1672
1720
|
toggles.className = "zc-consent-toggles";
|
|
1673
|
-
const
|
|
1721
|
+
const zerocostBaseUrl = ZEROCOST_BASE_URL;
|
|
1674
1722
|
toggles.appendChild(createToggleCard(
|
|
1675
1723
|
"zc-toggle-ads",
|
|
1676
1724
|
"Ads",
|
|
1677
1725
|
"Contextual, non-intrusive ads. No cookies or browsing history used.",
|
|
1678
|
-
`${
|
|
1726
|
+
`${zerocostBaseUrl}/consent/ads`,
|
|
1679
1727
|
defaults.ads
|
|
1680
1728
|
));
|
|
1681
1729
|
toggles.appendChild(createToggleCard(
|
|
1682
1730
|
"zc-toggle-usage",
|
|
1683
1731
|
"Usage data",
|
|
1684
1732
|
"Anonymized usage patterns. No personal information is shared.",
|
|
1685
|
-
`${
|
|
1733
|
+
`${zerocostBaseUrl}/consent/usage-data`,
|
|
1686
1734
|
defaults.usageData
|
|
1687
1735
|
));
|
|
1688
1736
|
toggles.appendChild(createToggleCard(
|
|
1689
1737
|
"zc-toggle-ai",
|
|
1690
1738
|
"AI interactions",
|
|
1691
1739
|
"Anonymized conversation data used for AI research.",
|
|
1692
|
-
`${
|
|
1740
|
+
`${zerocostBaseUrl}/consent/ai-interactions`,
|
|
1693
1741
|
defaults.aiInteractions
|
|
1694
1742
|
));
|
|
1695
1743
|
card.appendChild(toggles);
|
|
1696
1744
|
const footer = document.createElement("div");
|
|
1697
1745
|
footer.className = "zc-consent-footer";
|
|
1698
1746
|
const ppLink = document.createElement("a");
|
|
1699
|
-
ppLink.href = privacyPolicyUrl || `${
|
|
1747
|
+
ppLink.href = privacyPolicyUrl || `${zerocostBaseUrl}/privacy`;
|
|
1700
1748
|
ppLink.target = "_blank";
|
|
1701
1749
|
ppLink.rel = "noopener noreferrer";
|
|
1702
1750
|
ppLink.textContent = "Privacy Policy";
|
|
@@ -1706,7 +1754,7 @@ function showConsentUI(options) {
|
|
|
1706
1754
|
sep1.textContent = "\xB7";
|
|
1707
1755
|
footer.appendChild(sep1);
|
|
1708
1756
|
const termsLink = document.createElement("a");
|
|
1709
|
-
termsLink.href = `${
|
|
1757
|
+
termsLink.href = `${zerocostBaseUrl}/terms`;
|
|
1710
1758
|
termsLink.target = "_blank";
|
|
1711
1759
|
termsLink.rel = "noopener noreferrer";
|
|
1712
1760
|
termsLink.textContent = "Terms";
|
|
@@ -1716,7 +1764,7 @@ function showConsentUI(options) {
|
|
|
1716
1764
|
sep2.textContent = "\xB7";
|
|
1717
1765
|
footer.appendChild(sep2);
|
|
1718
1766
|
const dnsLink = document.createElement("a");
|
|
1719
|
-
dnsLink.href = `${
|
|
1767
|
+
dnsLink.href = `${zerocostBaseUrl}/do-not-sell`;
|
|
1720
1768
|
dnsLink.target = "_blank";
|
|
1721
1769
|
dnsLink.rel = "noopener noreferrer";
|
|
1722
1770
|
dnsLink.textContent = "Do Not Sell My Data";
|
|
@@ -1760,6 +1808,9 @@ var ConsentManager = class {
|
|
|
1760
1808
|
this.appName = opts.appName ?? "";
|
|
1761
1809
|
this.theme = opts.theme ?? "dark";
|
|
1762
1810
|
this.hydrateFromStorage();
|
|
1811
|
+
if (this.consentConfig.showSettingsButton) {
|
|
1812
|
+
this.injectSettingsButton();
|
|
1813
|
+
}
|
|
1763
1814
|
}
|
|
1764
1815
|
// ── Public API (per spec §6.3) ───────────────────────────────────
|
|
1765
1816
|
/** Returns the current consent record, or null if none exists. */
|
|
@@ -1825,6 +1876,9 @@ var ConsentManager = class {
|
|
|
1825
1876
|
};
|
|
1826
1877
|
this.record = record;
|
|
1827
1878
|
this.writeStorage(record);
|
|
1879
|
+
if (this.consentConfig.showSettingsButton) {
|
|
1880
|
+
this.injectSettingsButton();
|
|
1881
|
+
}
|
|
1828
1882
|
if (this.consentConfig.onConsentChange) {
|
|
1829
1883
|
try {
|
|
1830
1884
|
this.consentConfig.onConsentChange(record);
|
|
@@ -1903,6 +1957,23 @@ var ConsentManager = class {
|
|
|
1903
1957
|
this.client.log(`Failed to submit consent to server: ${err}`);
|
|
1904
1958
|
}
|
|
1905
1959
|
}
|
|
1960
|
+
injectSettingsButton() {
|
|
1961
|
+
if (typeof document === "undefined") return;
|
|
1962
|
+
const existing = document.getElementById("zerocost-privacy-settings-btn");
|
|
1963
|
+
if (existing) return;
|
|
1964
|
+
const btn = document.createElement("button");
|
|
1965
|
+
btn.id = "zerocost-privacy-settings-btn";
|
|
1966
|
+
btn.setAttribute("aria-label", "Privacy Settings");
|
|
1967
|
+
btn.title = "Privacy Settings";
|
|
1968
|
+
const pos = this.consentConfig.buttonPosition || "bottom-left";
|
|
1969
|
+
btn.className = `zc-settings-btn zc-settings-${pos}`;
|
|
1970
|
+
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>`;
|
|
1971
|
+
btn.addEventListener("click", () => this.open());
|
|
1972
|
+
document.body.appendChild(btn);
|
|
1973
|
+
}
|
|
1974
|
+
removeSettingsButton() {
|
|
1975
|
+
document.getElementById("zerocost-privacy-settings-btn")?.remove();
|
|
1976
|
+
}
|
|
1906
1977
|
};
|
|
1907
1978
|
|
|
1908
1979
|
// src/index.ts
|
|
@@ -1936,21 +2007,15 @@ var ZerocostSDK = class {
|
|
|
1936
2007
|
});
|
|
1937
2008
|
}
|
|
1938
2009
|
async init() {
|
|
1939
|
-
this.core.init();
|
|
1940
2010
|
if (typeof document === "undefined") {
|
|
1941
|
-
this.core.log("Running in non-browser environment; skipping DOM injection.");
|
|
1942
2011
|
return;
|
|
1943
2012
|
}
|
|
1944
2013
|
if (window !== window.top) {
|
|
1945
|
-
this.core.log("Running inside an iframe. Ads render if permissions allow.");
|
|
1946
2014
|
}
|
|
1947
|
-
this.core.log("Initializing Zerocost SDK.");
|
|
1948
2015
|
if (this.consent.shouldPrompt()) {
|
|
1949
|
-
this.core.log("Consent required \u2014 showing prompt.");
|
|
1950
2016
|
await this.consent.promptAndWait();
|
|
1951
2017
|
}
|
|
1952
2018
|
if (!this.consent.has("ads")) {
|
|
1953
|
-
this.core.log("Ads consent not granted \u2014 skipping ad injection.");
|
|
1954
2019
|
return;
|
|
1955
2020
|
}
|
|
1956
2021
|
const cachedConfig = this.readCachedConfig();
|
|
@@ -1958,12 +2023,11 @@ var ZerocostSDK = class {
|
|
|
1958
2023
|
this.lastConfigHash = this.configToHash(cachedConfig);
|
|
1959
2024
|
this.syncDataCollection(cachedConfig.dataCollection);
|
|
1960
2025
|
await this.widget.autoInjectWithConfig(cachedConfig.display, cachedConfig.widget);
|
|
1961
|
-
this.core.log("Applied cached config immediately.");
|
|
1962
2026
|
}
|
|
1963
2027
|
this.startConfigSync();
|
|
1964
2028
|
try {
|
|
1965
2029
|
await this.refreshConfig({ force: true, reason: "init" });
|
|
1966
|
-
this.core.log(
|
|
2030
|
+
this.core.log(`Zerocost SDK initialized (${this.core.getConfig().appId})`);
|
|
1967
2031
|
} catch (err) {
|
|
1968
2032
|
this.core.log(`Init error: ${err}. Attempting fallback ad injection.`);
|
|
1969
2033
|
await this.widget.autoInject();
|
|
@@ -2137,6 +2201,10 @@ var ZerocostSDK = class {
|
|
|
2137
2201
|
return { valid: false, error: err.message };
|
|
2138
2202
|
}
|
|
2139
2203
|
}
|
|
2204
|
+
/** Open the consent/privacy settings popup. */
|
|
2205
|
+
async showSettings() {
|
|
2206
|
+
return this.consent.open();
|
|
2207
|
+
}
|
|
2140
2208
|
};
|
|
2141
2209
|
// Annotate the CommonJS export names for ESM import in node:
|
|
2142
2210
|
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
|
};
|
|
@@ -1324,13 +1333,14 @@ function injectStyles(theme) {
|
|
|
1324
1333
|
`;
|
|
1325
1334
|
}
|
|
1326
1335
|
const css = `
|
|
1336
|
+
@import url('https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;600;700&display=swap');
|
|
1327
1337
|
${themeRule}
|
|
1328
1338
|
|
|
1329
1339
|
.zc-consent-root * {
|
|
1330
1340
|
box-sizing: border-box;
|
|
1331
1341
|
margin: 0;
|
|
1332
1342
|
padding: 0;
|
|
1333
|
-
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI',
|
|
1343
|
+
font-family: 'Space Grotesk', -apple-system, BlinkMacSystemFont, 'Segoe UI', Inter, Roboto, sans-serif;
|
|
1334
1344
|
}
|
|
1335
1345
|
|
|
1336
1346
|
.zc-consent-backdrop {
|
|
@@ -1359,10 +1369,10 @@ function injectStyles(theme) {
|
|
|
1359
1369
|
border: 1px solid var(--zc-border);
|
|
1360
1370
|
border-radius: 16px;
|
|
1361
1371
|
width: 100%;
|
|
1362
|
-
max-width:
|
|
1372
|
+
max-width: 440px;
|
|
1363
1373
|
max-height: 90vh;
|
|
1364
1374
|
overflow-y: auto;
|
|
1365
|
-
padding:
|
|
1375
|
+
padding: 24px 20px 20px;
|
|
1366
1376
|
animation: zc-fade-in 200ms ease;
|
|
1367
1377
|
}
|
|
1368
1378
|
|
|
@@ -1386,7 +1396,7 @@ function injectStyles(theme) {
|
|
|
1386
1396
|
display: flex;
|
|
1387
1397
|
align-items: center;
|
|
1388
1398
|
gap: 10px;
|
|
1389
|
-
margin-bottom:
|
|
1399
|
+
margin-bottom: 4px;
|
|
1390
1400
|
}
|
|
1391
1401
|
|
|
1392
1402
|
.zc-consent-logo {
|
|
@@ -1416,21 +1426,21 @@ function injectStyles(theme) {
|
|
|
1416
1426
|
font-size: 13px;
|
|
1417
1427
|
color: var(--zc-text-secondary);
|
|
1418
1428
|
line-height: 1.5;
|
|
1419
|
-
margin-bottom:
|
|
1429
|
+
margin-bottom: 16px;
|
|
1420
1430
|
}
|
|
1421
1431
|
|
|
1422
1432
|
.zc-consent-toggles {
|
|
1423
1433
|
display: flex;
|
|
1424
1434
|
flex-direction: column;
|
|
1425
1435
|
gap: 10px;
|
|
1426
|
-
margin-bottom:
|
|
1436
|
+
margin-bottom: 16px;
|
|
1427
1437
|
}
|
|
1428
1438
|
|
|
1429
1439
|
.zc-consent-toggle-card {
|
|
1430
1440
|
background: var(--zc-surface);
|
|
1431
1441
|
border: 1px solid var(--zc-border);
|
|
1432
1442
|
border-radius: 12px;
|
|
1433
|
-
padding: 14px
|
|
1443
|
+
padding: 12px 14px;
|
|
1434
1444
|
}
|
|
1435
1445
|
|
|
1436
1446
|
.zc-consent-toggle-row {
|
|
@@ -1515,7 +1525,7 @@ function injectStyles(theme) {
|
|
|
1515
1525
|
flex-wrap: wrap;
|
|
1516
1526
|
gap: 4px 12px;
|
|
1517
1527
|
justify-content: center;
|
|
1518
|
-
margin-bottom:
|
|
1528
|
+
margin-bottom: 14px;
|
|
1519
1529
|
}
|
|
1520
1530
|
|
|
1521
1531
|
.zc-consent-footer a {
|
|
@@ -1558,6 +1568,44 @@ function injectStyles(theme) {
|
|
|
1558
1568
|
.zc-consent-confirm:active {
|
|
1559
1569
|
opacity: 0.75;
|
|
1560
1570
|
}
|
|
1571
|
+
|
|
1572
|
+
/* Floating settings button */
|
|
1573
|
+
.zc-settings-btn {
|
|
1574
|
+
position: fixed;
|
|
1575
|
+
width: 44px;
|
|
1576
|
+
height: 44px;
|
|
1577
|
+
border-radius: 50%;
|
|
1578
|
+
background: var(--zc-bg);
|
|
1579
|
+
border: 1px solid var(--zc-border);
|
|
1580
|
+
color: var(--zc-text);
|
|
1581
|
+
display: flex;
|
|
1582
|
+
align-items: center;
|
|
1583
|
+
justify-content: center;
|
|
1584
|
+
cursor: pointer;
|
|
1585
|
+
z-index: 999998;
|
|
1586
|
+
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
|
|
1587
|
+
transition: transform 200ms, border-color 200ms;
|
|
1588
|
+
}
|
|
1589
|
+
|
|
1590
|
+
.zc-settings-btn:hover {
|
|
1591
|
+
transform: scale(1.05);
|
|
1592
|
+
border-color: var(--zc-text-secondary);
|
|
1593
|
+
}
|
|
1594
|
+
|
|
1595
|
+
.zc-settings-btn svg {
|
|
1596
|
+
width: 20px;
|
|
1597
|
+
height: 20px;
|
|
1598
|
+
}
|
|
1599
|
+
|
|
1600
|
+
.zc-settings-bottom-left { bottom: 20px; left: 20px; }
|
|
1601
|
+
.zc-settings-bottom-right { bottom: 20px; right: 20px; }
|
|
1602
|
+
.zc-settings-top-left { top: 20px; left: 20px; }
|
|
1603
|
+
.zc-settings-top-right { top: 20px; right: 20px; }
|
|
1604
|
+
|
|
1605
|
+
@media (max-width: 640px) {
|
|
1606
|
+
.zc-settings-btn { width: 40px; height: 40px; }
|
|
1607
|
+
.zc-settings-bottom-left, .zc-settings-bottom-right { bottom: 16px; }
|
|
1608
|
+
}
|
|
1561
1609
|
`;
|
|
1562
1610
|
const style = document.createElement("style");
|
|
1563
1611
|
style.id = STYLE_ID;
|
|
@@ -1640,33 +1688,33 @@ function showConsentUI(options) {
|
|
|
1640
1688
|
card.appendChild(subtitle);
|
|
1641
1689
|
const toggles = document.createElement("div");
|
|
1642
1690
|
toggles.className = "zc-consent-toggles";
|
|
1643
|
-
const
|
|
1691
|
+
const zerocostBaseUrl = ZEROCOST_BASE_URL;
|
|
1644
1692
|
toggles.appendChild(createToggleCard(
|
|
1645
1693
|
"zc-toggle-ads",
|
|
1646
1694
|
"Ads",
|
|
1647
1695
|
"Contextual, non-intrusive ads. No cookies or browsing history used.",
|
|
1648
|
-
`${
|
|
1696
|
+
`${zerocostBaseUrl}/consent/ads`,
|
|
1649
1697
|
defaults.ads
|
|
1650
1698
|
));
|
|
1651
1699
|
toggles.appendChild(createToggleCard(
|
|
1652
1700
|
"zc-toggle-usage",
|
|
1653
1701
|
"Usage data",
|
|
1654
1702
|
"Anonymized usage patterns. No personal information is shared.",
|
|
1655
|
-
`${
|
|
1703
|
+
`${zerocostBaseUrl}/consent/usage-data`,
|
|
1656
1704
|
defaults.usageData
|
|
1657
1705
|
));
|
|
1658
1706
|
toggles.appendChild(createToggleCard(
|
|
1659
1707
|
"zc-toggle-ai",
|
|
1660
1708
|
"AI interactions",
|
|
1661
1709
|
"Anonymized conversation data used for AI research.",
|
|
1662
|
-
`${
|
|
1710
|
+
`${zerocostBaseUrl}/consent/ai-interactions`,
|
|
1663
1711
|
defaults.aiInteractions
|
|
1664
1712
|
));
|
|
1665
1713
|
card.appendChild(toggles);
|
|
1666
1714
|
const footer = document.createElement("div");
|
|
1667
1715
|
footer.className = "zc-consent-footer";
|
|
1668
1716
|
const ppLink = document.createElement("a");
|
|
1669
|
-
ppLink.href = privacyPolicyUrl || `${
|
|
1717
|
+
ppLink.href = privacyPolicyUrl || `${zerocostBaseUrl}/privacy`;
|
|
1670
1718
|
ppLink.target = "_blank";
|
|
1671
1719
|
ppLink.rel = "noopener noreferrer";
|
|
1672
1720
|
ppLink.textContent = "Privacy Policy";
|
|
@@ -1676,7 +1724,7 @@ function showConsentUI(options) {
|
|
|
1676
1724
|
sep1.textContent = "\xB7";
|
|
1677
1725
|
footer.appendChild(sep1);
|
|
1678
1726
|
const termsLink = document.createElement("a");
|
|
1679
|
-
termsLink.href = `${
|
|
1727
|
+
termsLink.href = `${zerocostBaseUrl}/terms`;
|
|
1680
1728
|
termsLink.target = "_blank";
|
|
1681
1729
|
termsLink.rel = "noopener noreferrer";
|
|
1682
1730
|
termsLink.textContent = "Terms";
|
|
@@ -1686,7 +1734,7 @@ function showConsentUI(options) {
|
|
|
1686
1734
|
sep2.textContent = "\xB7";
|
|
1687
1735
|
footer.appendChild(sep2);
|
|
1688
1736
|
const dnsLink = document.createElement("a");
|
|
1689
|
-
dnsLink.href = `${
|
|
1737
|
+
dnsLink.href = `${zerocostBaseUrl}/do-not-sell`;
|
|
1690
1738
|
dnsLink.target = "_blank";
|
|
1691
1739
|
dnsLink.rel = "noopener noreferrer";
|
|
1692
1740
|
dnsLink.textContent = "Do Not Sell My Data";
|
|
@@ -1730,6 +1778,9 @@ var ConsentManager = class {
|
|
|
1730
1778
|
this.appName = opts.appName ?? "";
|
|
1731
1779
|
this.theme = opts.theme ?? "dark";
|
|
1732
1780
|
this.hydrateFromStorage();
|
|
1781
|
+
if (this.consentConfig.showSettingsButton) {
|
|
1782
|
+
this.injectSettingsButton();
|
|
1783
|
+
}
|
|
1733
1784
|
}
|
|
1734
1785
|
// ── Public API (per spec §6.3) ───────────────────────────────────
|
|
1735
1786
|
/** Returns the current consent record, or null if none exists. */
|
|
@@ -1795,6 +1846,9 @@ var ConsentManager = class {
|
|
|
1795
1846
|
};
|
|
1796
1847
|
this.record = record;
|
|
1797
1848
|
this.writeStorage(record);
|
|
1849
|
+
if (this.consentConfig.showSettingsButton) {
|
|
1850
|
+
this.injectSettingsButton();
|
|
1851
|
+
}
|
|
1798
1852
|
if (this.consentConfig.onConsentChange) {
|
|
1799
1853
|
try {
|
|
1800
1854
|
this.consentConfig.onConsentChange(record);
|
|
@@ -1873,6 +1927,23 @@ var ConsentManager = class {
|
|
|
1873
1927
|
this.client.log(`Failed to submit consent to server: ${err}`);
|
|
1874
1928
|
}
|
|
1875
1929
|
}
|
|
1930
|
+
injectSettingsButton() {
|
|
1931
|
+
if (typeof document === "undefined") return;
|
|
1932
|
+
const existing = document.getElementById("zerocost-privacy-settings-btn");
|
|
1933
|
+
if (existing) return;
|
|
1934
|
+
const btn = document.createElement("button");
|
|
1935
|
+
btn.id = "zerocost-privacy-settings-btn";
|
|
1936
|
+
btn.setAttribute("aria-label", "Privacy Settings");
|
|
1937
|
+
btn.title = "Privacy Settings";
|
|
1938
|
+
const pos = this.consentConfig.buttonPosition || "bottom-left";
|
|
1939
|
+
btn.className = `zc-settings-btn zc-settings-${pos}`;
|
|
1940
|
+
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>`;
|
|
1941
|
+
btn.addEventListener("click", () => this.open());
|
|
1942
|
+
document.body.appendChild(btn);
|
|
1943
|
+
}
|
|
1944
|
+
removeSettingsButton() {
|
|
1945
|
+
document.getElementById("zerocost-privacy-settings-btn")?.remove();
|
|
1946
|
+
}
|
|
1876
1947
|
};
|
|
1877
1948
|
|
|
1878
1949
|
// src/index.ts
|
|
@@ -1906,21 +1977,15 @@ var ZerocostSDK = class {
|
|
|
1906
1977
|
});
|
|
1907
1978
|
}
|
|
1908
1979
|
async init() {
|
|
1909
|
-
this.core.init();
|
|
1910
1980
|
if (typeof document === "undefined") {
|
|
1911
|
-
this.core.log("Running in non-browser environment; skipping DOM injection.");
|
|
1912
1981
|
return;
|
|
1913
1982
|
}
|
|
1914
1983
|
if (window !== window.top) {
|
|
1915
|
-
this.core.log("Running inside an iframe. Ads render if permissions allow.");
|
|
1916
1984
|
}
|
|
1917
|
-
this.core.log("Initializing Zerocost SDK.");
|
|
1918
1985
|
if (this.consent.shouldPrompt()) {
|
|
1919
|
-
this.core.log("Consent required \u2014 showing prompt.");
|
|
1920
1986
|
await this.consent.promptAndWait();
|
|
1921
1987
|
}
|
|
1922
1988
|
if (!this.consent.has("ads")) {
|
|
1923
|
-
this.core.log("Ads consent not granted \u2014 skipping ad injection.");
|
|
1924
1989
|
return;
|
|
1925
1990
|
}
|
|
1926
1991
|
const cachedConfig = this.readCachedConfig();
|
|
@@ -1928,12 +1993,11 @@ var ZerocostSDK = class {
|
|
|
1928
1993
|
this.lastConfigHash = this.configToHash(cachedConfig);
|
|
1929
1994
|
this.syncDataCollection(cachedConfig.dataCollection);
|
|
1930
1995
|
await this.widget.autoInjectWithConfig(cachedConfig.display, cachedConfig.widget);
|
|
1931
|
-
this.core.log("Applied cached config immediately.");
|
|
1932
1996
|
}
|
|
1933
1997
|
this.startConfigSync();
|
|
1934
1998
|
try {
|
|
1935
1999
|
await this.refreshConfig({ force: true, reason: "init" });
|
|
1936
|
-
this.core.log(
|
|
2000
|
+
this.core.log(`Zerocost SDK initialized (${this.core.getConfig().appId})`);
|
|
1937
2001
|
} catch (err) {
|
|
1938
2002
|
this.core.log(`Init error: ${err}. Attempting fallback ad injection.`);
|
|
1939
2003
|
await this.widget.autoInject();
|
|
@@ -2107,6 +2171,10 @@ var ZerocostSDK = class {
|
|
|
2107
2171
|
return { valid: false, error: err.message };
|
|
2108
2172
|
}
|
|
2109
2173
|
}
|
|
2174
|
+
/** Open the consent/privacy settings popup. */
|
|
2175
|
+
async showSettings() {
|
|
2176
|
+
return this.consent.open();
|
|
2177
|
+
}
|
|
2110
2178
|
};
|
|
2111
2179
|
export {
|
|
2112
2180
|
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';
|