@syntrologie/runtime-sdk 2.8.0-canary.183 → 2.8.0-canary.185
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/defaults/clickIds.d.ts +14 -0
- package/dist/defaults/identifyOnEmail.d.ts +23 -0
- package/dist/defaults/identifyOnUrlParam.d.ts +29 -0
- package/dist/defaults/index.d.ts +51 -0
- package/dist/defaults/initialProperties.d.ts +26 -0
- package/dist/defaults/jsonLd.d.ts +17 -0
- package/dist/index.js +729 -1
- package/dist/index.js.map +4 -4
- package/dist/smart-canvas.esm.js +100 -100
- package/dist/smart-canvas.esm.js.map +4 -4
- package/dist/smart-canvas.js +711 -2
- package/dist/smart-canvas.js.map +4 -4
- package/dist/smart-canvas.min.js +101 -101
- package/dist/smart-canvas.min.js.map +4 -4
- package/dist/telemetry/adapters/posthog.d.ts +26 -0
- package/dist/telemetry/consent/ConsentDetector.d.ts +26 -0
- package/dist/telemetry/{consent.d.ts → consent/ConsentGate.d.ts} +33 -1
- package/dist/telemetry/consent/adapters/gtmConsentMode.d.ts +18 -0
- package/dist/telemetry/consent/adapters/iabTcf.d.ts +21 -0
- package/dist/telemetry/consent/adapters/shopify.d.ts +17 -0
- package/dist/telemetry/consent/index.d.ts +17 -0
- package/dist/telemetry/consent/types.d.ts +47 -0
- package/dist/telemetry/types.d.ts +24 -0
- package/dist/version.d.ts +1 -1
- package/package.json +3 -5
- package/schema/canvas-config.schema.json +352 -1
- package/CAPABILITIES.md +0 -1450
package/dist/smart-canvas.js
CHANGED
|
@@ -16144,7 +16144,7 @@ Please report this to https://github.com/markedjs/marked.`, e9) {
|
|
|
16144
16144
|
}
|
|
16145
16145
|
|
|
16146
16146
|
// src/version.ts
|
|
16147
|
-
var SDK_VERSION = "2.8.0-canary.
|
|
16147
|
+
var SDK_VERSION = "2.8.0-canary.185";
|
|
16148
16148
|
|
|
16149
16149
|
// src/types.ts
|
|
16150
16150
|
var SDK_SCHEMA_VERSION = "2.0";
|
|
@@ -21640,6 +21640,300 @@ ${cssRules}
|
|
|
21640
21640
|
return derivedCdnBase;
|
|
21641
21641
|
}
|
|
21642
21642
|
|
|
21643
|
+
// src/defaults/clickIds.ts
|
|
21644
|
+
var CLICK_ID_KEYS = [
|
|
21645
|
+
"fbclid",
|
|
21646
|
+
// Meta (Facebook + Instagram)
|
|
21647
|
+
"ttclid",
|
|
21648
|
+
// TikTok
|
|
21649
|
+
"gclid",
|
|
21650
|
+
// Google Ads
|
|
21651
|
+
"gbraid",
|
|
21652
|
+
// Google Ads (iOS app web)
|
|
21653
|
+
"wbraid",
|
|
21654
|
+
// Google Ads (web app)
|
|
21655
|
+
"msclkid",
|
|
21656
|
+
// Microsoft / Bing Ads
|
|
21657
|
+
"twclid",
|
|
21658
|
+
// X / Twitter
|
|
21659
|
+
"li_fat_id",
|
|
21660
|
+
// LinkedIn
|
|
21661
|
+
"epik",
|
|
21662
|
+
// Pinterest
|
|
21663
|
+
"ScCid",
|
|
21664
|
+
// Snapchat
|
|
21665
|
+
"yclid",
|
|
21666
|
+
// Yandex
|
|
21667
|
+
"_kx",
|
|
21668
|
+
// Klaviyo
|
|
21669
|
+
"mc_eid",
|
|
21670
|
+
// Mailchimp recipient
|
|
21671
|
+
"mc_cid"
|
|
21672
|
+
// Mailchimp campaign
|
|
21673
|
+
];
|
|
21674
|
+
function promoteClickIds(telemetry) {
|
|
21675
|
+
if (typeof window === "undefined") return;
|
|
21676
|
+
if (!telemetry.setPersonPropertiesOnce) return;
|
|
21677
|
+
const params = new URLSearchParams(window.location.search);
|
|
21678
|
+
const captured = {};
|
|
21679
|
+
for (const key of CLICK_ID_KEYS) {
|
|
21680
|
+
const value = params.get(key);
|
|
21681
|
+
if (value) {
|
|
21682
|
+
captured[`$initial_${key}`] = value;
|
|
21683
|
+
}
|
|
21684
|
+
}
|
|
21685
|
+
if (Object.keys(captured).length > 0) {
|
|
21686
|
+
telemetry.setPersonPropertiesOnce(captured);
|
|
21687
|
+
}
|
|
21688
|
+
}
|
|
21689
|
+
|
|
21690
|
+
// src/defaults/identifyOnEmail.ts
|
|
21691
|
+
var ANONYMOUS_UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
21692
|
+
var EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
21693
|
+
var DENYLIST_PATTERNS = [
|
|
21694
|
+
/@yopmail\.com$/i,
|
|
21695
|
+
/@mailinator\.com$/i,
|
|
21696
|
+
/@guerrillamail\.(com|info|net|org|biz)$/i,
|
|
21697
|
+
/@10minutemail\.com$/i,
|
|
21698
|
+
/^test@/i,
|
|
21699
|
+
/^noreply@/i,
|
|
21700
|
+
/^no-reply@/i
|
|
21701
|
+
];
|
|
21702
|
+
function isValidEmail(value) {
|
|
21703
|
+
if (!value) return false;
|
|
21704
|
+
return EMAIL_RE.test(value);
|
|
21705
|
+
}
|
|
21706
|
+
function isEmailDenylisted(value) {
|
|
21707
|
+
return DENYLIST_PATTERNS.some((re3) => re3.test(value));
|
|
21708
|
+
}
|
|
21709
|
+
function isEmailLikeInput(el) {
|
|
21710
|
+
if (el.type === "email") return true;
|
|
21711
|
+
const name = (el.name || "").toLowerCase();
|
|
21712
|
+
const id = (el.id || "").toLowerCase();
|
|
21713
|
+
if (/email/.test(name) || /email/.test(id)) return true;
|
|
21714
|
+
return false;
|
|
21715
|
+
}
|
|
21716
|
+
function isOptedOut(el) {
|
|
21717
|
+
return el.closest("[data-syntro-no-identify]") !== null;
|
|
21718
|
+
}
|
|
21719
|
+
function isAnonymous(distinctId) {
|
|
21720
|
+
if (!distinctId) return true;
|
|
21721
|
+
return ANONYMOUS_UUID_RE.test(distinctId);
|
|
21722
|
+
}
|
|
21723
|
+
function installEmailIdentifyListener(telemetry, consentGate) {
|
|
21724
|
+
if (typeof document === "undefined") return () => {
|
|
21725
|
+
};
|
|
21726
|
+
if (!telemetry.identify && !telemetry.alias) return () => {
|
|
21727
|
+
};
|
|
21728
|
+
const seenThisSession = /* @__PURE__ */ new Set();
|
|
21729
|
+
const fireIdentifyOrAlias = (value) => {
|
|
21730
|
+
const currentId = telemetry.getDistinctId?.();
|
|
21731
|
+
if (currentId === value) {
|
|
21732
|
+
seenThisSession.add(value);
|
|
21733
|
+
return;
|
|
21734
|
+
}
|
|
21735
|
+
if (isAnonymous(currentId)) {
|
|
21736
|
+
telemetry.identify?.(value, { email: value });
|
|
21737
|
+
} else {
|
|
21738
|
+
telemetry.alias?.(value);
|
|
21739
|
+
}
|
|
21740
|
+
seenThisSession.add(value);
|
|
21741
|
+
};
|
|
21742
|
+
const handler = (e9) => {
|
|
21743
|
+
const target = e9.target;
|
|
21744
|
+
if (!(target instanceof HTMLInputElement)) return;
|
|
21745
|
+
if (isOptedOut(target)) return;
|
|
21746
|
+
if (!isEmailLikeInput(target)) return;
|
|
21747
|
+
const value = (target.value ?? "").trim().toLowerCase();
|
|
21748
|
+
if (!isValidEmail(value)) return;
|
|
21749
|
+
if (isEmailDenylisted(value)) return;
|
|
21750
|
+
if (seenThisSession.has(value)) return;
|
|
21751
|
+
if (!consentGate) {
|
|
21752
|
+
fireIdentifyOrAlias(value);
|
|
21753
|
+
return;
|
|
21754
|
+
}
|
|
21755
|
+
const status = consentGate.getStatus();
|
|
21756
|
+
if (status === "granted") {
|
|
21757
|
+
fireIdentifyOrAlias(value);
|
|
21758
|
+
return;
|
|
21759
|
+
}
|
|
21760
|
+
if (status === "denied") {
|
|
21761
|
+
return;
|
|
21762
|
+
}
|
|
21763
|
+
consentGate.resolvePending().then((resolved) => {
|
|
21764
|
+
if (resolved === "granted") fireIdentifyOrAlias(value);
|
|
21765
|
+
});
|
|
21766
|
+
};
|
|
21767
|
+
document.addEventListener("change", handler, true);
|
|
21768
|
+
return () => {
|
|
21769
|
+
document.removeEventListener("change", handler, true);
|
|
21770
|
+
seenThisSession.clear();
|
|
21771
|
+
};
|
|
21772
|
+
}
|
|
21773
|
+
|
|
21774
|
+
// src/defaults/identifyOnUrlParam.ts
|
|
21775
|
+
var SYNTRO_UID_PARAM = "_syu";
|
|
21776
|
+
var ANONYMOUS_UUID_RE2 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
21777
|
+
function isAnonymous2(distinctId) {
|
|
21778
|
+
if (!distinctId) return true;
|
|
21779
|
+
return ANONYMOUS_UUID_RE2.test(distinctId);
|
|
21780
|
+
}
|
|
21781
|
+
function identifyFromUrlParam(telemetry, consentGate) {
|
|
21782
|
+
if (typeof window === "undefined") return;
|
|
21783
|
+
if (!telemetry.identify && !telemetry.alias) return;
|
|
21784
|
+
const value = new URLSearchParams(window.location.search).get(SYNTRO_UID_PARAM);
|
|
21785
|
+
if (!value) return;
|
|
21786
|
+
const trimmed = value.trim();
|
|
21787
|
+
if (!trimmed) return;
|
|
21788
|
+
const fire = () => {
|
|
21789
|
+
const currentId = telemetry.getDistinctId?.();
|
|
21790
|
+
if (currentId === trimmed) return;
|
|
21791
|
+
const properties = isValidEmail(trimmed) ? { email: trimmed } : void 0;
|
|
21792
|
+
if (isAnonymous2(currentId)) {
|
|
21793
|
+
telemetry.identify?.(trimmed, properties);
|
|
21794
|
+
} else {
|
|
21795
|
+
telemetry.alias?.(trimmed);
|
|
21796
|
+
}
|
|
21797
|
+
};
|
|
21798
|
+
if (!consentGate) {
|
|
21799
|
+
fire();
|
|
21800
|
+
return;
|
|
21801
|
+
}
|
|
21802
|
+
if (consentGate.getStatus() === "granted") {
|
|
21803
|
+
fire();
|
|
21804
|
+
return;
|
|
21805
|
+
}
|
|
21806
|
+
let fired = false;
|
|
21807
|
+
const unsub = consentGate.subscribe((status) => {
|
|
21808
|
+
if (fired) return;
|
|
21809
|
+
if (status !== "granted") return;
|
|
21810
|
+
fired = true;
|
|
21811
|
+
unsub();
|
|
21812
|
+
fire();
|
|
21813
|
+
});
|
|
21814
|
+
}
|
|
21815
|
+
|
|
21816
|
+
// src/defaults/initialProperties.ts
|
|
21817
|
+
var UTM_KEYS = ["utm_source", "utm_medium", "utm_campaign", "utm_content", "utm_term"];
|
|
21818
|
+
function isStrictRegion(geo) {
|
|
21819
|
+
if (geo.is_eu === "true") return true;
|
|
21820
|
+
if (geo.country_code === "GB" || geo.country_code === "CH") return true;
|
|
21821
|
+
if (!geo.is_eu && !geo.country_code) return true;
|
|
21822
|
+
return false;
|
|
21823
|
+
}
|
|
21824
|
+
function safeReferringDomain(referrer) {
|
|
21825
|
+
if (!referrer) return "";
|
|
21826
|
+
try {
|
|
21827
|
+
return new URL(referrer).hostname;
|
|
21828
|
+
} catch {
|
|
21829
|
+
return "";
|
|
21830
|
+
}
|
|
21831
|
+
}
|
|
21832
|
+
function safeOriginPathname() {
|
|
21833
|
+
if (typeof window === "undefined") return "";
|
|
21834
|
+
return `${window.location.origin}${window.location.pathname}`;
|
|
21835
|
+
}
|
|
21836
|
+
function captureInitialProperties(telemetry, geoPromise) {
|
|
21837
|
+
if (typeof window === "undefined" || typeof document === "undefined") return;
|
|
21838
|
+
if (!telemetry.setPersonPropertiesOnce) return;
|
|
21839
|
+
const referrer = document.referrer || "";
|
|
21840
|
+
const params = new URLSearchParams(window.location.search);
|
|
21841
|
+
const props = {
|
|
21842
|
+
$initial_pathname: window.location.pathname,
|
|
21843
|
+
$initial_referrer: referrer,
|
|
21844
|
+
$initial_referring_domain: safeReferringDomain(referrer)
|
|
21845
|
+
};
|
|
21846
|
+
for (const key of UTM_KEYS) {
|
|
21847
|
+
const value = params.get(key);
|
|
21848
|
+
if (value) {
|
|
21849
|
+
props[`$initial_${key}`] = value;
|
|
21850
|
+
}
|
|
21851
|
+
}
|
|
21852
|
+
telemetry.setPersonPropertiesOnce(props);
|
|
21853
|
+
if (!geoPromise) {
|
|
21854
|
+
telemetry.setPersonPropertiesOnce({ $initial_url: safeOriginPathname() });
|
|
21855
|
+
return;
|
|
21856
|
+
}
|
|
21857
|
+
geoPromise.then((geo) => {
|
|
21858
|
+
const url = isStrictRegion(geo) ? safeOriginPathname() : window.location.href;
|
|
21859
|
+
telemetry.setPersonPropertiesOnce?.({ $initial_url: url });
|
|
21860
|
+
}).catch(() => {
|
|
21861
|
+
telemetry.setPersonPropertiesOnce?.({ $initial_url: safeOriginPathname() });
|
|
21862
|
+
});
|
|
21863
|
+
}
|
|
21864
|
+
|
|
21865
|
+
// src/defaults/jsonLd.ts
|
|
21866
|
+
var PII_KEYS = /* @__PURE__ */ new Set([
|
|
21867
|
+
"email",
|
|
21868
|
+
"telephone",
|
|
21869
|
+
"streetAddress",
|
|
21870
|
+
"addressLocality",
|
|
21871
|
+
"addressRegion",
|
|
21872
|
+
"postalCode",
|
|
21873
|
+
"givenName",
|
|
21874
|
+
"familyName"
|
|
21875
|
+
]);
|
|
21876
|
+
var MAX_DESCRIPTION_LENGTH = 500;
|
|
21877
|
+
var MAX_IMAGE_ARRAY_LENGTH = 3;
|
|
21878
|
+
function stripPii(obj) {
|
|
21879
|
+
if (Array.isArray(obj)) return obj.map(stripPii);
|
|
21880
|
+
if (obj && typeof obj === "object") {
|
|
21881
|
+
const out = {};
|
|
21882
|
+
for (const [k4, v4] of Object.entries(obj)) {
|
|
21883
|
+
if (PII_KEYS.has(k4)) continue;
|
|
21884
|
+
out[k4] = stripPii(v4);
|
|
21885
|
+
}
|
|
21886
|
+
return out;
|
|
21887
|
+
}
|
|
21888
|
+
return obj;
|
|
21889
|
+
}
|
|
21890
|
+
function stripBloat(obj) {
|
|
21891
|
+
if (Array.isArray(obj)) return obj.map(stripBloat);
|
|
21892
|
+
if (obj && typeof obj === "object") {
|
|
21893
|
+
const out = {};
|
|
21894
|
+
for (const [k4, v4] of Object.entries(obj)) {
|
|
21895
|
+
if (k4 === "description" && typeof v4 === "string" && v4.length > MAX_DESCRIPTION_LENGTH) {
|
|
21896
|
+
continue;
|
|
21897
|
+
}
|
|
21898
|
+
if (k4 === "image" && Array.isArray(v4) && v4.length > MAX_IMAGE_ARRAY_LENGTH) {
|
|
21899
|
+
out[k4] = v4.slice(0, MAX_IMAGE_ARRAY_LENGTH);
|
|
21900
|
+
continue;
|
|
21901
|
+
}
|
|
21902
|
+
out[k4] = stripBloat(v4);
|
|
21903
|
+
}
|
|
21904
|
+
return out;
|
|
21905
|
+
}
|
|
21906
|
+
return obj;
|
|
21907
|
+
}
|
|
21908
|
+
function extractJsonLdBlocks() {
|
|
21909
|
+
if (typeof document === "undefined") return [];
|
|
21910
|
+
return Array.from(document.querySelectorAll('script[type="application/ld+json"]')).filter((el) => !el.hasAttribute("data-syntro-no-jsonld")).map((el) => {
|
|
21911
|
+
try {
|
|
21912
|
+
return JSON.parse(el.textContent ?? "");
|
|
21913
|
+
} catch {
|
|
21914
|
+
return null;
|
|
21915
|
+
}
|
|
21916
|
+
}).filter((parsed) => parsed !== null);
|
|
21917
|
+
}
|
|
21918
|
+
function extractAndRegisterJsonLd(telemetry) {
|
|
21919
|
+
if (!telemetry.register) return;
|
|
21920
|
+
const blocks = extractJsonLdBlocks().map(stripBloat).map(stripPii);
|
|
21921
|
+
if (blocks.length === 0) return;
|
|
21922
|
+
telemetry.register({ json_ld: blocks });
|
|
21923
|
+
}
|
|
21924
|
+
|
|
21925
|
+
// src/defaults/index.ts
|
|
21926
|
+
function installDefaults(telemetry, consentGate, options = {}) {
|
|
21927
|
+
captureInitialProperties(telemetry, options.geoPromise);
|
|
21928
|
+
promoteClickIds(telemetry);
|
|
21929
|
+
extractAndRegisterJsonLd(telemetry);
|
|
21930
|
+
identifyFromUrlParam(telemetry, consentGate);
|
|
21931
|
+
const teardownEmail = installEmailIdentifyListener(telemetry, consentGate);
|
|
21932
|
+
return () => {
|
|
21933
|
+
teardownEmail();
|
|
21934
|
+
};
|
|
21935
|
+
}
|
|
21936
|
+
|
|
21643
21937
|
// src/events/validation.ts
|
|
21644
21938
|
var APP_PREFIX = "app:";
|
|
21645
21939
|
var RESERVED_PREFIX = "syntro:";
|
|
@@ -27253,6 +27547,344 @@ ${cssRules}
|
|
|
27253
27547
|
return runtime5;
|
|
27254
27548
|
}
|
|
27255
27549
|
|
|
27550
|
+
// src/telemetry/consent/adapters/gtmConsentMode.ts
|
|
27551
|
+
function mapStorage(value) {
|
|
27552
|
+
if (value === "granted") return "granted";
|
|
27553
|
+
if (value === "denied") return "denied";
|
|
27554
|
+
return "pending";
|
|
27555
|
+
}
|
|
27556
|
+
function extractAnalyticsStorage(entry) {
|
|
27557
|
+
if (!entry) return null;
|
|
27558
|
+
if (Array.isArray(entry) && entry[0] === "consent") {
|
|
27559
|
+
const payload = entry[2];
|
|
27560
|
+
return payload?.analytics_storage ?? null;
|
|
27561
|
+
}
|
|
27562
|
+
if (typeof entry === "object" && entry.event === "consent_update") {
|
|
27563
|
+
const payload = entry;
|
|
27564
|
+
return payload.analytics_storage ?? null;
|
|
27565
|
+
}
|
|
27566
|
+
return null;
|
|
27567
|
+
}
|
|
27568
|
+
var GtmConsentModeAdapter = class {
|
|
27569
|
+
constructor() {
|
|
27570
|
+
this.name = "gtm-consent-mode-v2";
|
|
27571
|
+
}
|
|
27572
|
+
detect() {
|
|
27573
|
+
if (typeof window === "undefined") return false;
|
|
27574
|
+
const dl = window.dataLayer;
|
|
27575
|
+
if (!Array.isArray(dl)) return false;
|
|
27576
|
+
for (const entry of dl) {
|
|
27577
|
+
if (extractAnalyticsStorage(entry) !== null) return true;
|
|
27578
|
+
}
|
|
27579
|
+
return false;
|
|
27580
|
+
}
|
|
27581
|
+
initialize(callback) {
|
|
27582
|
+
if (typeof window === "undefined") {
|
|
27583
|
+
callback("pending");
|
|
27584
|
+
return () => {
|
|
27585
|
+
};
|
|
27586
|
+
}
|
|
27587
|
+
if (!Array.isArray(window.dataLayer)) {
|
|
27588
|
+
window.dataLayer = [];
|
|
27589
|
+
}
|
|
27590
|
+
callback(this.readCurrentState());
|
|
27591
|
+
const dl = window.dataLayer;
|
|
27592
|
+
const original = dl.push.bind(dl);
|
|
27593
|
+
const wrappedPush = (...args) => {
|
|
27594
|
+
const result = original(...args);
|
|
27595
|
+
for (const arg of args) {
|
|
27596
|
+
const storage = extractAnalyticsStorage(arg);
|
|
27597
|
+
if (storage !== null) {
|
|
27598
|
+
callback(mapStorage(storage));
|
|
27599
|
+
}
|
|
27600
|
+
}
|
|
27601
|
+
return result;
|
|
27602
|
+
};
|
|
27603
|
+
dl.push = wrappedPush;
|
|
27604
|
+
return () => {
|
|
27605
|
+
if (dl.push === wrappedPush) {
|
|
27606
|
+
dl.push = original;
|
|
27607
|
+
}
|
|
27608
|
+
};
|
|
27609
|
+
}
|
|
27610
|
+
readCurrentState() {
|
|
27611
|
+
if (typeof window === "undefined") return "pending";
|
|
27612
|
+
const dl = window.dataLayer;
|
|
27613
|
+
if (!Array.isArray(dl)) return "pending";
|
|
27614
|
+
for (let i9 = dl.length - 1; i9 >= 0; i9--) {
|
|
27615
|
+
const storage = extractAnalyticsStorage(dl[i9]);
|
|
27616
|
+
if (storage !== null) return mapStorage(storage);
|
|
27617
|
+
}
|
|
27618
|
+
return "pending";
|
|
27619
|
+
}
|
|
27620
|
+
};
|
|
27621
|
+
|
|
27622
|
+
// src/telemetry/consent/adapters/iabTcf.ts
|
|
27623
|
+
var PURPOSE_STORAGE = "1";
|
|
27624
|
+
var PURPOSE_ANALYTICS = "8";
|
|
27625
|
+
var IabTcfAdapter = class {
|
|
27626
|
+
constructor() {
|
|
27627
|
+
this.name = "iab-tcf-v2";
|
|
27628
|
+
}
|
|
27629
|
+
detect() {
|
|
27630
|
+
if (typeof window === "undefined") return false;
|
|
27631
|
+
return typeof window.__tcfapi === "function";
|
|
27632
|
+
}
|
|
27633
|
+
initialize(callback) {
|
|
27634
|
+
callback("pending");
|
|
27635
|
+
if (typeof window === "undefined") return () => {
|
|
27636
|
+
};
|
|
27637
|
+
const tcfapi = window.__tcfapi;
|
|
27638
|
+
if (typeof tcfapi !== "function") return () => {
|
|
27639
|
+
};
|
|
27640
|
+
let listenerId;
|
|
27641
|
+
tcfapi("addEventListener", 2, (tcData, success) => {
|
|
27642
|
+
if (!success || !tcData) return;
|
|
27643
|
+
if (listenerId === void 0 && typeof tcData.listenerId === "number") {
|
|
27644
|
+
listenerId = tcData.listenerId;
|
|
27645
|
+
}
|
|
27646
|
+
const ready = tcData.eventStatus === "tcloaded" || tcData.eventStatus === "useractioncomplete";
|
|
27647
|
+
if (!ready) return;
|
|
27648
|
+
if (tcData.gdprApplies === false) {
|
|
27649
|
+
callback("granted");
|
|
27650
|
+
return;
|
|
27651
|
+
}
|
|
27652
|
+
const consents = tcData.purpose?.consents ?? {};
|
|
27653
|
+
const p1 = consents[PURPOSE_STORAGE] === true;
|
|
27654
|
+
const p8 = consents[PURPOSE_ANALYTICS] === true;
|
|
27655
|
+
callback(p1 && p8 ? "granted" : "denied");
|
|
27656
|
+
});
|
|
27657
|
+
return () => {
|
|
27658
|
+
if (listenerId !== void 0) {
|
|
27659
|
+
try {
|
|
27660
|
+
tcfapi("removeEventListener", 2, () => {
|
|
27661
|
+
}, listenerId);
|
|
27662
|
+
} catch {
|
|
27663
|
+
}
|
|
27664
|
+
}
|
|
27665
|
+
};
|
|
27666
|
+
}
|
|
27667
|
+
};
|
|
27668
|
+
|
|
27669
|
+
// src/telemetry/consent/adapters/shopify.ts
|
|
27670
|
+
var VISITOR_CONSENT_EVENT = "visitorConsentCollected";
|
|
27671
|
+
var ShopifyCustomerPrivacyAdapter = class {
|
|
27672
|
+
constructor() {
|
|
27673
|
+
this.name = "shopify-customer-privacy";
|
|
27674
|
+
}
|
|
27675
|
+
detect() {
|
|
27676
|
+
if (typeof window === "undefined") return false;
|
|
27677
|
+
const shopify = window.Shopify;
|
|
27678
|
+
return typeof shopify?.loadFeatures === "function";
|
|
27679
|
+
}
|
|
27680
|
+
initialize(callback) {
|
|
27681
|
+
callback("pending");
|
|
27682
|
+
if (typeof window === "undefined") return () => {
|
|
27683
|
+
};
|
|
27684
|
+
const shopify = window.Shopify;
|
|
27685
|
+
if (!shopify?.loadFeatures) return () => {
|
|
27686
|
+
};
|
|
27687
|
+
let visitorEventHandler;
|
|
27688
|
+
shopify.loadFeatures([{ name: "consent-tracking-api", version: "0.1" }], (error2) => {
|
|
27689
|
+
if (error2) {
|
|
27690
|
+
return;
|
|
27691
|
+
}
|
|
27692
|
+
const allowed = shopify.customerPrivacy?.userCanBeTracked() ?? true;
|
|
27693
|
+
callback(allowed ? "granted" : "denied");
|
|
27694
|
+
visitorEventHandler = (event) => {
|
|
27695
|
+
const detail = event.detail;
|
|
27696
|
+
callback(detail?.analyticsAllowed ? "granted" : "denied");
|
|
27697
|
+
};
|
|
27698
|
+
document.addEventListener(VISITOR_CONSENT_EVENT, visitorEventHandler);
|
|
27699
|
+
});
|
|
27700
|
+
return () => {
|
|
27701
|
+
if (visitorEventHandler) {
|
|
27702
|
+
document.removeEventListener(VISITOR_CONSENT_EVENT, visitorEventHandler);
|
|
27703
|
+
}
|
|
27704
|
+
};
|
|
27705
|
+
}
|
|
27706
|
+
};
|
|
27707
|
+
|
|
27708
|
+
// src/telemetry/consent/ConsentDetector.ts
|
|
27709
|
+
var ConsentDetector = class {
|
|
27710
|
+
constructor(options) {
|
|
27711
|
+
this.options = options;
|
|
27712
|
+
}
|
|
27713
|
+
/**
|
|
27714
|
+
* Start the detector. Probes adapters in array order; first match wins.
|
|
27715
|
+
* Returns a teardown function that unsubscribes the active adapter.
|
|
27716
|
+
*
|
|
27717
|
+
* Async because adapters may need async initialization. Bootstrap
|
|
27718
|
+
* should NOT await this — feature flags and anonymous telemetry
|
|
27719
|
+
* should keep flowing while the detector probes.
|
|
27720
|
+
*/
|
|
27721
|
+
async start(gate) {
|
|
27722
|
+
for (const adapter of this.options.adapters) {
|
|
27723
|
+
let detected = false;
|
|
27724
|
+
try {
|
|
27725
|
+
detected = adapter.detect();
|
|
27726
|
+
} catch {
|
|
27727
|
+
continue;
|
|
27728
|
+
}
|
|
27729
|
+
if (!detected) continue;
|
|
27730
|
+
try {
|
|
27731
|
+
const teardown = adapter.initialize((status) => gate.setStatus(status));
|
|
27732
|
+
this.options.onAdapterSelected?.(adapter.name);
|
|
27733
|
+
return teardown;
|
|
27734
|
+
} catch (err) {
|
|
27735
|
+
console.warn(`[Syntro consent] Adapter "${adapter.name}" failed:`, err);
|
|
27736
|
+
}
|
|
27737
|
+
}
|
|
27738
|
+
this.options.onAdapterSelected?.(null);
|
|
27739
|
+
const fallback = await this.resolveFallback();
|
|
27740
|
+
gate.setStatus(fallback);
|
|
27741
|
+
return () => {
|
|
27742
|
+
};
|
|
27743
|
+
}
|
|
27744
|
+
async resolveFallback() {
|
|
27745
|
+
if (!this.options.fallbackStatus) return "pending";
|
|
27746
|
+
try {
|
|
27747
|
+
return await this.options.fallbackStatus();
|
|
27748
|
+
} catch {
|
|
27749
|
+
return "pending";
|
|
27750
|
+
}
|
|
27751
|
+
}
|
|
27752
|
+
};
|
|
27753
|
+
|
|
27754
|
+
// src/telemetry/consent/ConsentGate.ts
|
|
27755
|
+
var ConsentGate = class {
|
|
27756
|
+
constructor(config = {}) {
|
|
27757
|
+
this.listeners = /* @__PURE__ */ new Set();
|
|
27758
|
+
this.destroyed = false;
|
|
27759
|
+
this.status = config.initialStatus ?? "pending";
|
|
27760
|
+
this.configCallback = config.onConsentChange;
|
|
27761
|
+
this.gtmEnabled = config.readFromGtmConsentMode ?? false;
|
|
27762
|
+
this.pendingResolverFn = config.pendingResolver;
|
|
27763
|
+
}
|
|
27764
|
+
/**
|
|
27765
|
+
* Grant consent — enables telemetry collection.
|
|
27766
|
+
*/
|
|
27767
|
+
grant() {
|
|
27768
|
+
this.setStatus("granted");
|
|
27769
|
+
}
|
|
27770
|
+
/**
|
|
27771
|
+
* Deny consent — disables telemetry collection.
|
|
27772
|
+
*/
|
|
27773
|
+
deny() {
|
|
27774
|
+
this.setStatus("denied");
|
|
27775
|
+
}
|
|
27776
|
+
/**
|
|
27777
|
+
* Get the current consent status.
|
|
27778
|
+
*/
|
|
27779
|
+
getStatus() {
|
|
27780
|
+
return this.status;
|
|
27781
|
+
}
|
|
27782
|
+
/**
|
|
27783
|
+
* Subscribe to consent status changes.
|
|
27784
|
+
* Returns an unsubscribe function.
|
|
27785
|
+
*/
|
|
27786
|
+
subscribe(listener) {
|
|
27787
|
+
this.listeners.add(listener);
|
|
27788
|
+
return () => {
|
|
27789
|
+
this.listeners.delete(listener);
|
|
27790
|
+
};
|
|
27791
|
+
}
|
|
27792
|
+
/**
|
|
27793
|
+
* Subscribe to the next definitive (granted | denied) status change,
|
|
27794
|
+
* then auto-unsubscribe. If status is already definitive, fires
|
|
27795
|
+
* synchronously (next microtask) with current status.
|
|
27796
|
+
*
|
|
27797
|
+
* Used by D-2 (URL-param identify) to defer the identify() call
|
|
27798
|
+
* until consent is decided.
|
|
27799
|
+
*/
|
|
27800
|
+
subscribeOnce(listener) {
|
|
27801
|
+
if (this.status !== "pending") {
|
|
27802
|
+
queueMicrotask(() => listener(this.status));
|
|
27803
|
+
return () => {
|
|
27804
|
+
};
|
|
27805
|
+
}
|
|
27806
|
+
const unsub = this.subscribe((status) => {
|
|
27807
|
+
if (status === "pending") return;
|
|
27808
|
+
unsub();
|
|
27809
|
+
listener(status);
|
|
27810
|
+
});
|
|
27811
|
+
return unsub;
|
|
27812
|
+
}
|
|
27813
|
+
/**
|
|
27814
|
+
* Resolve what the current 'pending' state should be treated as,
|
|
27815
|
+
* via the configured pendingResolver. Returns the current status
|
|
27816
|
+
* directly if it's already 'granted' or 'denied'.
|
|
27817
|
+
*
|
|
27818
|
+
* Defaults to 'denied' if no resolver is configured (conservative).
|
|
27819
|
+
*/
|
|
27820
|
+
async resolvePending() {
|
|
27821
|
+
if (this.status !== "pending") return this.status;
|
|
27822
|
+
if (!this.pendingResolverFn) return "denied";
|
|
27823
|
+
try {
|
|
27824
|
+
return await this.pendingResolverFn();
|
|
27825
|
+
} catch {
|
|
27826
|
+
return "denied";
|
|
27827
|
+
}
|
|
27828
|
+
}
|
|
27829
|
+
/**
|
|
27830
|
+
* Public setter — used by ConsentDetector / adapters to push state changes.
|
|
27831
|
+
*/
|
|
27832
|
+
setStatus(newStatus) {
|
|
27833
|
+
this.applyStatus(newStatus);
|
|
27834
|
+
}
|
|
27835
|
+
/**
|
|
27836
|
+
* Poll GTM's dataLayer for consent state.
|
|
27837
|
+
* Scans for the most recent consent update event with analytics_storage.
|
|
27838
|
+
*/
|
|
27839
|
+
pollGtmConsent() {
|
|
27840
|
+
if (!this.gtmEnabled || typeof window === "undefined") return;
|
|
27841
|
+
const dataLayer = window.dataLayer;
|
|
27842
|
+
if (!Array.isArray(dataLayer)) return;
|
|
27843
|
+
for (let i9 = dataLayer.length - 1; i9 >= 0; i9--) {
|
|
27844
|
+
const entry = dataLayer[i9];
|
|
27845
|
+
if (!entry) continue;
|
|
27846
|
+
const isConsentUpdate = entry[0] === "consent" && entry[1] === "update" && entry[2];
|
|
27847
|
+
if (isConsentUpdate) {
|
|
27848
|
+
const storage = entry[2].analytics_storage;
|
|
27849
|
+
if (storage === "granted") {
|
|
27850
|
+
this.setStatus("granted");
|
|
27851
|
+
} else if (storage === "denied") {
|
|
27852
|
+
this.setStatus("denied");
|
|
27853
|
+
}
|
|
27854
|
+
return;
|
|
27855
|
+
}
|
|
27856
|
+
}
|
|
27857
|
+
}
|
|
27858
|
+
/**
|
|
27859
|
+
* Clean up listeners and stop responding to changes.
|
|
27860
|
+
*/
|
|
27861
|
+
destroy() {
|
|
27862
|
+
this.destroyed = true;
|
|
27863
|
+
this.listeners.clear();
|
|
27864
|
+
}
|
|
27865
|
+
// ─── Private ─────────────────────────────────────────────────────
|
|
27866
|
+
applyStatus(newStatus) {
|
|
27867
|
+
if (this.destroyed) return;
|
|
27868
|
+
if (this.status === newStatus) return;
|
|
27869
|
+
this.status = newStatus;
|
|
27870
|
+
this.notify(newStatus);
|
|
27871
|
+
}
|
|
27872
|
+
notify(status) {
|
|
27873
|
+
if (this.configCallback) {
|
|
27874
|
+
try {
|
|
27875
|
+
this.configCallback(status);
|
|
27876
|
+
} catch {
|
|
27877
|
+
}
|
|
27878
|
+
}
|
|
27879
|
+
for (const listener of this.listeners) {
|
|
27880
|
+
try {
|
|
27881
|
+
listener(status);
|
|
27882
|
+
} catch {
|
|
27883
|
+
}
|
|
27884
|
+
}
|
|
27885
|
+
}
|
|
27886
|
+
};
|
|
27887
|
+
|
|
27256
27888
|
// src/telemetry/InterventionTracker.ts
|
|
27257
27889
|
var InterventionTracker = class {
|
|
27258
27890
|
constructor(telemetry, variantId) {
|
|
@@ -27348,6 +27980,14 @@ ${cssRules}
|
|
|
27348
27980
|
}
|
|
27349
27981
|
track(_eventName, _properties) {
|
|
27350
27982
|
}
|
|
27983
|
+
identify(_distinctId, _properties) {
|
|
27984
|
+
}
|
|
27985
|
+
alias(_newDistinctId, _oldDistinctId) {
|
|
27986
|
+
}
|
|
27987
|
+
optInCapturing() {
|
|
27988
|
+
}
|
|
27989
|
+
optOutCapturing() {
|
|
27990
|
+
}
|
|
27351
27991
|
};
|
|
27352
27992
|
function createNoopClient() {
|
|
27353
27993
|
return new NoopAdapter();
|
|
@@ -32286,6 +32926,9 @@ ${cssRules}
|
|
|
32286
32926
|
// Enable web vitals
|
|
32287
32927
|
enable_recording_console_log: true
|
|
32288
32928
|
};
|
|
32929
|
+
if (options.cookieless_mode) {
|
|
32930
|
+
initOptions.cookieless_mode = options.cookieless_mode;
|
|
32931
|
+
}
|
|
32289
32932
|
const result = Jo.init(
|
|
32290
32933
|
options.apiKey,
|
|
32291
32934
|
initOptions,
|
|
@@ -32390,6 +33033,22 @@ ${cssRules}
|
|
|
32390
33033
|
alias(id, aliasId) {
|
|
32391
33034
|
this.client?.alias(id, aliasId);
|
|
32392
33035
|
}
|
|
33036
|
+
/**
|
|
33037
|
+
* Opt the current visitor INTO capturing. Drives the granted-state
|
|
33038
|
+
* transition from the consent gate. When previously cookieless or
|
|
33039
|
+
* opted-out, switches to full identified capture.
|
|
33040
|
+
*/
|
|
33041
|
+
optInCapturing() {
|
|
33042
|
+
this.client?.opt_in_capturing();
|
|
33043
|
+
}
|
|
33044
|
+
/**
|
|
33045
|
+
* Opt the current visitor OUT of capturing. Drives the denied-state
|
|
33046
|
+
* transition from the consent gate. Stops sending events; existing
|
|
33047
|
+
* cookieless/anonymous events remain in the data warehouse.
|
|
33048
|
+
*/
|
|
33049
|
+
optOutCapturing() {
|
|
33050
|
+
this.client?.opt_out_capturing();
|
|
33051
|
+
}
|
|
32393
33052
|
track(eventName, payload) {
|
|
32394
33053
|
this.client?.capture(eventName, payload);
|
|
32395
33054
|
}
|
|
@@ -32648,6 +33307,11 @@ ${cssRules}
|
|
|
32648
33307
|
// Enable PostHog feature flags for segment membership
|
|
32649
33308
|
enableFeatureFlags: true,
|
|
32650
33309
|
sessionRecording: true,
|
|
33310
|
+
// Cookieless mode: anonymous baseline until consent decision.
|
|
33311
|
+
// PostHog captures behavioral data with privacy-preserving session
|
|
33312
|
+
// hashes during pending; switches to cookied tracking on grant;
|
|
33313
|
+
// continues cookielessly on reject.
|
|
33314
|
+
cookieless_mode: "on_reject",
|
|
32651
33315
|
// Wire up callback for when flags are loaded (Phase 2)
|
|
32652
33316
|
onFeatureFlagsLoaded,
|
|
32653
33317
|
// Wire up event capture to feed into event processor
|
|
@@ -32668,6 +33332,51 @@ ${cssRules}
|
|
|
32668
33332
|
events.setPosthogCapture((name, props) => {
|
|
32669
33333
|
telemetryForCapture.track?.(name, props);
|
|
32670
33334
|
});
|
|
33335
|
+
const telemetryForGate = telemetry;
|
|
33336
|
+
const resolveGeoStatus = async () => {
|
|
33337
|
+
const geo = await geoPromise;
|
|
33338
|
+
const isStrictRegion2 = geo.is_eu === "true" || geo.country_code === "GB" || geo.country_code === "CH";
|
|
33339
|
+
return isStrictRegion2 ? "denied" : "granted";
|
|
33340
|
+
};
|
|
33341
|
+
const consentGate = new ConsentGate({
|
|
33342
|
+
initialStatus: "pending",
|
|
33343
|
+
pendingResolver: resolveGeoStatus,
|
|
33344
|
+
// Drive PostHog opt-in/opt-out from gate transitions.
|
|
33345
|
+
onConsentChange: (status) => {
|
|
33346
|
+
if (status === "granted") {
|
|
33347
|
+
telemetryForGate.optInCapturing?.();
|
|
33348
|
+
} else if (status === "denied") {
|
|
33349
|
+
telemetryForGate.optOutCapturing?.();
|
|
33350
|
+
}
|
|
33351
|
+
}
|
|
33352
|
+
});
|
|
33353
|
+
try {
|
|
33354
|
+
installDefaults(telemetry, consentGate, { geoPromise });
|
|
33355
|
+
debug("Syntro Bootstrap", "SDK defaults installed (D-1..D-5) with consent gate");
|
|
33356
|
+
} catch (err) {
|
|
33357
|
+
warn("Syntro Bootstrap", "installDefaults failed", err);
|
|
33358
|
+
}
|
|
33359
|
+
try {
|
|
33360
|
+
const detector = new ConsentDetector({
|
|
33361
|
+
adapters: [
|
|
33362
|
+
new ShopifyCustomerPrivacyAdapter(),
|
|
33363
|
+
new IabTcfAdapter(),
|
|
33364
|
+
new GtmConsentModeAdapter()
|
|
33365
|
+
],
|
|
33366
|
+
fallbackStatus: resolveGeoStatus,
|
|
33367
|
+
onAdapterSelected: (name) => {
|
|
33368
|
+
telemetryForGate.track?.("syntro_consent_adapter_selected", {
|
|
33369
|
+
adapter: name ?? "none"
|
|
33370
|
+
});
|
|
33371
|
+
debug("Syntro Bootstrap", `Consent adapter selected: ${name ?? "none"}`);
|
|
33372
|
+
}
|
|
33373
|
+
});
|
|
33374
|
+
detector.start(consentGate).catch((err) => {
|
|
33375
|
+
warn("Syntro Bootstrap", "Consent detector failed to start", err);
|
|
33376
|
+
});
|
|
33377
|
+
} catch (err) {
|
|
33378
|
+
warn("Syntro Bootstrap", "Consent detector setup failed", err);
|
|
33379
|
+
}
|
|
32671
33380
|
if (platformAdapter?.name === "shopify" && telemetryHost) {
|
|
32672
33381
|
try {
|
|
32673
33382
|
shopifyPixelBridge = new ShopifyPixelBridge({
|
|
@@ -33381,7 +34090,7 @@ ${cssRules}
|
|
|
33381
34090
|
}
|
|
33382
34091
|
|
|
33383
34092
|
// src/index.ts
|
|
33384
|
-
var RUNTIME_SDK_BUILD = true ? `${"2026-05-
|
|
34093
|
+
var RUNTIME_SDK_BUILD = true ? `${"2026-05-05T03:23:35.905Z"} (${"b84ed35ba47"})` : "dev";
|
|
33385
34094
|
if (typeof window !== "undefined") {
|
|
33386
34095
|
console.log(`[Syntro Runtime] Build: ${RUNTIME_SDK_BUILD} (Lit)`);
|
|
33387
34096
|
const existing = window.SynOS;
|