@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/index.js
CHANGED
|
@@ -9577,7 +9577,7 @@ function error(prefix, message, data) {
|
|
|
9577
9577
|
}
|
|
9578
9578
|
|
|
9579
9579
|
// src/version.ts
|
|
9580
|
-
var SDK_VERSION = "2.8.0-canary.
|
|
9580
|
+
var SDK_VERSION = "2.8.0-canary.185";
|
|
9581
9581
|
|
|
9582
9582
|
// src/types.ts
|
|
9583
9583
|
var SDK_SCHEMA_VERSION = "2.0";
|
|
@@ -13916,6 +13916,306 @@ function getDerivedCdnBase() {
|
|
|
13916
13916
|
return derivedCdnBase;
|
|
13917
13917
|
}
|
|
13918
13918
|
|
|
13919
|
+
// src/defaults/clickIds.ts
|
|
13920
|
+
var CLICK_ID_KEYS = [
|
|
13921
|
+
"fbclid",
|
|
13922
|
+
// Meta (Facebook + Instagram)
|
|
13923
|
+
"ttclid",
|
|
13924
|
+
// TikTok
|
|
13925
|
+
"gclid",
|
|
13926
|
+
// Google Ads
|
|
13927
|
+
"gbraid",
|
|
13928
|
+
// Google Ads (iOS app web)
|
|
13929
|
+
"wbraid",
|
|
13930
|
+
// Google Ads (web app)
|
|
13931
|
+
"msclkid",
|
|
13932
|
+
// Microsoft / Bing Ads
|
|
13933
|
+
"twclid",
|
|
13934
|
+
// X / Twitter
|
|
13935
|
+
"li_fat_id",
|
|
13936
|
+
// LinkedIn
|
|
13937
|
+
"epik",
|
|
13938
|
+
// Pinterest
|
|
13939
|
+
"ScCid",
|
|
13940
|
+
// Snapchat
|
|
13941
|
+
"yclid",
|
|
13942
|
+
// Yandex
|
|
13943
|
+
"_kx",
|
|
13944
|
+
// Klaviyo
|
|
13945
|
+
"mc_eid",
|
|
13946
|
+
// Mailchimp recipient
|
|
13947
|
+
"mc_cid"
|
|
13948
|
+
// Mailchimp campaign
|
|
13949
|
+
];
|
|
13950
|
+
function promoteClickIds(telemetry) {
|
|
13951
|
+
if (typeof window === "undefined") return;
|
|
13952
|
+
if (!telemetry.setPersonPropertiesOnce) return;
|
|
13953
|
+
const params = new URLSearchParams(window.location.search);
|
|
13954
|
+
const captured = {};
|
|
13955
|
+
for (const key of CLICK_ID_KEYS) {
|
|
13956
|
+
const value = params.get(key);
|
|
13957
|
+
if (value) {
|
|
13958
|
+
captured[`$initial_${key}`] = value;
|
|
13959
|
+
}
|
|
13960
|
+
}
|
|
13961
|
+
if (Object.keys(captured).length > 0) {
|
|
13962
|
+
telemetry.setPersonPropertiesOnce(captured);
|
|
13963
|
+
}
|
|
13964
|
+
}
|
|
13965
|
+
|
|
13966
|
+
// src/defaults/identifyOnEmail.ts
|
|
13967
|
+
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;
|
|
13968
|
+
var EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
13969
|
+
var DENYLIST_PATTERNS = [
|
|
13970
|
+
/@yopmail\.com$/i,
|
|
13971
|
+
/@mailinator\.com$/i,
|
|
13972
|
+
/@guerrillamail\.(com|info|net|org|biz)$/i,
|
|
13973
|
+
/@10minutemail\.com$/i,
|
|
13974
|
+
/^test@/i,
|
|
13975
|
+
/^noreply@/i,
|
|
13976
|
+
/^no-reply@/i
|
|
13977
|
+
];
|
|
13978
|
+
function isValidEmail(value) {
|
|
13979
|
+
if (!value) return false;
|
|
13980
|
+
return EMAIL_RE.test(value);
|
|
13981
|
+
}
|
|
13982
|
+
function isEmailDenylisted(value) {
|
|
13983
|
+
return DENYLIST_PATTERNS.some((re2) => re2.test(value));
|
|
13984
|
+
}
|
|
13985
|
+
function isEmailLikeInput(el) {
|
|
13986
|
+
if (el.type === "email") return true;
|
|
13987
|
+
const name = (el.name || "").toLowerCase();
|
|
13988
|
+
const id = (el.id || "").toLowerCase();
|
|
13989
|
+
if (/email/.test(name) || /email/.test(id)) return true;
|
|
13990
|
+
return false;
|
|
13991
|
+
}
|
|
13992
|
+
function isOptedOut(el) {
|
|
13993
|
+
return el.closest("[data-syntro-no-identify]") !== null;
|
|
13994
|
+
}
|
|
13995
|
+
function isAnonymous(distinctId) {
|
|
13996
|
+
if (!distinctId) return true;
|
|
13997
|
+
return ANONYMOUS_UUID_RE.test(distinctId);
|
|
13998
|
+
}
|
|
13999
|
+
function installEmailIdentifyListener(telemetry, consentGate) {
|
|
14000
|
+
if (typeof document === "undefined") return () => {
|
|
14001
|
+
};
|
|
14002
|
+
if (!telemetry.identify && !telemetry.alias) return () => {
|
|
14003
|
+
};
|
|
14004
|
+
const seenThisSession = /* @__PURE__ */ new Set();
|
|
14005
|
+
const fireIdentifyOrAlias = (value) => {
|
|
14006
|
+
var _a3, _b, _c;
|
|
14007
|
+
const currentId = (_a3 = telemetry.getDistinctId) == null ? void 0 : _a3.call(telemetry);
|
|
14008
|
+
if (currentId === value) {
|
|
14009
|
+
seenThisSession.add(value);
|
|
14010
|
+
return;
|
|
14011
|
+
}
|
|
14012
|
+
if (isAnonymous(currentId)) {
|
|
14013
|
+
(_b = telemetry.identify) == null ? void 0 : _b.call(telemetry, value, { email: value });
|
|
14014
|
+
} else {
|
|
14015
|
+
(_c = telemetry.alias) == null ? void 0 : _c.call(telemetry, value);
|
|
14016
|
+
}
|
|
14017
|
+
seenThisSession.add(value);
|
|
14018
|
+
};
|
|
14019
|
+
const handler = (e) => {
|
|
14020
|
+
var _a3;
|
|
14021
|
+
const target = e.target;
|
|
14022
|
+
if (!(target instanceof HTMLInputElement)) return;
|
|
14023
|
+
if (isOptedOut(target)) return;
|
|
14024
|
+
if (!isEmailLikeInput(target)) return;
|
|
14025
|
+
const value = ((_a3 = target.value) != null ? _a3 : "").trim().toLowerCase();
|
|
14026
|
+
if (!isValidEmail(value)) return;
|
|
14027
|
+
if (isEmailDenylisted(value)) return;
|
|
14028
|
+
if (seenThisSession.has(value)) return;
|
|
14029
|
+
if (!consentGate) {
|
|
14030
|
+
fireIdentifyOrAlias(value);
|
|
14031
|
+
return;
|
|
14032
|
+
}
|
|
14033
|
+
const status = consentGate.getStatus();
|
|
14034
|
+
if (status === "granted") {
|
|
14035
|
+
fireIdentifyOrAlias(value);
|
|
14036
|
+
return;
|
|
14037
|
+
}
|
|
14038
|
+
if (status === "denied") {
|
|
14039
|
+
return;
|
|
14040
|
+
}
|
|
14041
|
+
consentGate.resolvePending().then((resolved) => {
|
|
14042
|
+
if (resolved === "granted") fireIdentifyOrAlias(value);
|
|
14043
|
+
});
|
|
14044
|
+
};
|
|
14045
|
+
document.addEventListener("change", handler, true);
|
|
14046
|
+
return () => {
|
|
14047
|
+
document.removeEventListener("change", handler, true);
|
|
14048
|
+
seenThisSession.clear();
|
|
14049
|
+
};
|
|
14050
|
+
}
|
|
14051
|
+
|
|
14052
|
+
// src/defaults/identifyOnUrlParam.ts
|
|
14053
|
+
var SYNTRO_UID_PARAM = "_syu";
|
|
14054
|
+
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;
|
|
14055
|
+
function isAnonymous2(distinctId) {
|
|
14056
|
+
if (!distinctId) return true;
|
|
14057
|
+
return ANONYMOUS_UUID_RE2.test(distinctId);
|
|
14058
|
+
}
|
|
14059
|
+
function identifyFromUrlParam(telemetry, consentGate) {
|
|
14060
|
+
if (typeof window === "undefined") return;
|
|
14061
|
+
if (!telemetry.identify && !telemetry.alias) return;
|
|
14062
|
+
const value = new URLSearchParams(window.location.search).get(SYNTRO_UID_PARAM);
|
|
14063
|
+
if (!value) return;
|
|
14064
|
+
const trimmed = value.trim();
|
|
14065
|
+
if (!trimmed) return;
|
|
14066
|
+
const fire = () => {
|
|
14067
|
+
var _a3, _b, _c;
|
|
14068
|
+
const currentId = (_a3 = telemetry.getDistinctId) == null ? void 0 : _a3.call(telemetry);
|
|
14069
|
+
if (currentId === trimmed) return;
|
|
14070
|
+
const properties = isValidEmail(trimmed) ? { email: trimmed } : void 0;
|
|
14071
|
+
if (isAnonymous2(currentId)) {
|
|
14072
|
+
(_b = telemetry.identify) == null ? void 0 : _b.call(telemetry, trimmed, properties);
|
|
14073
|
+
} else {
|
|
14074
|
+
(_c = telemetry.alias) == null ? void 0 : _c.call(telemetry, trimmed);
|
|
14075
|
+
}
|
|
14076
|
+
};
|
|
14077
|
+
if (!consentGate) {
|
|
14078
|
+
fire();
|
|
14079
|
+
return;
|
|
14080
|
+
}
|
|
14081
|
+
if (consentGate.getStatus() === "granted") {
|
|
14082
|
+
fire();
|
|
14083
|
+
return;
|
|
14084
|
+
}
|
|
14085
|
+
let fired = false;
|
|
14086
|
+
const unsub = consentGate.subscribe((status) => {
|
|
14087
|
+
if (fired) return;
|
|
14088
|
+
if (status !== "granted") return;
|
|
14089
|
+
fired = true;
|
|
14090
|
+
unsub();
|
|
14091
|
+
fire();
|
|
14092
|
+
});
|
|
14093
|
+
}
|
|
14094
|
+
|
|
14095
|
+
// src/defaults/initialProperties.ts
|
|
14096
|
+
var UTM_KEYS = ["utm_source", "utm_medium", "utm_campaign", "utm_content", "utm_term"];
|
|
14097
|
+
function isStrictRegion(geo) {
|
|
14098
|
+
if (geo.is_eu === "true") return true;
|
|
14099
|
+
if (geo.country_code === "GB" || geo.country_code === "CH") return true;
|
|
14100
|
+
if (!geo.is_eu && !geo.country_code) return true;
|
|
14101
|
+
return false;
|
|
14102
|
+
}
|
|
14103
|
+
function safeReferringDomain(referrer) {
|
|
14104
|
+
if (!referrer) return "";
|
|
14105
|
+
try {
|
|
14106
|
+
return new URL(referrer).hostname;
|
|
14107
|
+
} catch {
|
|
14108
|
+
return "";
|
|
14109
|
+
}
|
|
14110
|
+
}
|
|
14111
|
+
function safeOriginPathname() {
|
|
14112
|
+
if (typeof window === "undefined") return "";
|
|
14113
|
+
return `${window.location.origin}${window.location.pathname}`;
|
|
14114
|
+
}
|
|
14115
|
+
function captureInitialProperties(telemetry, geoPromise) {
|
|
14116
|
+
if (typeof window === "undefined" || typeof document === "undefined") return;
|
|
14117
|
+
if (!telemetry.setPersonPropertiesOnce) return;
|
|
14118
|
+
const referrer = document.referrer || "";
|
|
14119
|
+
const params = new URLSearchParams(window.location.search);
|
|
14120
|
+
const props = {
|
|
14121
|
+
$initial_pathname: window.location.pathname,
|
|
14122
|
+
$initial_referrer: referrer,
|
|
14123
|
+
$initial_referring_domain: safeReferringDomain(referrer)
|
|
14124
|
+
};
|
|
14125
|
+
for (const key of UTM_KEYS) {
|
|
14126
|
+
const value = params.get(key);
|
|
14127
|
+
if (value) {
|
|
14128
|
+
props[`$initial_${key}`] = value;
|
|
14129
|
+
}
|
|
14130
|
+
}
|
|
14131
|
+
telemetry.setPersonPropertiesOnce(props);
|
|
14132
|
+
if (!geoPromise) {
|
|
14133
|
+
telemetry.setPersonPropertiesOnce({ $initial_url: safeOriginPathname() });
|
|
14134
|
+
return;
|
|
14135
|
+
}
|
|
14136
|
+
geoPromise.then((geo) => {
|
|
14137
|
+
var _a3;
|
|
14138
|
+
const url = isStrictRegion(geo) ? safeOriginPathname() : window.location.href;
|
|
14139
|
+
(_a3 = telemetry.setPersonPropertiesOnce) == null ? void 0 : _a3.call(telemetry, { $initial_url: url });
|
|
14140
|
+
}).catch(() => {
|
|
14141
|
+
var _a3;
|
|
14142
|
+
(_a3 = telemetry.setPersonPropertiesOnce) == null ? void 0 : _a3.call(telemetry, { $initial_url: safeOriginPathname() });
|
|
14143
|
+
});
|
|
14144
|
+
}
|
|
14145
|
+
|
|
14146
|
+
// src/defaults/jsonLd.ts
|
|
14147
|
+
var PII_KEYS = /* @__PURE__ */ new Set([
|
|
14148
|
+
"email",
|
|
14149
|
+
"telephone",
|
|
14150
|
+
"streetAddress",
|
|
14151
|
+
"addressLocality",
|
|
14152
|
+
"addressRegion",
|
|
14153
|
+
"postalCode",
|
|
14154
|
+
"givenName",
|
|
14155
|
+
"familyName"
|
|
14156
|
+
]);
|
|
14157
|
+
var MAX_DESCRIPTION_LENGTH = 500;
|
|
14158
|
+
var MAX_IMAGE_ARRAY_LENGTH = 3;
|
|
14159
|
+
function stripPii(obj) {
|
|
14160
|
+
if (Array.isArray(obj)) return obj.map(stripPii);
|
|
14161
|
+
if (obj && typeof obj === "object") {
|
|
14162
|
+
const out = {};
|
|
14163
|
+
for (const [k2, v2] of Object.entries(obj)) {
|
|
14164
|
+
if (PII_KEYS.has(k2)) continue;
|
|
14165
|
+
out[k2] = stripPii(v2);
|
|
14166
|
+
}
|
|
14167
|
+
return out;
|
|
14168
|
+
}
|
|
14169
|
+
return obj;
|
|
14170
|
+
}
|
|
14171
|
+
function stripBloat(obj) {
|
|
14172
|
+
if (Array.isArray(obj)) return obj.map(stripBloat);
|
|
14173
|
+
if (obj && typeof obj === "object") {
|
|
14174
|
+
const out = {};
|
|
14175
|
+
for (const [k2, v2] of Object.entries(obj)) {
|
|
14176
|
+
if (k2 === "description" && typeof v2 === "string" && v2.length > MAX_DESCRIPTION_LENGTH) {
|
|
14177
|
+
continue;
|
|
14178
|
+
}
|
|
14179
|
+
if (k2 === "image" && Array.isArray(v2) && v2.length > MAX_IMAGE_ARRAY_LENGTH) {
|
|
14180
|
+
out[k2] = v2.slice(0, MAX_IMAGE_ARRAY_LENGTH);
|
|
14181
|
+
continue;
|
|
14182
|
+
}
|
|
14183
|
+
out[k2] = stripBloat(v2);
|
|
14184
|
+
}
|
|
14185
|
+
return out;
|
|
14186
|
+
}
|
|
14187
|
+
return obj;
|
|
14188
|
+
}
|
|
14189
|
+
function extractJsonLdBlocks() {
|
|
14190
|
+
if (typeof document === "undefined") return [];
|
|
14191
|
+
return Array.from(document.querySelectorAll('script[type="application/ld+json"]')).filter((el) => !el.hasAttribute("data-syntro-no-jsonld")).map((el) => {
|
|
14192
|
+
var _a3;
|
|
14193
|
+
try {
|
|
14194
|
+
return JSON.parse((_a3 = el.textContent) != null ? _a3 : "");
|
|
14195
|
+
} catch {
|
|
14196
|
+
return null;
|
|
14197
|
+
}
|
|
14198
|
+
}).filter((parsed) => parsed !== null);
|
|
14199
|
+
}
|
|
14200
|
+
function extractAndRegisterJsonLd(telemetry) {
|
|
14201
|
+
if (!telemetry.register) return;
|
|
14202
|
+
const blocks = extractJsonLdBlocks().map(stripBloat).map(stripPii);
|
|
14203
|
+
if (blocks.length === 0) return;
|
|
14204
|
+
telemetry.register({ json_ld: blocks });
|
|
14205
|
+
}
|
|
14206
|
+
|
|
14207
|
+
// src/defaults/index.ts
|
|
14208
|
+
function installDefaults(telemetry, consentGate, options = {}) {
|
|
14209
|
+
captureInitialProperties(telemetry, options.geoPromise);
|
|
14210
|
+
promoteClickIds(telemetry);
|
|
14211
|
+
extractAndRegisterJsonLd(telemetry);
|
|
14212
|
+
identifyFromUrlParam(telemetry, consentGate);
|
|
14213
|
+
const teardownEmail = installEmailIdentifyListener(telemetry, consentGate);
|
|
14214
|
+
return () => {
|
|
14215
|
+
teardownEmail();
|
|
14216
|
+
};
|
|
14217
|
+
}
|
|
14218
|
+
|
|
13919
14219
|
// src/events/validation.ts
|
|
13920
14220
|
var APP_PREFIX = "app:";
|
|
13921
14221
|
var RESERVED_PREFIX = "syntro:";
|
|
@@ -16879,6 +17179,353 @@ function createSmartCanvasRuntime(options = {}) {
|
|
|
16879
17179
|
return runtime5;
|
|
16880
17180
|
}
|
|
16881
17181
|
|
|
17182
|
+
// src/telemetry/consent/adapters/gtmConsentMode.ts
|
|
17183
|
+
function mapStorage(value) {
|
|
17184
|
+
if (value === "granted") return "granted";
|
|
17185
|
+
if (value === "denied") return "denied";
|
|
17186
|
+
return "pending";
|
|
17187
|
+
}
|
|
17188
|
+
function extractAnalyticsStorage(entry) {
|
|
17189
|
+
var _a3, _b;
|
|
17190
|
+
if (!entry) return null;
|
|
17191
|
+
if (Array.isArray(entry) && entry[0] === "consent") {
|
|
17192
|
+
const payload = entry[2];
|
|
17193
|
+
return (_a3 = payload == null ? void 0 : payload.analytics_storage) != null ? _a3 : null;
|
|
17194
|
+
}
|
|
17195
|
+
if (typeof entry === "object" && entry.event === "consent_update") {
|
|
17196
|
+
const payload = entry;
|
|
17197
|
+
return (_b = payload.analytics_storage) != null ? _b : null;
|
|
17198
|
+
}
|
|
17199
|
+
return null;
|
|
17200
|
+
}
|
|
17201
|
+
var GtmConsentModeAdapter = class {
|
|
17202
|
+
constructor() {
|
|
17203
|
+
__publicField(this, "name", "gtm-consent-mode-v2");
|
|
17204
|
+
}
|
|
17205
|
+
detect() {
|
|
17206
|
+
if (typeof window === "undefined") return false;
|
|
17207
|
+
const dl = window.dataLayer;
|
|
17208
|
+
if (!Array.isArray(dl)) return false;
|
|
17209
|
+
for (const entry of dl) {
|
|
17210
|
+
if (extractAnalyticsStorage(entry) !== null) return true;
|
|
17211
|
+
}
|
|
17212
|
+
return false;
|
|
17213
|
+
}
|
|
17214
|
+
initialize(callback) {
|
|
17215
|
+
if (typeof window === "undefined") {
|
|
17216
|
+
callback("pending");
|
|
17217
|
+
return () => {
|
|
17218
|
+
};
|
|
17219
|
+
}
|
|
17220
|
+
if (!Array.isArray(window.dataLayer)) {
|
|
17221
|
+
window.dataLayer = [];
|
|
17222
|
+
}
|
|
17223
|
+
callback(this.readCurrentState());
|
|
17224
|
+
const dl = window.dataLayer;
|
|
17225
|
+
const original = dl.push.bind(dl);
|
|
17226
|
+
const wrappedPush = (...args) => {
|
|
17227
|
+
const result = original(...args);
|
|
17228
|
+
for (const arg of args) {
|
|
17229
|
+
const storage = extractAnalyticsStorage(arg);
|
|
17230
|
+
if (storage !== null) {
|
|
17231
|
+
callback(mapStorage(storage));
|
|
17232
|
+
}
|
|
17233
|
+
}
|
|
17234
|
+
return result;
|
|
17235
|
+
};
|
|
17236
|
+
dl.push = wrappedPush;
|
|
17237
|
+
return () => {
|
|
17238
|
+
if (dl.push === wrappedPush) {
|
|
17239
|
+
dl.push = original;
|
|
17240
|
+
}
|
|
17241
|
+
};
|
|
17242
|
+
}
|
|
17243
|
+
readCurrentState() {
|
|
17244
|
+
if (typeof window === "undefined") return "pending";
|
|
17245
|
+
const dl = window.dataLayer;
|
|
17246
|
+
if (!Array.isArray(dl)) return "pending";
|
|
17247
|
+
for (let i = dl.length - 1; i >= 0; i--) {
|
|
17248
|
+
const storage = extractAnalyticsStorage(dl[i]);
|
|
17249
|
+
if (storage !== null) return mapStorage(storage);
|
|
17250
|
+
}
|
|
17251
|
+
return "pending";
|
|
17252
|
+
}
|
|
17253
|
+
};
|
|
17254
|
+
|
|
17255
|
+
// src/telemetry/consent/adapters/iabTcf.ts
|
|
17256
|
+
var PURPOSE_STORAGE = "1";
|
|
17257
|
+
var PURPOSE_ANALYTICS = "8";
|
|
17258
|
+
var IabTcfAdapter = class {
|
|
17259
|
+
constructor() {
|
|
17260
|
+
__publicField(this, "name", "iab-tcf-v2");
|
|
17261
|
+
}
|
|
17262
|
+
detect() {
|
|
17263
|
+
if (typeof window === "undefined") return false;
|
|
17264
|
+
return typeof window.__tcfapi === "function";
|
|
17265
|
+
}
|
|
17266
|
+
initialize(callback) {
|
|
17267
|
+
callback("pending");
|
|
17268
|
+
if (typeof window === "undefined") return () => {
|
|
17269
|
+
};
|
|
17270
|
+
const tcfapi = window.__tcfapi;
|
|
17271
|
+
if (typeof tcfapi !== "function") return () => {
|
|
17272
|
+
};
|
|
17273
|
+
let listenerId;
|
|
17274
|
+
tcfapi("addEventListener", 2, (tcData, success) => {
|
|
17275
|
+
var _a3, _b;
|
|
17276
|
+
if (!success || !tcData) return;
|
|
17277
|
+
if (listenerId === void 0 && typeof tcData.listenerId === "number") {
|
|
17278
|
+
listenerId = tcData.listenerId;
|
|
17279
|
+
}
|
|
17280
|
+
const ready = tcData.eventStatus === "tcloaded" || tcData.eventStatus === "useractioncomplete";
|
|
17281
|
+
if (!ready) return;
|
|
17282
|
+
if (tcData.gdprApplies === false) {
|
|
17283
|
+
callback("granted");
|
|
17284
|
+
return;
|
|
17285
|
+
}
|
|
17286
|
+
const consents = (_b = (_a3 = tcData.purpose) == null ? void 0 : _a3.consents) != null ? _b : {};
|
|
17287
|
+
const p1 = consents[PURPOSE_STORAGE] === true;
|
|
17288
|
+
const p8 = consents[PURPOSE_ANALYTICS] === true;
|
|
17289
|
+
callback(p1 && p8 ? "granted" : "denied");
|
|
17290
|
+
});
|
|
17291
|
+
return () => {
|
|
17292
|
+
if (listenerId !== void 0) {
|
|
17293
|
+
try {
|
|
17294
|
+
tcfapi("removeEventListener", 2, () => {
|
|
17295
|
+
}, listenerId);
|
|
17296
|
+
} catch {
|
|
17297
|
+
}
|
|
17298
|
+
}
|
|
17299
|
+
};
|
|
17300
|
+
}
|
|
17301
|
+
};
|
|
17302
|
+
|
|
17303
|
+
// src/telemetry/consent/adapters/shopify.ts
|
|
17304
|
+
var VISITOR_CONSENT_EVENT = "visitorConsentCollected";
|
|
17305
|
+
var ShopifyCustomerPrivacyAdapter = class {
|
|
17306
|
+
constructor() {
|
|
17307
|
+
__publicField(this, "name", "shopify-customer-privacy");
|
|
17308
|
+
}
|
|
17309
|
+
detect() {
|
|
17310
|
+
if (typeof window === "undefined") return false;
|
|
17311
|
+
const shopify = window.Shopify;
|
|
17312
|
+
return typeof (shopify == null ? void 0 : shopify.loadFeatures) === "function";
|
|
17313
|
+
}
|
|
17314
|
+
initialize(callback) {
|
|
17315
|
+
callback("pending");
|
|
17316
|
+
if (typeof window === "undefined") return () => {
|
|
17317
|
+
};
|
|
17318
|
+
const shopify = window.Shopify;
|
|
17319
|
+
if (!(shopify == null ? void 0 : shopify.loadFeatures)) return () => {
|
|
17320
|
+
};
|
|
17321
|
+
let visitorEventHandler;
|
|
17322
|
+
shopify.loadFeatures([{ name: "consent-tracking-api", version: "0.1" }], (error2) => {
|
|
17323
|
+
var _a3, _b;
|
|
17324
|
+
if (error2) {
|
|
17325
|
+
return;
|
|
17326
|
+
}
|
|
17327
|
+
const allowed = (_b = (_a3 = shopify.customerPrivacy) == null ? void 0 : _a3.userCanBeTracked()) != null ? _b : true;
|
|
17328
|
+
callback(allowed ? "granted" : "denied");
|
|
17329
|
+
visitorEventHandler = (event) => {
|
|
17330
|
+
const detail = event.detail;
|
|
17331
|
+
callback((detail == null ? void 0 : detail.analyticsAllowed) ? "granted" : "denied");
|
|
17332
|
+
};
|
|
17333
|
+
document.addEventListener(VISITOR_CONSENT_EVENT, visitorEventHandler);
|
|
17334
|
+
});
|
|
17335
|
+
return () => {
|
|
17336
|
+
if (visitorEventHandler) {
|
|
17337
|
+
document.removeEventListener(VISITOR_CONSENT_EVENT, visitorEventHandler);
|
|
17338
|
+
}
|
|
17339
|
+
};
|
|
17340
|
+
}
|
|
17341
|
+
};
|
|
17342
|
+
|
|
17343
|
+
// src/telemetry/consent/ConsentDetector.ts
|
|
17344
|
+
var ConsentDetector = class {
|
|
17345
|
+
constructor(options) {
|
|
17346
|
+
this.options = options;
|
|
17347
|
+
}
|
|
17348
|
+
/**
|
|
17349
|
+
* Start the detector. Probes adapters in array order; first match wins.
|
|
17350
|
+
* Returns a teardown function that unsubscribes the active adapter.
|
|
17351
|
+
*
|
|
17352
|
+
* Async because adapters may need async initialization. Bootstrap
|
|
17353
|
+
* should NOT await this — feature flags and anonymous telemetry
|
|
17354
|
+
* should keep flowing while the detector probes.
|
|
17355
|
+
*/
|
|
17356
|
+
async start(gate) {
|
|
17357
|
+
var _a3, _b, _c, _d;
|
|
17358
|
+
for (const adapter of this.options.adapters) {
|
|
17359
|
+
let detected = false;
|
|
17360
|
+
try {
|
|
17361
|
+
detected = adapter.detect();
|
|
17362
|
+
} catch {
|
|
17363
|
+
continue;
|
|
17364
|
+
}
|
|
17365
|
+
if (!detected) continue;
|
|
17366
|
+
try {
|
|
17367
|
+
const teardown = adapter.initialize((status) => gate.setStatus(status));
|
|
17368
|
+
(_b = (_a3 = this.options).onAdapterSelected) == null ? void 0 : _b.call(_a3, adapter.name);
|
|
17369
|
+
return teardown;
|
|
17370
|
+
} catch (err) {
|
|
17371
|
+
console.warn(`[Syntro consent] Adapter "${adapter.name}" failed:`, err);
|
|
17372
|
+
}
|
|
17373
|
+
}
|
|
17374
|
+
(_d = (_c = this.options).onAdapterSelected) == null ? void 0 : _d.call(_c, null);
|
|
17375
|
+
const fallback = await this.resolveFallback();
|
|
17376
|
+
gate.setStatus(fallback);
|
|
17377
|
+
return () => {
|
|
17378
|
+
};
|
|
17379
|
+
}
|
|
17380
|
+
async resolveFallback() {
|
|
17381
|
+
if (!this.options.fallbackStatus) return "pending";
|
|
17382
|
+
try {
|
|
17383
|
+
return await this.options.fallbackStatus();
|
|
17384
|
+
} catch {
|
|
17385
|
+
return "pending";
|
|
17386
|
+
}
|
|
17387
|
+
}
|
|
17388
|
+
};
|
|
17389
|
+
|
|
17390
|
+
// src/telemetry/consent/ConsentGate.ts
|
|
17391
|
+
var ConsentGate = class {
|
|
17392
|
+
constructor(config = {}) {
|
|
17393
|
+
__publicField(this, "status");
|
|
17394
|
+
__publicField(this, "listeners", /* @__PURE__ */ new Set());
|
|
17395
|
+
__publicField(this, "configCallback");
|
|
17396
|
+
__publicField(this, "gtmEnabled");
|
|
17397
|
+
__publicField(this, "destroyed", false);
|
|
17398
|
+
__publicField(this, "pendingResolverFn");
|
|
17399
|
+
var _a3, _b;
|
|
17400
|
+
this.status = (_a3 = config.initialStatus) != null ? _a3 : "pending";
|
|
17401
|
+
this.configCallback = config.onConsentChange;
|
|
17402
|
+
this.gtmEnabled = (_b = config.readFromGtmConsentMode) != null ? _b : false;
|
|
17403
|
+
this.pendingResolverFn = config.pendingResolver;
|
|
17404
|
+
}
|
|
17405
|
+
/**
|
|
17406
|
+
* Grant consent — enables telemetry collection.
|
|
17407
|
+
*/
|
|
17408
|
+
grant() {
|
|
17409
|
+
this.setStatus("granted");
|
|
17410
|
+
}
|
|
17411
|
+
/**
|
|
17412
|
+
* Deny consent — disables telemetry collection.
|
|
17413
|
+
*/
|
|
17414
|
+
deny() {
|
|
17415
|
+
this.setStatus("denied");
|
|
17416
|
+
}
|
|
17417
|
+
/**
|
|
17418
|
+
* Get the current consent status.
|
|
17419
|
+
*/
|
|
17420
|
+
getStatus() {
|
|
17421
|
+
return this.status;
|
|
17422
|
+
}
|
|
17423
|
+
/**
|
|
17424
|
+
* Subscribe to consent status changes.
|
|
17425
|
+
* Returns an unsubscribe function.
|
|
17426
|
+
*/
|
|
17427
|
+
subscribe(listener) {
|
|
17428
|
+
this.listeners.add(listener);
|
|
17429
|
+
return () => {
|
|
17430
|
+
this.listeners.delete(listener);
|
|
17431
|
+
};
|
|
17432
|
+
}
|
|
17433
|
+
/**
|
|
17434
|
+
* Subscribe to the next definitive (granted | denied) status change,
|
|
17435
|
+
* then auto-unsubscribe. If status is already definitive, fires
|
|
17436
|
+
* synchronously (next microtask) with current status.
|
|
17437
|
+
*
|
|
17438
|
+
* Used by D-2 (URL-param identify) to defer the identify() call
|
|
17439
|
+
* until consent is decided.
|
|
17440
|
+
*/
|
|
17441
|
+
subscribeOnce(listener) {
|
|
17442
|
+
if (this.status !== "pending") {
|
|
17443
|
+
queueMicrotask(() => listener(this.status));
|
|
17444
|
+
return () => {
|
|
17445
|
+
};
|
|
17446
|
+
}
|
|
17447
|
+
const unsub = this.subscribe((status) => {
|
|
17448
|
+
if (status === "pending") return;
|
|
17449
|
+
unsub();
|
|
17450
|
+
listener(status);
|
|
17451
|
+
});
|
|
17452
|
+
return unsub;
|
|
17453
|
+
}
|
|
17454
|
+
/**
|
|
17455
|
+
* Resolve what the current 'pending' state should be treated as,
|
|
17456
|
+
* via the configured pendingResolver. Returns the current status
|
|
17457
|
+
* directly if it's already 'granted' or 'denied'.
|
|
17458
|
+
*
|
|
17459
|
+
* Defaults to 'denied' if no resolver is configured (conservative).
|
|
17460
|
+
*/
|
|
17461
|
+
async resolvePending() {
|
|
17462
|
+
if (this.status !== "pending") return this.status;
|
|
17463
|
+
if (!this.pendingResolverFn) return "denied";
|
|
17464
|
+
try {
|
|
17465
|
+
return await this.pendingResolverFn();
|
|
17466
|
+
} catch {
|
|
17467
|
+
return "denied";
|
|
17468
|
+
}
|
|
17469
|
+
}
|
|
17470
|
+
/**
|
|
17471
|
+
* Public setter — used by ConsentDetector / adapters to push state changes.
|
|
17472
|
+
*/
|
|
17473
|
+
setStatus(newStatus) {
|
|
17474
|
+
this.applyStatus(newStatus);
|
|
17475
|
+
}
|
|
17476
|
+
/**
|
|
17477
|
+
* Poll GTM's dataLayer for consent state.
|
|
17478
|
+
* Scans for the most recent consent update event with analytics_storage.
|
|
17479
|
+
*/
|
|
17480
|
+
pollGtmConsent() {
|
|
17481
|
+
if (!this.gtmEnabled || typeof window === "undefined") return;
|
|
17482
|
+
const dataLayer = window.dataLayer;
|
|
17483
|
+
if (!Array.isArray(dataLayer)) return;
|
|
17484
|
+
for (let i = dataLayer.length - 1; i >= 0; i--) {
|
|
17485
|
+
const entry = dataLayer[i];
|
|
17486
|
+
if (!entry) continue;
|
|
17487
|
+
const isConsentUpdate = entry[0] === "consent" && entry[1] === "update" && entry[2];
|
|
17488
|
+
if (isConsentUpdate) {
|
|
17489
|
+
const storage = entry[2].analytics_storage;
|
|
17490
|
+
if (storage === "granted") {
|
|
17491
|
+
this.setStatus("granted");
|
|
17492
|
+
} else if (storage === "denied") {
|
|
17493
|
+
this.setStatus("denied");
|
|
17494
|
+
}
|
|
17495
|
+
return;
|
|
17496
|
+
}
|
|
17497
|
+
}
|
|
17498
|
+
}
|
|
17499
|
+
/**
|
|
17500
|
+
* Clean up listeners and stop responding to changes.
|
|
17501
|
+
*/
|
|
17502
|
+
destroy() {
|
|
17503
|
+
this.destroyed = true;
|
|
17504
|
+
this.listeners.clear();
|
|
17505
|
+
}
|
|
17506
|
+
// ─── Private ─────────────────────────────────────────────────────
|
|
17507
|
+
applyStatus(newStatus) {
|
|
17508
|
+
if (this.destroyed) return;
|
|
17509
|
+
if (this.status === newStatus) return;
|
|
17510
|
+
this.status = newStatus;
|
|
17511
|
+
this.notify(newStatus);
|
|
17512
|
+
}
|
|
17513
|
+
notify(status) {
|
|
17514
|
+
if (this.configCallback) {
|
|
17515
|
+
try {
|
|
17516
|
+
this.configCallback(status);
|
|
17517
|
+
} catch {
|
|
17518
|
+
}
|
|
17519
|
+
}
|
|
17520
|
+
for (const listener of this.listeners) {
|
|
17521
|
+
try {
|
|
17522
|
+
listener(status);
|
|
17523
|
+
} catch {
|
|
17524
|
+
}
|
|
17525
|
+
}
|
|
17526
|
+
}
|
|
17527
|
+
};
|
|
17528
|
+
|
|
16882
17529
|
// src/telemetry/InterventionTracker.ts
|
|
16883
17530
|
var InterventionTracker = class {
|
|
16884
17531
|
constructor(telemetry, variantId) {
|
|
@@ -16981,6 +17628,14 @@ var NoopAdapter = class {
|
|
|
16981
17628
|
}
|
|
16982
17629
|
track(_eventName, _properties) {
|
|
16983
17630
|
}
|
|
17631
|
+
identify(_distinctId, _properties) {
|
|
17632
|
+
}
|
|
17633
|
+
alias(_newDistinctId, _oldDistinctId) {
|
|
17634
|
+
}
|
|
17635
|
+
optInCapturing() {
|
|
17636
|
+
}
|
|
17637
|
+
optOutCapturing() {
|
|
17638
|
+
}
|
|
16984
17639
|
};
|
|
16985
17640
|
function createNoopClient() {
|
|
16986
17641
|
return new NoopAdapter();
|
|
@@ -17058,6 +17713,9 @@ var PostHogAdapter = class {
|
|
|
17058
17713
|
// Enable web vitals
|
|
17059
17714
|
enable_recording_console_log: true
|
|
17060
17715
|
};
|
|
17716
|
+
if (options.cookieless_mode) {
|
|
17717
|
+
initOptions.cookieless_mode = options.cookieless_mode;
|
|
17718
|
+
}
|
|
17061
17719
|
const result = posthog.init(
|
|
17062
17720
|
options.apiKey,
|
|
17063
17721
|
initOptions,
|
|
@@ -17168,6 +17826,24 @@ var PostHogAdapter = class {
|
|
|
17168
17826
|
var _a3;
|
|
17169
17827
|
(_a3 = this.client) == null ? void 0 : _a3.alias(id, aliasId);
|
|
17170
17828
|
}
|
|
17829
|
+
/**
|
|
17830
|
+
* Opt the current visitor INTO capturing. Drives the granted-state
|
|
17831
|
+
* transition from the consent gate. When previously cookieless or
|
|
17832
|
+
* opted-out, switches to full identified capture.
|
|
17833
|
+
*/
|
|
17834
|
+
optInCapturing() {
|
|
17835
|
+
var _a3;
|
|
17836
|
+
(_a3 = this.client) == null ? void 0 : _a3.opt_in_capturing();
|
|
17837
|
+
}
|
|
17838
|
+
/**
|
|
17839
|
+
* Opt the current visitor OUT of capturing. Drives the denied-state
|
|
17840
|
+
* transition from the consent gate. Stops sending events; existing
|
|
17841
|
+
* cookieless/anonymous events remain in the data warehouse.
|
|
17842
|
+
*/
|
|
17843
|
+
optOutCapturing() {
|
|
17844
|
+
var _a3;
|
|
17845
|
+
(_a3 = this.client) == null ? void 0 : _a3.opt_out_capturing();
|
|
17846
|
+
}
|
|
17171
17847
|
track(eventName, payload) {
|
|
17172
17848
|
var _a3;
|
|
17173
17849
|
(_a3 = this.client) == null ? void 0 : _a3.capture(eventName, payload);
|
|
@@ -17436,6 +18112,11 @@ async function _initCore(options) {
|
|
|
17436
18112
|
// Enable PostHog feature flags for segment membership
|
|
17437
18113
|
enableFeatureFlags: true,
|
|
17438
18114
|
sessionRecording: true,
|
|
18115
|
+
// Cookieless mode: anonymous baseline until consent decision.
|
|
18116
|
+
// PostHog captures behavioral data with privacy-preserving session
|
|
18117
|
+
// hashes during pending; switches to cookied tracking on grant;
|
|
18118
|
+
// continues cookielessly on reject.
|
|
18119
|
+
cookieless_mode: "on_reject",
|
|
17439
18120
|
// Wire up callback for when flags are loaded (Phase 2)
|
|
17440
18121
|
onFeatureFlagsLoaded,
|
|
17441
18122
|
// Wire up event capture to feed into event processor
|
|
@@ -17457,6 +18138,53 @@ async function _initCore(options) {
|
|
|
17457
18138
|
var _a4;
|
|
17458
18139
|
(_a4 = telemetryForCapture.track) == null ? void 0 : _a4.call(telemetryForCapture, name, props);
|
|
17459
18140
|
});
|
|
18141
|
+
const telemetryForGate = telemetry;
|
|
18142
|
+
const resolveGeoStatus = async () => {
|
|
18143
|
+
const geo = await geoPromise;
|
|
18144
|
+
const isStrictRegion2 = geo.is_eu === "true" || geo.country_code === "GB" || geo.country_code === "CH";
|
|
18145
|
+
return isStrictRegion2 ? "denied" : "granted";
|
|
18146
|
+
};
|
|
18147
|
+
const consentGate = new ConsentGate({
|
|
18148
|
+
initialStatus: "pending",
|
|
18149
|
+
pendingResolver: resolveGeoStatus,
|
|
18150
|
+
// Drive PostHog opt-in/opt-out from gate transitions.
|
|
18151
|
+
onConsentChange: (status) => {
|
|
18152
|
+
var _a4, _b2;
|
|
18153
|
+
if (status === "granted") {
|
|
18154
|
+
(_a4 = telemetryForGate.optInCapturing) == null ? void 0 : _a4.call(telemetryForGate);
|
|
18155
|
+
} else if (status === "denied") {
|
|
18156
|
+
(_b2 = telemetryForGate.optOutCapturing) == null ? void 0 : _b2.call(telemetryForGate);
|
|
18157
|
+
}
|
|
18158
|
+
}
|
|
18159
|
+
});
|
|
18160
|
+
try {
|
|
18161
|
+
installDefaults(telemetry, consentGate, { geoPromise });
|
|
18162
|
+
debug("Syntro Bootstrap", "SDK defaults installed (D-1..D-5) with consent gate");
|
|
18163
|
+
} catch (err) {
|
|
18164
|
+
warn("Syntro Bootstrap", "installDefaults failed", err);
|
|
18165
|
+
}
|
|
18166
|
+
try {
|
|
18167
|
+
const detector = new ConsentDetector({
|
|
18168
|
+
adapters: [
|
|
18169
|
+
new ShopifyCustomerPrivacyAdapter(),
|
|
18170
|
+
new IabTcfAdapter(),
|
|
18171
|
+
new GtmConsentModeAdapter()
|
|
18172
|
+
],
|
|
18173
|
+
fallbackStatus: resolveGeoStatus,
|
|
18174
|
+
onAdapterSelected: (name) => {
|
|
18175
|
+
var _a4;
|
|
18176
|
+
(_a4 = telemetryForGate.track) == null ? void 0 : _a4.call(telemetryForGate, "syntro_consent_adapter_selected", {
|
|
18177
|
+
adapter: name != null ? name : "none"
|
|
18178
|
+
});
|
|
18179
|
+
debug("Syntro Bootstrap", `Consent adapter selected: ${name != null ? name : "none"}`);
|
|
18180
|
+
}
|
|
18181
|
+
});
|
|
18182
|
+
detector.start(consentGate).catch((err) => {
|
|
18183
|
+
warn("Syntro Bootstrap", "Consent detector failed to start", err);
|
|
18184
|
+
});
|
|
18185
|
+
} catch (err) {
|
|
18186
|
+
warn("Syntro Bootstrap", "Consent detector setup failed", err);
|
|
18187
|
+
}
|
|
17460
18188
|
if ((platformAdapter == null ? void 0 : platformAdapter.name) === "shopify" && telemetryHost) {
|
|
17461
18189
|
try {
|
|
17462
18190
|
shopifyPixelBridge = new ShopifyPixelBridge({
|