@bacano/sdk 1.1.20 → 1.2.2
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/README.md +4 -0
- package/STOREFRONT_FORMS.md +7 -0
- package/STOREFRONT_MARKETING.md +161 -6
- package/index.cjs +322 -86
- package/index.js +322 -86
- package/marketing/browser-adapters.d.ts +30 -0
- package/marketing/clarity.d.ts +1 -1
- package/marketing/form-interactions.d.ts +12 -0
- package/marketing/gtm.d.ts +6 -0
- package/marketing/index.d.ts +2 -0
- package/marketing/interactions.d.ts +19 -0
- package/marketing/types.d.ts +5 -2
- package/package.json +1 -1
- package/react/cta-tracking.d.ts +5 -0
- package/react/form-sections.d.ts +16 -0
- package/react/form-tracking.d.ts +11 -0
- package/react/forms.cjs +230 -93
- package/react/forms.d.ts +6 -2
- package/react/forms.js +222 -85
- package/react/marketing-provider.d.ts +2 -1
- package/react.cjs +411 -136
- package/react.d.ts +1 -0
- package/react.js +394 -119
- package/skills/bacano-web/SKILL.md +5 -0
- package/skills/bacano-web/references/forms.md +5 -0
- package/skills/bacano-web/references/interactions.md +147 -0
- package/skills/bacano-web/references/report.md +5 -0
- package/types.d.ts +4 -0
package/index.js
CHANGED
|
@@ -337,7 +337,8 @@ async function getWebsitePublicConfig(options) {
|
|
|
337
337
|
pixelId: marketing.destinations.meta.pixel_id,
|
|
338
338
|
diagnosticMode: marketing.destinations.meta.diagnostic_mode ?? false
|
|
339
339
|
} : null,
|
|
340
|
-
clarity: marketing.destinations.clarity ? { projectId: marketing.destinations.clarity.project_id } : null
|
|
340
|
+
clarity: marketing.destinations.clarity ? { projectId: marketing.destinations.clarity.project_id } : null,
|
|
341
|
+
...marketing.destinations.gtm ? { gtm: { containerId: marketing.destinations.gtm.container_id } } : {}
|
|
341
342
|
}
|
|
342
343
|
},
|
|
343
344
|
legal: {
|
|
@@ -2900,6 +2901,9 @@ var ALLOWED_MARKETING_PARAMETERS = /* @__PURE__ */ new Set([
|
|
|
2900
2901
|
function sanitizeMarketingUrl(input, baseUrl) {
|
|
2901
2902
|
try {
|
|
2902
2903
|
const url = new URL(input, baseUrl);
|
|
2904
|
+
if (url.protocol !== "http:" && url.protocol !== "https:") return "";
|
|
2905
|
+
url.username = "";
|
|
2906
|
+
url.password = "";
|
|
2903
2907
|
const safeParameters = new URLSearchParams();
|
|
2904
2908
|
for (const [key, value] of url.searchParams.entries()) {
|
|
2905
2909
|
if (ALLOWED_MARKETING_PARAMETERS.has(key.toLowerCase()) && !SENSITIVE_PARAMETER_PATTERNS.some((pattern) => pattern.test(key))) {
|
|
@@ -5675,50 +5679,65 @@ function createContentModule(graphqlUrl, getHeaders, getContext, storageUrl) {
|
|
|
5675
5679
|
};
|
|
5676
5680
|
}
|
|
5677
5681
|
|
|
5678
|
-
// libs/sdk/src/marketing/
|
|
5679
|
-
var
|
|
5680
|
-
|
|
5681
|
-
|
|
5682
|
-
|
|
5683
|
-
|
|
5684
|
-
|
|
5685
|
-
|
|
5686
|
-
|
|
5687
|
-
|
|
5688
|
-
|
|
5689
|
-
|
|
5690
|
-
|
|
5691
|
-
|
|
5682
|
+
// libs/sdk/src/marketing/interactions.ts
|
|
5683
|
+
var WEBSITE_INTERACTION_EVENTS = [
|
|
5684
|
+
"cta_click",
|
|
5685
|
+
"form_open",
|
|
5686
|
+
"form_start",
|
|
5687
|
+
"form_step_complete",
|
|
5688
|
+
"form_submit_attempt",
|
|
5689
|
+
"form_submit_error",
|
|
5690
|
+
"form_submit_success"
|
|
5691
|
+
];
|
|
5692
|
+
function isInteractionEvent(name) {
|
|
5693
|
+
return WEBSITE_INTERACTION_EVENTS.some((candidate) => candidate === name);
|
|
5694
|
+
}
|
|
5695
|
+
function isInteractionIdentifier(value) {
|
|
5696
|
+
return typeof value === "string" && /^[a-z][a-z0-9_-]{0,63}$/.test(value);
|
|
5697
|
+
}
|
|
5698
|
+
function isBrowserMarketingEvent(name) {
|
|
5699
|
+
return isInteractionEvent(name) || [
|
|
5700
|
+
"page_view",
|
|
5701
|
+
"search",
|
|
5702
|
+
"view_item_list",
|
|
5703
|
+
"select_item",
|
|
5704
|
+
"view_item",
|
|
5705
|
+
"add_to_cart",
|
|
5706
|
+
"remove_from_cart",
|
|
5707
|
+
"view_cart",
|
|
5708
|
+
"begin_checkout",
|
|
5709
|
+
"add_shipping_info",
|
|
5710
|
+
"add_payment_info",
|
|
5711
|
+
"contact"
|
|
5712
|
+
].includes(name);
|
|
5713
|
+
}
|
|
5714
|
+
function interactionParameters(event) {
|
|
5715
|
+
if (!isInteractionEvent(event.eventName)) return {};
|
|
5716
|
+
if (event.eventName === "cta_click" ? !isInteractionIdentifier(event.ctaId) : !isInteractionIdentifier(event.formKey))
|
|
5717
|
+
return null;
|
|
5718
|
+
if (event.eventName === "form_step_complete" && !isInteractionIdentifier(event.stepKey))
|
|
5719
|
+
return null;
|
|
5720
|
+
if (event.eventName === "form_submit_error" && !["validation", "network", "rejected", "unavailable"].includes(
|
|
5721
|
+
event.errorCategory ?? ""
|
|
5722
|
+
))
|
|
5723
|
+
return null;
|
|
5724
|
+
const params = {};
|
|
5725
|
+
for (const [key, value] of Object.entries({
|
|
5726
|
+
form_key: event.formKey,
|
|
5727
|
+
cta_id: event.ctaId,
|
|
5728
|
+
placement: event.placement,
|
|
5729
|
+
step_key: event.stepKey
|
|
5730
|
+
})) {
|
|
5731
|
+
if (value === void 0) continue;
|
|
5732
|
+
if (!isInteractionIdentifier(value)) return null;
|
|
5733
|
+
params[key] = value;
|
|
5692
5734
|
}
|
|
5693
|
-
|
|
5694
|
-
|
|
5695
|
-
|
|
5696
|
-
if (typeof document === "undefined") return;
|
|
5697
|
-
const id = `bacano-clarity-${projectId}`;
|
|
5698
|
-
if (document.getElementById(id)) return;
|
|
5699
|
-
const script = document.createElement("script");
|
|
5700
|
-
script.id = id;
|
|
5701
|
-
script.async = true;
|
|
5702
|
-
script.src = `https://www.clarity.ms/tag/${encodeURIComponent(projectId)}`;
|
|
5703
|
-
script.referrerPolicy = "strict-origin-when-cross-origin";
|
|
5704
|
-
document.head.appendChild(script);
|
|
5705
|
-
}
|
|
5706
|
-
function sendClarityEvent(eventName) {
|
|
5707
|
-
clarityWindow()?.clarity?.("event", eventName);
|
|
5735
|
+
if (event.eventName === "form_submit_error" && event.errorCategory)
|
|
5736
|
+
params.error_category = event.errorCategory;
|
|
5737
|
+
return params;
|
|
5708
5738
|
}
|
|
5709
|
-
|
|
5710
|
-
|
|
5711
|
-
function matchesCanonicalMarketingHost(canonicalDomain, currentHostname) {
|
|
5712
|
-
if (canonicalDomain === void 0) return true;
|
|
5713
|
-
if (!canonicalDomain?.trim()) return false;
|
|
5714
|
-
try {
|
|
5715
|
-
const configuredHost = new URL(
|
|
5716
|
-
canonicalDomain.includes("://") ? canonicalDomain : `https://${canonicalDomain}`
|
|
5717
|
-
).hostname.toLowerCase();
|
|
5718
|
-
return currentHostname.toLowerCase() === configuredHost;
|
|
5719
|
-
} catch {
|
|
5720
|
-
return false;
|
|
5721
|
-
}
|
|
5739
|
+
function newMarketingEventId() {
|
|
5740
|
+
return typeof crypto !== "undefined" && "randomUUID" in crypto ? crypto.randomUUID() : `evt_${Date.now()}_${Math.random().toString(16).slice(2)}`;
|
|
5722
5741
|
}
|
|
5723
5742
|
|
|
5724
5743
|
// libs/sdk/src/marketing/ga4.ts
|
|
@@ -5746,19 +5765,20 @@ var WEBSITE_MARKETING_CONTACT_PLACEMENTS = [
|
|
|
5746
5765
|
"contact_page"
|
|
5747
5766
|
];
|
|
5748
5767
|
|
|
5749
|
-
// libs/sdk/src/marketing/
|
|
5768
|
+
// libs/sdk/src/marketing/browser-adapters.ts
|
|
5750
5769
|
var initializedGa4Destinations = /* @__PURE__ */ new Set();
|
|
5751
5770
|
var initializedMetaDestinations = /* @__PURE__ */ new Set();
|
|
5752
5771
|
function marketingWindow() {
|
|
5753
5772
|
return typeof window === "undefined" ? null : window;
|
|
5754
5773
|
}
|
|
5755
|
-
function loadScriptOnce(id, source) {
|
|
5774
|
+
function loadScriptOnce(id, source, onError) {
|
|
5756
5775
|
if (typeof document === "undefined" || document.getElementById(id)) return;
|
|
5757
5776
|
const script = document.createElement("script");
|
|
5758
5777
|
script.id = id;
|
|
5759
5778
|
script.async = true;
|
|
5760
5779
|
script.src = source;
|
|
5761
5780
|
script.referrerPolicy = "strict-origin-when-cross-origin";
|
|
5781
|
+
script.onerror = () => onError?.();
|
|
5762
5782
|
document.head.appendChild(script);
|
|
5763
5783
|
}
|
|
5764
5784
|
function prepareGa4(measurementId) {
|
|
@@ -5773,10 +5793,11 @@ function prepareGa4(measurementId) {
|
|
|
5773
5793
|
target.gtag("config", measurementId, { send_page_view: false });
|
|
5774
5794
|
initializedGa4Destinations.add(measurementId);
|
|
5775
5795
|
}
|
|
5776
|
-
function loadGa4Script(measurementId) {
|
|
5796
|
+
function loadGa4Script(measurementId, onError) {
|
|
5777
5797
|
loadScriptOnce(
|
|
5778
5798
|
`bacano-ga4-${measurementId}`,
|
|
5779
|
-
`https://www.googletagmanager.com/gtag/js?id=${encodeURIComponent(measurementId)}
|
|
5799
|
+
`https://www.googletagmanager.com/gtag/js?id=${encodeURIComponent(measurementId)}`,
|
|
5800
|
+
onError
|
|
5780
5801
|
);
|
|
5781
5802
|
}
|
|
5782
5803
|
function captureGa4ClientId(measurementId, websiteId, diagnostic) {
|
|
@@ -5824,10 +5845,11 @@ function prepareMeta(pixelId) {
|
|
|
5824
5845
|
target.fbq("init", pixelId);
|
|
5825
5846
|
initializedMetaDestinations.add(pixelId);
|
|
5826
5847
|
}
|
|
5827
|
-
function loadMetaScript() {
|
|
5848
|
+
function loadMetaScript(onError) {
|
|
5828
5849
|
loadScriptOnce(
|
|
5829
5850
|
"bacano-meta-pixel",
|
|
5830
|
-
"https://connect.facebook.net/en_US/fbevents.js"
|
|
5851
|
+
"https://connect.facebook.net/en_US/fbevents.js",
|
|
5852
|
+
onError
|
|
5831
5853
|
);
|
|
5832
5854
|
}
|
|
5833
5855
|
function scheduleScriptLoad(load, strategy) {
|
|
@@ -5891,10 +5913,12 @@ function contactPlacementFor(event) {
|
|
|
5891
5913
|
}
|
|
5892
5914
|
return placement;
|
|
5893
5915
|
}
|
|
5894
|
-
function sendGa4(event, diagnosticMode) {
|
|
5916
|
+
function sendGa4(event, diagnosticMode, measurementId) {
|
|
5895
5917
|
const target = marketingWindow();
|
|
5896
5918
|
const contactPlacement = contactPlacementFor(event);
|
|
5897
5919
|
target?.gtag?.("event", event.eventName, {
|
|
5920
|
+
send_to: measurementId,
|
|
5921
|
+
...interactionParameters(event),
|
|
5898
5922
|
currency: event.currency,
|
|
5899
5923
|
value: event.value,
|
|
5900
5924
|
search_term: event.searchTerm,
|
|
@@ -5911,15 +5935,17 @@ function sendGa4(event, diagnosticMode) {
|
|
|
5911
5935
|
debug_mode: diagnosticMode ? true : void 0
|
|
5912
5936
|
});
|
|
5913
5937
|
}
|
|
5914
|
-
function sendMeta(event) {
|
|
5938
|
+
function sendMeta(event, pixelId) {
|
|
5915
5939
|
const target = marketingWindow();
|
|
5916
5940
|
const eventName = metaEventName(event.eventName);
|
|
5917
5941
|
const isStandard = eventName !== event.eventName;
|
|
5918
5942
|
const contactPlacement = contactPlacementFor(event);
|
|
5919
5943
|
target?.fbq?.(
|
|
5920
|
-
isStandard ? "
|
|
5944
|
+
isStandard ? "trackSingle" : "trackSingleCustom",
|
|
5945
|
+
pixelId,
|
|
5921
5946
|
eventName,
|
|
5922
5947
|
{
|
|
5948
|
+
...interactionParameters(event),
|
|
5923
5949
|
currency: event.currency,
|
|
5924
5950
|
value: event.value,
|
|
5925
5951
|
search_string: event.searchTerm,
|
|
@@ -5936,40 +5962,192 @@ function sendMeta(event) {
|
|
|
5936
5962
|
{ eventID: event.eventId }
|
|
5937
5963
|
);
|
|
5938
5964
|
}
|
|
5939
|
-
|
|
5940
|
-
|
|
5965
|
+
|
|
5966
|
+
// libs/sdk/src/marketing/clarity.ts
|
|
5967
|
+
var initializedDestinations = /* @__PURE__ */ new Set();
|
|
5968
|
+
function clarityWindow() {
|
|
5969
|
+
return typeof window === "undefined" ? null : window;
|
|
5970
|
+
}
|
|
5971
|
+
function prepareClarity(projectId) {
|
|
5972
|
+
const target = clarityWindow();
|
|
5973
|
+
if (!target || initializedDestinations.has(projectId)) return;
|
|
5974
|
+
if (!target.clarity) {
|
|
5975
|
+
const clarity = function() {
|
|
5976
|
+
clarity.q ??= [];
|
|
5977
|
+
clarity.q.push(arguments);
|
|
5978
|
+
};
|
|
5979
|
+
target.clarity = clarity;
|
|
5980
|
+
}
|
|
5981
|
+
initializedDestinations.add(projectId);
|
|
5982
|
+
}
|
|
5983
|
+
function loadClarityScript(projectId, onError) {
|
|
5984
|
+
if (typeof document === "undefined") return;
|
|
5985
|
+
const id = `bacano-clarity-${projectId}`;
|
|
5986
|
+
if (document.getElementById(id)) return;
|
|
5987
|
+
const script = document.createElement("script");
|
|
5988
|
+
script.id = id;
|
|
5989
|
+
script.async = true;
|
|
5990
|
+
script.src = `https://www.clarity.ms/tag/${encodeURIComponent(projectId)}`;
|
|
5991
|
+
script.referrerPolicy = "strict-origin-when-cross-origin";
|
|
5992
|
+
script.onerror = () => onError?.();
|
|
5993
|
+
document.head.appendChild(script);
|
|
5994
|
+
}
|
|
5995
|
+
function sendClarityEvent(eventName) {
|
|
5996
|
+
clarityWindow()?.clarity?.("event", eventName);
|
|
5997
|
+
}
|
|
5998
|
+
|
|
5999
|
+
// libs/sdk/src/marketing/domain.ts
|
|
6000
|
+
function matchesCanonicalMarketingHost(canonicalDomain, currentHostname) {
|
|
6001
|
+
if (canonicalDomain === void 0) return true;
|
|
6002
|
+
if (!canonicalDomain?.trim()) return false;
|
|
6003
|
+
try {
|
|
6004
|
+
const configuredHost = new URL(
|
|
6005
|
+
canonicalDomain.includes("://") ? canonicalDomain : `https://${canonicalDomain}`
|
|
6006
|
+
).hostname.toLowerCase();
|
|
6007
|
+
return currentHostname.toLowerCase() === configuredHost;
|
|
6008
|
+
} catch {
|
|
6009
|
+
return false;
|
|
6010
|
+
}
|
|
6011
|
+
}
|
|
6012
|
+
|
|
6013
|
+
// libs/sdk/src/marketing/gtm.ts
|
|
6014
|
+
var CONTAINER_ID = /^GTM-[A-Z0-9]{4,32}$/;
|
|
6015
|
+
var preparedQueues = /* @__PURE__ */ new WeakMap();
|
|
6016
|
+
function prepareGtm(containerId) {
|
|
6017
|
+
const target = marketingWindow();
|
|
6018
|
+
if (!target || !CONTAINER_ID.test(containerId)) return;
|
|
6019
|
+
target.dataLayer ??= [];
|
|
6020
|
+
const containers = preparedQueues.get(target.dataLayer) ?? /* @__PURE__ */ new Set();
|
|
6021
|
+
if (containers.has(containerId)) return;
|
|
6022
|
+
target.dataLayer.push({ "gtm.start": Date.now(), event: "gtm.js" });
|
|
6023
|
+
containers.add(containerId);
|
|
6024
|
+
preparedQueues.set(target.dataLayer, containers);
|
|
6025
|
+
}
|
|
6026
|
+
function loadGtmScript(containerId, onError) {
|
|
6027
|
+
const target = marketingWindow();
|
|
6028
|
+
if (!target || typeof document === "undefined" || !CONTAINER_ID.test(containerId))
|
|
6029
|
+
return;
|
|
6030
|
+
const id = `bacano-gtm-${containerId}`;
|
|
6031
|
+
if (document.getElementById(id)) return;
|
|
6032
|
+
prepareGtm(containerId);
|
|
6033
|
+
const script = document.createElement("script");
|
|
6034
|
+
script.id = id;
|
|
6035
|
+
script.async = true;
|
|
6036
|
+
script.src = `https://www.googletagmanager.com/gtm.js?id=${encodeURIComponent(containerId)}`;
|
|
6037
|
+
script.referrerPolicy = "strict-origin-when-cross-origin";
|
|
6038
|
+
script.onerror = () => onError();
|
|
6039
|
+
document.head.appendChild(script);
|
|
6040
|
+
}
|
|
6041
|
+
function sendGtm(event) {
|
|
6042
|
+
const target = marketingWindow();
|
|
6043
|
+
if (!target?.dataLayer) return;
|
|
6044
|
+
target.dataLayer.push({ bacano: null });
|
|
6045
|
+
target.dataLayer.push({
|
|
6046
|
+
event: `bacano_${event.eventName}`,
|
|
6047
|
+
bacano: {
|
|
6048
|
+
schema_version: event.schemaVersion,
|
|
6049
|
+
event_name: event.eventName,
|
|
6050
|
+
event_id: event.eventId,
|
|
6051
|
+
...interactionParameters(event),
|
|
6052
|
+
page_location: event.pageLocation,
|
|
6053
|
+
currency: event.currency,
|
|
6054
|
+
value: event.value,
|
|
6055
|
+
items: mapGa4Items(event.items),
|
|
6056
|
+
item_list_id: event.itemListId,
|
|
6057
|
+
item_list_name: event.itemListName,
|
|
6058
|
+
shipping_tier: event.shippingTier,
|
|
6059
|
+
payment_type: event.paymentType,
|
|
6060
|
+
method: event.contactMethod,
|
|
6061
|
+
contact_placement: event.eventName === "contact" && WEBSITE_MARKETING_CONTACT_PLACEMENTS.some(
|
|
6062
|
+
(value) => value === event.contactPlacement
|
|
6063
|
+
) ? event.contactPlacement : void 0
|
|
6064
|
+
}
|
|
6065
|
+
});
|
|
5941
6066
|
}
|
|
6067
|
+
|
|
6068
|
+
// libs/sdk/src/marketing/runtime.ts
|
|
5942
6069
|
function createWebsiteMarketingRuntime(options) {
|
|
5943
6070
|
let destroyed = false;
|
|
6071
|
+
const dispatched = /* @__PURE__ */ new Set();
|
|
6072
|
+
const observer = options.diagnostic;
|
|
6073
|
+
const diagnostic = (value) => {
|
|
6074
|
+
try {
|
|
6075
|
+
observer?.(value);
|
|
6076
|
+
} catch {
|
|
6077
|
+
}
|
|
6078
|
+
};
|
|
6079
|
+
options = { ...options, diagnostic };
|
|
5944
6080
|
let cancelScheduledLoad = () => void 0;
|
|
5945
6081
|
const target = marketingWindow();
|
|
5946
|
-
const enabled = options.config.enabled && Boolean(
|
|
6082
|
+
const enabled = options.config.enabled && options.measurementAllowed !== false && Boolean(
|
|
5947
6083
|
target && matchesCanonicalMarketingHost(
|
|
5948
6084
|
options.canonicalDomain,
|
|
5949
6085
|
target.location.hostname
|
|
5950
6086
|
)
|
|
5951
6087
|
) && Boolean(
|
|
5952
|
-
options.config.destinations.ga4 || options.config.destinations.meta || options.config.destinations.clarity
|
|
6088
|
+
options.config.destinations.ga4 || options.config.destinations.meta || options.config.destinations.clarity || options.config.destinations.gtm
|
|
5953
6089
|
);
|
|
6090
|
+
if (!enabled)
|
|
6091
|
+
diagnostic({
|
|
6092
|
+
level: "debug",
|
|
6093
|
+
code: !options.config.enabled ? "MARKETING_DISABLED" : options.measurementAllowed === false ? "MEASUREMENT_NOT_ALLOWED" : !target ? "SERVER_RENDER" : !matchesCanonicalMarketingHost(
|
|
6094
|
+
options.canonicalDomain,
|
|
6095
|
+
target.location.hostname
|
|
6096
|
+
) ? "DOMAIN_NOT_ALLOWED" : "PROVIDER_NOT_CONFIGURED",
|
|
6097
|
+
message: "Measurement is inactive; no events are emitted"
|
|
6098
|
+
});
|
|
5954
6099
|
if (enabled) {
|
|
6100
|
+
const guard = (provider, action) => {
|
|
6101
|
+
try {
|
|
6102
|
+
action();
|
|
6103
|
+
} catch {
|
|
6104
|
+
diagnostic({
|
|
6105
|
+
level: "warning",
|
|
6106
|
+
code: "RUNTIME_INITIALIZATION_FAILED",
|
|
6107
|
+
message: `${provider}: initialization failed`
|
|
6108
|
+
});
|
|
6109
|
+
}
|
|
6110
|
+
};
|
|
6111
|
+
const scriptError = (provider) => () => diagnostic({
|
|
6112
|
+
level: "warning",
|
|
6113
|
+
code: "SCRIPT_LOAD_FAILED",
|
|
6114
|
+
message: `${provider}: script could not load (network, CSP or blocker); external reception is unverified`
|
|
6115
|
+
});
|
|
5955
6116
|
try {
|
|
5956
6117
|
if (options.config.destinations.ga4) {
|
|
5957
|
-
|
|
6118
|
+
const { measurementId } = options.config.destinations.ga4;
|
|
6119
|
+
guard("GA4", () => prepareGa4(measurementId));
|
|
5958
6120
|
}
|
|
5959
6121
|
if (options.config.destinations.meta) {
|
|
5960
|
-
|
|
6122
|
+
const { pixelId } = options.config.destinations.meta;
|
|
6123
|
+
guard("Meta", () => prepareMeta(pixelId));
|
|
5961
6124
|
}
|
|
5962
6125
|
if (options.config.destinations.clarity) {
|
|
5963
|
-
|
|
6126
|
+
const { projectId } = options.config.destinations.clarity;
|
|
6127
|
+
guard("Clarity", () => prepareClarity(projectId));
|
|
6128
|
+
}
|
|
6129
|
+
if (options.config.destinations.gtm) {
|
|
6130
|
+
const { containerId } = options.config.destinations.gtm;
|
|
6131
|
+
guard("GTM", () => prepareGtm(containerId));
|
|
5964
6132
|
}
|
|
5965
6133
|
cancelScheduledLoad = scheduleScriptLoad(() => {
|
|
5966
6134
|
if (destroyed) return;
|
|
5967
6135
|
if (options.config.destinations.ga4) {
|
|
5968
|
-
|
|
6136
|
+
const { measurementId } = options.config.destinations.ga4;
|
|
6137
|
+
guard("GA4", () => loadGa4Script(measurementId, scriptError("GA4")));
|
|
5969
6138
|
}
|
|
5970
|
-
if (options.config.destinations.meta)
|
|
6139
|
+
if (options.config.destinations.meta)
|
|
6140
|
+
guard("Meta", () => loadMetaScript(scriptError("Meta")));
|
|
5971
6141
|
if (options.config.destinations.clarity) {
|
|
5972
|
-
|
|
6142
|
+
const { projectId } = options.config.destinations.clarity;
|
|
6143
|
+
guard(
|
|
6144
|
+
"Clarity",
|
|
6145
|
+
() => loadClarityScript(projectId, scriptError("Clarity"))
|
|
6146
|
+
);
|
|
6147
|
+
}
|
|
6148
|
+
if (options.config.destinations.gtm) {
|
|
6149
|
+
const { containerId } = options.config.destinations.gtm;
|
|
6150
|
+
guard("GTM", () => loadGtmScript(containerId, scriptError("GTM")));
|
|
5973
6151
|
}
|
|
5974
6152
|
}, options.scriptLoadStrategy);
|
|
5975
6153
|
captureWebsiteMarketingAttribution(
|
|
@@ -5991,11 +6169,11 @@ function createWebsiteMarketingRuntime(options) {
|
|
|
5991
6169
|
if (options.config.destinations.meta) {
|
|
5992
6170
|
refreshWebsiteMarketingMetaContext(options.websiteId);
|
|
5993
6171
|
}
|
|
5994
|
-
} catch
|
|
6172
|
+
} catch {
|
|
5995
6173
|
options.diagnostic?.({
|
|
5996
6174
|
level: "warning",
|
|
5997
6175
|
code: "RUNTIME_INITIALIZATION_FAILED",
|
|
5998
|
-
message:
|
|
6176
|
+
message: "Marketing initialization failed; external reception is unverified"
|
|
5999
6177
|
});
|
|
6000
6178
|
}
|
|
6001
6179
|
}
|
|
@@ -6003,41 +6181,99 @@ function createWebsiteMarketingRuntime(options) {
|
|
|
6003
6181
|
enabled,
|
|
6004
6182
|
track(input) {
|
|
6005
6183
|
if (!enabled || destroyed) return;
|
|
6184
|
+
if (!isBrowserMarketingEvent(input.eventName)) {
|
|
6185
|
+
diagnostic({
|
|
6186
|
+
level: "warning",
|
|
6187
|
+
code: "INVALID_EVENT",
|
|
6188
|
+
message: "Unknown or server-owned event rejected"
|
|
6189
|
+
});
|
|
6190
|
+
return;
|
|
6191
|
+
}
|
|
6192
|
+
try {
|
|
6193
|
+
captureWebsiteMarketingAttribution(
|
|
6194
|
+
options.websiteId,
|
|
6195
|
+
options.config.attributionWindowDays
|
|
6196
|
+
);
|
|
6197
|
+
} catch {
|
|
6198
|
+
}
|
|
6006
6199
|
const event = {
|
|
6007
6200
|
...input,
|
|
6008
6201
|
schemaVersion: 1,
|
|
6009
|
-
eventId: input.eventId ??
|
|
6202
|
+
eventId: input.eventId ?? newMarketingEventId(),
|
|
6010
6203
|
occurredAt: input.occurredAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
6011
6204
|
currency: input.currency ?? options.config.currencyCode,
|
|
6012
6205
|
pageLocation: sanitizeMarketingUrl(
|
|
6013
6206
|
input.pageLocation ?? (typeof window === "undefined" ? "" : window.location.href)
|
|
6014
6207
|
)
|
|
6015
6208
|
};
|
|
6016
|
-
|
|
6017
|
-
if (
|
|
6018
|
-
|
|
6209
|
+
if (isInteractionEvent(event.eventName)) {
|
|
6210
|
+
if (!interactionParameters(event) || !/^[a-zA-Z0-9_-]{1,100}$/.test(event.eventId)) {
|
|
6211
|
+
diagnostic({
|
|
6212
|
+
level: "warning",
|
|
6213
|
+
code: "INVALID_INTERACTION",
|
|
6214
|
+
message: "Interaction rejected by the public contract",
|
|
6215
|
+
eventName: event.eventName
|
|
6216
|
+
});
|
|
6217
|
+
return;
|
|
6019
6218
|
}
|
|
6020
|
-
|
|
6021
|
-
|
|
6022
|
-
|
|
6219
|
+
for (const key of [
|
|
6220
|
+
"currency",
|
|
6221
|
+
"value",
|
|
6222
|
+
"searchTerm",
|
|
6223
|
+
"itemListId",
|
|
6224
|
+
"itemListName",
|
|
6225
|
+
"shippingTier",
|
|
6226
|
+
"paymentType",
|
|
6227
|
+
"contactMethod",
|
|
6228
|
+
"contactPlacement",
|
|
6229
|
+
"items",
|
|
6230
|
+
"pageTitle"
|
|
6231
|
+
])
|
|
6232
|
+
delete event[key];
|
|
6233
|
+
}
|
|
6234
|
+
const identity = `${event.eventName}:${event.eventId}`;
|
|
6235
|
+
if (dispatched.has(identity)) return;
|
|
6236
|
+
dispatched.add(identity);
|
|
6237
|
+
const oldest = dispatched.values().next().value;
|
|
6238
|
+
if (dispatched.size > 1e3 && oldest) dispatched.delete(oldest);
|
|
6239
|
+
const dispatch = (provider, send) => {
|
|
6240
|
+
try {
|
|
6241
|
+
send();
|
|
6242
|
+
diagnostic({
|
|
6243
|
+
level: "debug",
|
|
6244
|
+
code: "EVENT_DISPATCHED",
|
|
6245
|
+
message: `${provider}: passed to adapter; external reception is unverified`,
|
|
6246
|
+
eventName: event.eventName
|
|
6247
|
+
});
|
|
6248
|
+
} catch {
|
|
6249
|
+
diagnostic({
|
|
6250
|
+
level: "warning",
|
|
6251
|
+
code: "EVENT_DISPATCH_FAILED",
|
|
6252
|
+
message: `${provider}: adapter failed`,
|
|
6253
|
+
eventName: event.eventName
|
|
6254
|
+
});
|
|
6023
6255
|
}
|
|
6024
|
-
|
|
6025
|
-
|
|
6026
|
-
|
|
6027
|
-
|
|
6028
|
-
|
|
6029
|
-
|
|
6030
|
-
|
|
6031
|
-
|
|
6032
|
-
|
|
6033
|
-
|
|
6034
|
-
|
|
6035
|
-
|
|
6036
|
-
|
|
6256
|
+
};
|
|
6257
|
+
if (options.config.destinations.ga4) {
|
|
6258
|
+
const ga4 = options.config.destinations.ga4;
|
|
6259
|
+
dispatch(
|
|
6260
|
+
"GA4",
|
|
6261
|
+
() => sendGa4(event, ga4.diagnosticMode, ga4.measurementId)
|
|
6262
|
+
);
|
|
6263
|
+
}
|
|
6264
|
+
if (options.config.destinations.meta) {
|
|
6265
|
+
const meta = options.config.destinations.meta;
|
|
6266
|
+
dispatch("Meta", () => sendMeta(event, meta.pixelId));
|
|
6267
|
+
}
|
|
6268
|
+
if (options.config.destinations.clarity) {
|
|
6269
|
+
dispatch("Clarity", () => sendClarityEvent(event.eventName));
|
|
6270
|
+
}
|
|
6271
|
+
if (options.config.destinations.gtm && /^GTM-[A-Z0-9]{4,32}$/.test(options.config.destinations.gtm.containerId)) {
|
|
6272
|
+
dispatch("GTM", () => sendGtm(event));
|
|
6037
6273
|
}
|
|
6038
6274
|
},
|
|
6039
6275
|
captureAttribution() {
|
|
6040
|
-
if (!enabled) return null;
|
|
6276
|
+
if (!enabled || destroyed) return null;
|
|
6041
6277
|
captureWebsiteMarketingAttribution(
|
|
6042
6278
|
options.websiteId,
|
|
6043
6279
|
options.config.attributionWindowDays
|
|
@@ -6048,7 +6284,7 @@ function createWebsiteMarketingRuntime(options) {
|
|
|
6048
6284
|
return getWebsiteMarketingAttribution(options.websiteId);
|
|
6049
6285
|
},
|
|
6050
6286
|
getAttribution() {
|
|
6051
|
-
if (!enabled) return null;
|
|
6287
|
+
if (!enabled || destroyed) return null;
|
|
6052
6288
|
if (options.config.destinations.meta) {
|
|
6053
6289
|
refreshWebsiteMarketingMetaContext(options.websiteId);
|
|
6054
6290
|
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { WebsiteMarketingBrowserEvent, WebsiteMarketingRuntimeOptions } from "./types";
|
|
2
|
+
type TrackingFunction = (...args: unknown[]) => void;
|
|
3
|
+
type MetaTrackingFunction = TrackingFunction & {
|
|
4
|
+
callMethod?: TrackingFunction;
|
|
5
|
+
push?: TrackingFunction;
|
|
6
|
+
loaded?: boolean;
|
|
7
|
+
version?: string;
|
|
8
|
+
queue?: unknown[];
|
|
9
|
+
};
|
|
10
|
+
type MarketingWindow = Window & {
|
|
11
|
+
dataLayer?: unknown[];
|
|
12
|
+
gtag?: TrackingFunction;
|
|
13
|
+
fbq?: MetaTrackingFunction;
|
|
14
|
+
_fbq?: MetaTrackingFunction;
|
|
15
|
+
requestIdleCallback?: (callback: () => void, options?: {
|
|
16
|
+
timeout: number;
|
|
17
|
+
}) => number;
|
|
18
|
+
cancelIdleCallback?: (handle: number) => void;
|
|
19
|
+
};
|
|
20
|
+
export declare function marketingWindow(): MarketingWindow | null;
|
|
21
|
+
export declare function prepareGa4(measurementId: string): void;
|
|
22
|
+
export declare function loadGa4Script(measurementId: string, onError: () => void): void;
|
|
23
|
+
export declare function captureGa4ClientId(measurementId: string, websiteId: string, diagnostic?: WebsiteMarketingRuntimeOptions["diagnostic"]): void;
|
|
24
|
+
export declare function captureGa4SessionId(measurementId: string, websiteId: string, diagnostic?: WebsiteMarketingRuntimeOptions["diagnostic"]): void;
|
|
25
|
+
export declare function prepareMeta(pixelId: string): void;
|
|
26
|
+
export declare function loadMetaScript(onError: () => void): void;
|
|
27
|
+
export declare function scheduleScriptLoad(load: () => void, strategy: WebsiteMarketingRuntimeOptions["scriptLoadStrategy"]): () => void;
|
|
28
|
+
export declare function sendGa4(event: WebsiteMarketingBrowserEvent, diagnosticMode: boolean, measurementId: string): void;
|
|
29
|
+
export declare function sendMeta(event: WebsiteMarketingBrowserEvent, pixelId: string): void;
|
|
30
|
+
export {};
|
package/marketing/clarity.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
export declare function prepareClarity(projectId: string): void;
|
|
2
|
-
export declare function loadClarityScript(projectId: string): void;
|
|
2
|
+
export declare function loadClarityScript(projectId: string, onError?: () => void): void;
|
|
3
3
|
export declare function sendClarityEvent(eventName: string): void;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { WebsiteFormResponseResult } from "../types";
|
|
2
|
+
import { type WebsiteFormErrorCategory } from "./interactions";
|
|
3
|
+
import type { WebsiteMarketingRuntime } from "./types";
|
|
4
|
+
/** One instance per mounted form. No answers, storage or provider listeners. */
|
|
5
|
+
export declare function createFormInteractionTracker(getRuntime: () => WebsiteMarketingRuntime, formKey: string): {
|
|
6
|
+
setVisible(next: boolean): void;
|
|
7
|
+
start(): void;
|
|
8
|
+
stepComplete(stepKey: string): void;
|
|
9
|
+
beginSubmit(): boolean;
|
|
10
|
+
fail(errorCategory: WebsiteFormErrorCategory): void;
|
|
11
|
+
complete(result: WebsiteFormResponseResult | void): void;
|
|
12
|
+
};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { WebsiteMarketingBrowserEvent } from "./types";
|
|
2
|
+
/** Preserve the shared Google queue, including an existing native gtag.js. */
|
|
3
|
+
export declare function prepareGtm(containerId: string): void;
|
|
4
|
+
export declare function loadGtmScript(containerId: string, onError: () => void): void;
|
|
5
|
+
/** Explicit allowlist: never spread caller input, customer data or attribution. */
|
|
6
|
+
export declare function sendGtm(event: WebsiteMarketingBrowserEvent): void;
|
package/marketing/index.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
export { captureWebsiteMarketingAttribution, getWebsiteMarketingAttribution, serializeWebsiteMarketingAttribution, } from "./attribution";
|
|
2
|
+
export type { WebsiteFormErrorCategory, WebsiteInteractionContext, WebsiteInteractionEventName, } from "./interactions";
|
|
3
|
+
export { WEBSITE_INTERACTION_EVENTS } from "./interactions";
|
|
2
4
|
export { createWebsiteMarketingRuntime } from "./runtime";
|
|
3
5
|
export { getAllowedMarketingParameters, sanitizeMarketingUrl, } from "./sanitize";
|
|
4
6
|
export type { WebsiteAdvertisingConsent, WebsiteMarketingAttribution, WebsiteMarketingBrowserEvent, WebsiteMarketingBrowserEventName, WebsiteMarketingContactPlacement, WebsiteMarketingDiagnostic, WebsiteMarketingItem, WebsiteMarketingRuntime, WebsiteMarketingRuntimeOptions, WebsiteMarketingScriptLoadStrategy, WebsiteMarketingTouchpoint, } from "./types";
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { WebsiteMarketingBrowserEvent } from "./types";
|
|
2
|
+
export declare const WEBSITE_INTERACTION_EVENTS: readonly ["cta_click", "form_open", "form_start", "form_step_complete", "form_submit_attempt", "form_submit_error", "form_submit_success"];
|
|
3
|
+
export type WebsiteInteractionEventName = (typeof WEBSITE_INTERACTION_EVENTS)[number];
|
|
4
|
+
export type WebsiteFormErrorCategory = "validation" | "network" | "rejected" | "unavailable";
|
|
5
|
+
export interface WebsiteInteractionContext {
|
|
6
|
+
/** Public definition key, never a field value. */
|
|
7
|
+
formKey?: string;
|
|
8
|
+
/** Stable technical identifiers (1–64 characters), not button text. */
|
|
9
|
+
ctaId?: string;
|
|
10
|
+
placement?: string;
|
|
11
|
+
stepKey?: string;
|
|
12
|
+
errorCategory?: WebsiteFormErrorCategory;
|
|
13
|
+
}
|
|
14
|
+
export declare function isInteractionEvent(name: string): name is WebsiteInteractionEventName;
|
|
15
|
+
export declare function isInteractionIdentifier(value: unknown): value is string;
|
|
16
|
+
export declare function isBrowserMarketingEvent(name: string): boolean;
|
|
17
|
+
/** Allowlisted projection: never spread caller fields into analytics payloads. */
|
|
18
|
+
export declare function interactionParameters(event: WebsiteMarketingBrowserEvent): Record<string, string> | null;
|
|
19
|
+
export declare function newMarketingEventId(): string;
|
package/marketing/types.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { WebsiteMarketingPublicConfig } from "../types";
|
|
2
|
+
import type { WebsiteInteractionContext, WebsiteInteractionEventName } from "./interactions";
|
|
2
3
|
export declare const MARKETING_SCHEMA_VERSION: 1;
|
|
3
|
-
export type WebsiteMarketingBrowserEventName = "page_view" | "search" | "view_item_list" | "select_item" | "view_item" | "add_to_cart" | "remove_from_cart" | "view_cart" | "begin_checkout" | "add_shipping_info" | "add_payment_info" | "contact";
|
|
4
|
+
export type WebsiteMarketingBrowserEventName = WebsiteInteractionEventName | "page_view" | "search" | "view_item_list" | "select_item" | "view_item" | "add_to_cart" | "remove_from_cart" | "view_cart" | "begin_checkout" | "add_shipping_info" | "add_payment_info" | "contact";
|
|
4
5
|
export declare const WEBSITE_MARKETING_CONTACT_PLACEMENTS: readonly ["product_detail", "floating", "header", "footer", "contact_page"];
|
|
5
6
|
export type WebsiteMarketingContactPlacement = (typeof WEBSITE_MARKETING_CONTACT_PLACEMENTS)[number];
|
|
6
7
|
export interface WebsiteMarketingItem {
|
|
@@ -15,7 +16,7 @@ export interface WebsiteMarketingItem {
|
|
|
15
16
|
itemVariant?: string | null;
|
|
16
17
|
index?: number;
|
|
17
18
|
}
|
|
18
|
-
export interface WebsiteMarketingBrowserEvent {
|
|
19
|
+
export interface WebsiteMarketingBrowserEvent extends WebsiteInteractionContext {
|
|
19
20
|
schemaVersion: typeof MARKETING_SCHEMA_VERSION;
|
|
20
21
|
eventId: string;
|
|
21
22
|
eventName: WebsiteMarketingBrowserEventName;
|
|
@@ -88,6 +89,8 @@ export type WebsiteMarketingDiagnostic = {
|
|
|
88
89
|
};
|
|
89
90
|
export type WebsiteMarketingScriptLoadStrategy = "immediate" | "idle";
|
|
90
91
|
export interface WebsiteMarketingRuntimeOptions {
|
|
92
|
+
/** Host consent integration: false prevents scripts, events and attribution. */
|
|
93
|
+
measurementAllowed?: boolean;
|
|
91
94
|
websiteId: string;
|
|
92
95
|
config: WebsiteMarketingPublicConfig;
|
|
93
96
|
/**
|