@bacano/sdk 1.1.20 → 1.2.1
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/STOREFRONT_FORMS.md +7 -0
- package/STOREFRONT_MARKETING.md +103 -4
- package/index.cjs +253 -84
- package/index.js +253 -84
- 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/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 +342 -134
- package/react.d.ts +1 -0
- package/react.js +325 -117
- 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/index.js
CHANGED
|
@@ -2900,6 +2900,9 @@ var ALLOWED_MARKETING_PARAMETERS = /* @__PURE__ */ new Set([
|
|
|
2900
2900
|
function sanitizeMarketingUrl(input, baseUrl) {
|
|
2901
2901
|
try {
|
|
2902
2902
|
const url = new URL(input, baseUrl);
|
|
2903
|
+
if (url.protocol !== "http:" && url.protocol !== "https:") return "";
|
|
2904
|
+
url.username = "";
|
|
2905
|
+
url.password = "";
|
|
2903
2906
|
const safeParameters = new URLSearchParams();
|
|
2904
2907
|
for (const [key, value] of url.searchParams.entries()) {
|
|
2905
2908
|
if (ALLOWED_MARKETING_PARAMETERS.has(key.toLowerCase()) && !SENSITIVE_PARAMETER_PATTERNS.some((pattern) => pattern.test(key))) {
|
|
@@ -5675,50 +5678,65 @@ function createContentModule(graphqlUrl, getHeaders, getContext, storageUrl) {
|
|
|
5675
5678
|
};
|
|
5676
5679
|
}
|
|
5677
5680
|
|
|
5678
|
-
// libs/sdk/src/marketing/
|
|
5679
|
-
var
|
|
5680
|
-
|
|
5681
|
-
|
|
5682
|
-
|
|
5683
|
-
|
|
5684
|
-
|
|
5685
|
-
|
|
5686
|
-
|
|
5687
|
-
|
|
5688
|
-
|
|
5689
|
-
|
|
5690
|
-
|
|
5691
|
-
|
|
5681
|
+
// libs/sdk/src/marketing/interactions.ts
|
|
5682
|
+
var WEBSITE_INTERACTION_EVENTS = [
|
|
5683
|
+
"cta_click",
|
|
5684
|
+
"form_open",
|
|
5685
|
+
"form_start",
|
|
5686
|
+
"form_step_complete",
|
|
5687
|
+
"form_submit_attempt",
|
|
5688
|
+
"form_submit_error",
|
|
5689
|
+
"form_submit_success"
|
|
5690
|
+
];
|
|
5691
|
+
function isInteractionEvent(name) {
|
|
5692
|
+
return WEBSITE_INTERACTION_EVENTS.some((candidate) => candidate === name);
|
|
5693
|
+
}
|
|
5694
|
+
function isInteractionIdentifier(value) {
|
|
5695
|
+
return typeof value === "string" && /^[a-z][a-z0-9_-]{0,63}$/.test(value);
|
|
5696
|
+
}
|
|
5697
|
+
function isBrowserMarketingEvent(name) {
|
|
5698
|
+
return isInteractionEvent(name) || [
|
|
5699
|
+
"page_view",
|
|
5700
|
+
"search",
|
|
5701
|
+
"view_item_list",
|
|
5702
|
+
"select_item",
|
|
5703
|
+
"view_item",
|
|
5704
|
+
"add_to_cart",
|
|
5705
|
+
"remove_from_cart",
|
|
5706
|
+
"view_cart",
|
|
5707
|
+
"begin_checkout",
|
|
5708
|
+
"add_shipping_info",
|
|
5709
|
+
"add_payment_info",
|
|
5710
|
+
"contact"
|
|
5711
|
+
].includes(name);
|
|
5712
|
+
}
|
|
5713
|
+
function interactionParameters(event) {
|
|
5714
|
+
if (!isInteractionEvent(event.eventName)) return {};
|
|
5715
|
+
if (event.eventName === "cta_click" ? !isInteractionIdentifier(event.ctaId) : !isInteractionIdentifier(event.formKey))
|
|
5716
|
+
return null;
|
|
5717
|
+
if (event.eventName === "form_step_complete" && !isInteractionIdentifier(event.stepKey))
|
|
5718
|
+
return null;
|
|
5719
|
+
if (event.eventName === "form_submit_error" && !["validation", "network", "rejected", "unavailable"].includes(
|
|
5720
|
+
event.errorCategory ?? ""
|
|
5721
|
+
))
|
|
5722
|
+
return null;
|
|
5723
|
+
const params = {};
|
|
5724
|
+
for (const [key, value] of Object.entries({
|
|
5725
|
+
form_key: event.formKey,
|
|
5726
|
+
cta_id: event.ctaId,
|
|
5727
|
+
placement: event.placement,
|
|
5728
|
+
step_key: event.stepKey
|
|
5729
|
+
})) {
|
|
5730
|
+
if (value === void 0) continue;
|
|
5731
|
+
if (!isInteractionIdentifier(value)) return null;
|
|
5732
|
+
params[key] = value;
|
|
5692
5733
|
}
|
|
5693
|
-
|
|
5734
|
+
if (event.eventName === "form_submit_error" && event.errorCategory)
|
|
5735
|
+
params.error_category = event.errorCategory;
|
|
5736
|
+
return params;
|
|
5694
5737
|
}
|
|
5695
|
-
function
|
|
5696
|
-
|
|
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);
|
|
5708
|
-
}
|
|
5709
|
-
|
|
5710
|
-
// libs/sdk/src/marketing/domain.ts
|
|
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
|
-
}
|
|
5738
|
+
function newMarketingEventId() {
|
|
5739
|
+
return typeof crypto !== "undefined" && "randomUUID" in crypto ? crypto.randomUUID() : `evt_${Date.now()}_${Math.random().toString(16).slice(2)}`;
|
|
5722
5740
|
}
|
|
5723
5741
|
|
|
5724
5742
|
// libs/sdk/src/marketing/ga4.ts
|
|
@@ -5746,19 +5764,20 @@ var WEBSITE_MARKETING_CONTACT_PLACEMENTS = [
|
|
|
5746
5764
|
"contact_page"
|
|
5747
5765
|
];
|
|
5748
5766
|
|
|
5749
|
-
// libs/sdk/src/marketing/
|
|
5767
|
+
// libs/sdk/src/marketing/browser-adapters.ts
|
|
5750
5768
|
var initializedGa4Destinations = /* @__PURE__ */ new Set();
|
|
5751
5769
|
var initializedMetaDestinations = /* @__PURE__ */ new Set();
|
|
5752
5770
|
function marketingWindow() {
|
|
5753
5771
|
return typeof window === "undefined" ? null : window;
|
|
5754
5772
|
}
|
|
5755
|
-
function loadScriptOnce(id, source) {
|
|
5773
|
+
function loadScriptOnce(id, source, onError) {
|
|
5756
5774
|
if (typeof document === "undefined" || document.getElementById(id)) return;
|
|
5757
5775
|
const script = document.createElement("script");
|
|
5758
5776
|
script.id = id;
|
|
5759
5777
|
script.async = true;
|
|
5760
5778
|
script.src = source;
|
|
5761
5779
|
script.referrerPolicy = "strict-origin-when-cross-origin";
|
|
5780
|
+
script.onerror = () => onError?.();
|
|
5762
5781
|
document.head.appendChild(script);
|
|
5763
5782
|
}
|
|
5764
5783
|
function prepareGa4(measurementId) {
|
|
@@ -5773,10 +5792,11 @@ function prepareGa4(measurementId) {
|
|
|
5773
5792
|
target.gtag("config", measurementId, { send_page_view: false });
|
|
5774
5793
|
initializedGa4Destinations.add(measurementId);
|
|
5775
5794
|
}
|
|
5776
|
-
function loadGa4Script(measurementId) {
|
|
5795
|
+
function loadGa4Script(measurementId, onError) {
|
|
5777
5796
|
loadScriptOnce(
|
|
5778
5797
|
`bacano-ga4-${measurementId}`,
|
|
5779
|
-
`https://www.googletagmanager.com/gtag/js?id=${encodeURIComponent(measurementId)}
|
|
5798
|
+
`https://www.googletagmanager.com/gtag/js?id=${encodeURIComponent(measurementId)}`,
|
|
5799
|
+
onError
|
|
5780
5800
|
);
|
|
5781
5801
|
}
|
|
5782
5802
|
function captureGa4ClientId(measurementId, websiteId, diagnostic) {
|
|
@@ -5824,10 +5844,11 @@ function prepareMeta(pixelId) {
|
|
|
5824
5844
|
target.fbq("init", pixelId);
|
|
5825
5845
|
initializedMetaDestinations.add(pixelId);
|
|
5826
5846
|
}
|
|
5827
|
-
function loadMetaScript() {
|
|
5847
|
+
function loadMetaScript(onError) {
|
|
5828
5848
|
loadScriptOnce(
|
|
5829
5849
|
"bacano-meta-pixel",
|
|
5830
|
-
"https://connect.facebook.net/en_US/fbevents.js"
|
|
5850
|
+
"https://connect.facebook.net/en_US/fbevents.js",
|
|
5851
|
+
onError
|
|
5831
5852
|
);
|
|
5832
5853
|
}
|
|
5833
5854
|
function scheduleScriptLoad(load, strategy) {
|
|
@@ -5891,10 +5912,12 @@ function contactPlacementFor(event) {
|
|
|
5891
5912
|
}
|
|
5892
5913
|
return placement;
|
|
5893
5914
|
}
|
|
5894
|
-
function sendGa4(event, diagnosticMode) {
|
|
5915
|
+
function sendGa4(event, diagnosticMode, measurementId) {
|
|
5895
5916
|
const target = marketingWindow();
|
|
5896
5917
|
const contactPlacement = contactPlacementFor(event);
|
|
5897
5918
|
target?.gtag?.("event", event.eventName, {
|
|
5919
|
+
send_to: measurementId,
|
|
5920
|
+
...interactionParameters(event),
|
|
5898
5921
|
currency: event.currency,
|
|
5899
5922
|
value: event.value,
|
|
5900
5923
|
search_term: event.searchTerm,
|
|
@@ -5911,15 +5934,17 @@ function sendGa4(event, diagnosticMode) {
|
|
|
5911
5934
|
debug_mode: diagnosticMode ? true : void 0
|
|
5912
5935
|
});
|
|
5913
5936
|
}
|
|
5914
|
-
function sendMeta(event) {
|
|
5937
|
+
function sendMeta(event, pixelId) {
|
|
5915
5938
|
const target = marketingWindow();
|
|
5916
5939
|
const eventName = metaEventName(event.eventName);
|
|
5917
5940
|
const isStandard = eventName !== event.eventName;
|
|
5918
5941
|
const contactPlacement = contactPlacementFor(event);
|
|
5919
5942
|
target?.fbq?.(
|
|
5920
|
-
isStandard ? "
|
|
5943
|
+
isStandard ? "trackSingle" : "trackSingleCustom",
|
|
5944
|
+
pixelId,
|
|
5921
5945
|
eventName,
|
|
5922
5946
|
{
|
|
5947
|
+
...interactionParameters(event),
|
|
5923
5948
|
currency: event.currency,
|
|
5924
5949
|
value: event.value,
|
|
5925
5950
|
search_string: event.searchTerm,
|
|
@@ -5936,14 +5961,69 @@ function sendMeta(event) {
|
|
|
5936
5961
|
{ eventID: event.eventId }
|
|
5937
5962
|
);
|
|
5938
5963
|
}
|
|
5939
|
-
|
|
5940
|
-
|
|
5964
|
+
|
|
5965
|
+
// libs/sdk/src/marketing/clarity.ts
|
|
5966
|
+
var initializedDestinations = /* @__PURE__ */ new Set();
|
|
5967
|
+
function clarityWindow() {
|
|
5968
|
+
return typeof window === "undefined" ? null : window;
|
|
5941
5969
|
}
|
|
5970
|
+
function prepareClarity(projectId) {
|
|
5971
|
+
const target = clarityWindow();
|
|
5972
|
+
if (!target || initializedDestinations.has(projectId)) return;
|
|
5973
|
+
if (!target.clarity) {
|
|
5974
|
+
const clarity = function() {
|
|
5975
|
+
clarity.q ??= [];
|
|
5976
|
+
clarity.q.push(arguments);
|
|
5977
|
+
};
|
|
5978
|
+
target.clarity = clarity;
|
|
5979
|
+
}
|
|
5980
|
+
initializedDestinations.add(projectId);
|
|
5981
|
+
}
|
|
5982
|
+
function loadClarityScript(projectId, onError) {
|
|
5983
|
+
if (typeof document === "undefined") return;
|
|
5984
|
+
const id = `bacano-clarity-${projectId}`;
|
|
5985
|
+
if (document.getElementById(id)) return;
|
|
5986
|
+
const script = document.createElement("script");
|
|
5987
|
+
script.id = id;
|
|
5988
|
+
script.async = true;
|
|
5989
|
+
script.src = `https://www.clarity.ms/tag/${encodeURIComponent(projectId)}`;
|
|
5990
|
+
script.referrerPolicy = "strict-origin-when-cross-origin";
|
|
5991
|
+
script.onerror = () => onError?.();
|
|
5992
|
+
document.head.appendChild(script);
|
|
5993
|
+
}
|
|
5994
|
+
function sendClarityEvent(eventName) {
|
|
5995
|
+
clarityWindow()?.clarity?.("event", eventName);
|
|
5996
|
+
}
|
|
5997
|
+
|
|
5998
|
+
// libs/sdk/src/marketing/domain.ts
|
|
5999
|
+
function matchesCanonicalMarketingHost(canonicalDomain, currentHostname) {
|
|
6000
|
+
if (canonicalDomain === void 0) return true;
|
|
6001
|
+
if (!canonicalDomain?.trim()) return false;
|
|
6002
|
+
try {
|
|
6003
|
+
const configuredHost = new URL(
|
|
6004
|
+
canonicalDomain.includes("://") ? canonicalDomain : `https://${canonicalDomain}`
|
|
6005
|
+
).hostname.toLowerCase();
|
|
6006
|
+
return currentHostname.toLowerCase() === configuredHost;
|
|
6007
|
+
} catch {
|
|
6008
|
+
return false;
|
|
6009
|
+
}
|
|
6010
|
+
}
|
|
6011
|
+
|
|
6012
|
+
// libs/sdk/src/marketing/runtime.ts
|
|
5942
6013
|
function createWebsiteMarketingRuntime(options) {
|
|
5943
6014
|
let destroyed = false;
|
|
6015
|
+
const dispatched = /* @__PURE__ */ new Set();
|
|
6016
|
+
const observer = options.diagnostic;
|
|
6017
|
+
const diagnostic = (value) => {
|
|
6018
|
+
try {
|
|
6019
|
+
observer?.(value);
|
|
6020
|
+
} catch {
|
|
6021
|
+
}
|
|
6022
|
+
};
|
|
6023
|
+
options = { ...options, diagnostic };
|
|
5944
6024
|
let cancelScheduledLoad = () => void 0;
|
|
5945
6025
|
const target = marketingWindow();
|
|
5946
|
-
const enabled = options.config.enabled && Boolean(
|
|
6026
|
+
const enabled = options.config.enabled && options.measurementAllowed !== false && Boolean(
|
|
5947
6027
|
target && matchesCanonicalMarketingHost(
|
|
5948
6028
|
options.canonicalDomain,
|
|
5949
6029
|
target.location.hostname
|
|
@@ -5951,25 +6031,59 @@ function createWebsiteMarketingRuntime(options) {
|
|
|
5951
6031
|
) && Boolean(
|
|
5952
6032
|
options.config.destinations.ga4 || options.config.destinations.meta || options.config.destinations.clarity
|
|
5953
6033
|
);
|
|
6034
|
+
if (!enabled)
|
|
6035
|
+
diagnostic({
|
|
6036
|
+
level: "debug",
|
|
6037
|
+
code: !options.config.enabled ? "MARKETING_DISABLED" : options.measurementAllowed === false ? "MEASUREMENT_NOT_ALLOWED" : !target ? "SERVER_RENDER" : !matchesCanonicalMarketingHost(
|
|
6038
|
+
options.canonicalDomain,
|
|
6039
|
+
target.location.hostname
|
|
6040
|
+
) ? "DOMAIN_NOT_ALLOWED" : "PROVIDER_NOT_CONFIGURED",
|
|
6041
|
+
message: "Measurement is inactive; no events are emitted"
|
|
6042
|
+
});
|
|
5954
6043
|
if (enabled) {
|
|
6044
|
+
const guard = (provider, action) => {
|
|
6045
|
+
try {
|
|
6046
|
+
action();
|
|
6047
|
+
} catch {
|
|
6048
|
+
diagnostic({
|
|
6049
|
+
level: "warning",
|
|
6050
|
+
code: "RUNTIME_INITIALIZATION_FAILED",
|
|
6051
|
+
message: `${provider}: initialization failed`
|
|
6052
|
+
});
|
|
6053
|
+
}
|
|
6054
|
+
};
|
|
6055
|
+
const scriptError = (provider) => () => diagnostic({
|
|
6056
|
+
level: "warning",
|
|
6057
|
+
code: "SCRIPT_LOAD_FAILED",
|
|
6058
|
+
message: `${provider}: script could not load (network, CSP or blocker); external reception is unverified`
|
|
6059
|
+
});
|
|
5955
6060
|
try {
|
|
5956
6061
|
if (options.config.destinations.ga4) {
|
|
5957
|
-
|
|
6062
|
+
const { measurementId } = options.config.destinations.ga4;
|
|
6063
|
+
guard("GA4", () => prepareGa4(measurementId));
|
|
5958
6064
|
}
|
|
5959
6065
|
if (options.config.destinations.meta) {
|
|
5960
|
-
|
|
6066
|
+
const { pixelId } = options.config.destinations.meta;
|
|
6067
|
+
guard("Meta", () => prepareMeta(pixelId));
|
|
5961
6068
|
}
|
|
5962
6069
|
if (options.config.destinations.clarity) {
|
|
5963
|
-
|
|
6070
|
+
const { projectId } = options.config.destinations.clarity;
|
|
6071
|
+
guard("Clarity", () => prepareClarity(projectId));
|
|
5964
6072
|
}
|
|
5965
6073
|
cancelScheduledLoad = scheduleScriptLoad(() => {
|
|
5966
6074
|
if (destroyed) return;
|
|
5967
6075
|
if (options.config.destinations.ga4) {
|
|
5968
|
-
|
|
6076
|
+
const { measurementId } = options.config.destinations.ga4;
|
|
6077
|
+
guard("GA4", () => loadGa4Script(measurementId, scriptError("GA4")));
|
|
5969
6078
|
}
|
|
5970
|
-
if (options.config.destinations.meta)
|
|
6079
|
+
if (options.config.destinations.meta)
|
|
6080
|
+
guard("Meta", () => loadMetaScript(scriptError("Meta")));
|
|
5971
6081
|
if (options.config.destinations.clarity) {
|
|
5972
|
-
|
|
6082
|
+
const { projectId } = options.config.destinations.clarity;
|
|
6083
|
+
guard(
|
|
6084
|
+
"Clarity",
|
|
6085
|
+
() => loadClarityScript(projectId, scriptError("Clarity"))
|
|
6086
|
+
);
|
|
5973
6087
|
}
|
|
5974
6088
|
}, options.scriptLoadStrategy);
|
|
5975
6089
|
captureWebsiteMarketingAttribution(
|
|
@@ -5991,11 +6105,11 @@ function createWebsiteMarketingRuntime(options) {
|
|
|
5991
6105
|
if (options.config.destinations.meta) {
|
|
5992
6106
|
refreshWebsiteMarketingMetaContext(options.websiteId);
|
|
5993
6107
|
}
|
|
5994
|
-
} catch
|
|
6108
|
+
} catch {
|
|
5995
6109
|
options.diagnostic?.({
|
|
5996
6110
|
level: "warning",
|
|
5997
6111
|
code: "RUNTIME_INITIALIZATION_FAILED",
|
|
5998
|
-
message:
|
|
6112
|
+
message: "Marketing initialization failed; external reception is unverified"
|
|
5999
6113
|
});
|
|
6000
6114
|
}
|
|
6001
6115
|
}
|
|
@@ -6003,41 +6117,96 @@ function createWebsiteMarketingRuntime(options) {
|
|
|
6003
6117
|
enabled,
|
|
6004
6118
|
track(input) {
|
|
6005
6119
|
if (!enabled || destroyed) return;
|
|
6120
|
+
if (!isBrowserMarketingEvent(input.eventName)) {
|
|
6121
|
+
diagnostic({
|
|
6122
|
+
level: "warning",
|
|
6123
|
+
code: "INVALID_EVENT",
|
|
6124
|
+
message: "Unknown or server-owned event rejected"
|
|
6125
|
+
});
|
|
6126
|
+
return;
|
|
6127
|
+
}
|
|
6128
|
+
try {
|
|
6129
|
+
captureWebsiteMarketingAttribution(
|
|
6130
|
+
options.websiteId,
|
|
6131
|
+
options.config.attributionWindowDays
|
|
6132
|
+
);
|
|
6133
|
+
} catch {
|
|
6134
|
+
}
|
|
6006
6135
|
const event = {
|
|
6007
6136
|
...input,
|
|
6008
6137
|
schemaVersion: 1,
|
|
6009
|
-
eventId: input.eventId ??
|
|
6138
|
+
eventId: input.eventId ?? newMarketingEventId(),
|
|
6010
6139
|
occurredAt: input.occurredAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
6011
6140
|
currency: input.currency ?? options.config.currencyCode,
|
|
6012
6141
|
pageLocation: sanitizeMarketingUrl(
|
|
6013
6142
|
input.pageLocation ?? (typeof window === "undefined" ? "" : window.location.href)
|
|
6014
6143
|
)
|
|
6015
6144
|
};
|
|
6016
|
-
|
|
6017
|
-
if (
|
|
6018
|
-
|
|
6145
|
+
if (isInteractionEvent(event.eventName)) {
|
|
6146
|
+
if (!interactionParameters(event) || !/^[a-zA-Z0-9_-]{1,100}$/.test(event.eventId)) {
|
|
6147
|
+
diagnostic({
|
|
6148
|
+
level: "warning",
|
|
6149
|
+
code: "INVALID_INTERACTION",
|
|
6150
|
+
message: "Interaction rejected by the public contract",
|
|
6151
|
+
eventName: event.eventName
|
|
6152
|
+
});
|
|
6153
|
+
return;
|
|
6019
6154
|
}
|
|
6020
|
-
|
|
6021
|
-
|
|
6022
|
-
|
|
6155
|
+
for (const key of [
|
|
6156
|
+
"currency",
|
|
6157
|
+
"value",
|
|
6158
|
+
"searchTerm",
|
|
6159
|
+
"itemListId",
|
|
6160
|
+
"itemListName",
|
|
6161
|
+
"shippingTier",
|
|
6162
|
+
"paymentType",
|
|
6163
|
+
"contactMethod",
|
|
6164
|
+
"contactPlacement",
|
|
6165
|
+
"items",
|
|
6166
|
+
"pageTitle"
|
|
6167
|
+
])
|
|
6168
|
+
delete event[key];
|
|
6169
|
+
}
|
|
6170
|
+
const identity = `${event.eventName}:${event.eventId}`;
|
|
6171
|
+
if (dispatched.has(identity)) return;
|
|
6172
|
+
dispatched.add(identity);
|
|
6173
|
+
const oldest = dispatched.values().next().value;
|
|
6174
|
+
if (dispatched.size > 1e3 && oldest) dispatched.delete(oldest);
|
|
6175
|
+
const dispatch = (provider, send) => {
|
|
6176
|
+
try {
|
|
6177
|
+
send();
|
|
6178
|
+
diagnostic({
|
|
6179
|
+
level: "debug",
|
|
6180
|
+
code: "EVENT_DISPATCHED",
|
|
6181
|
+
message: `${provider}: passed to adapter; external reception is unverified`,
|
|
6182
|
+
eventName: event.eventName
|
|
6183
|
+
});
|
|
6184
|
+
} catch {
|
|
6185
|
+
diagnostic({
|
|
6186
|
+
level: "warning",
|
|
6187
|
+
code: "EVENT_DISPATCH_FAILED",
|
|
6188
|
+
message: `${provider}: adapter failed`,
|
|
6189
|
+
eventName: event.eventName
|
|
6190
|
+
});
|
|
6023
6191
|
}
|
|
6024
|
-
|
|
6025
|
-
|
|
6026
|
-
|
|
6027
|
-
|
|
6028
|
-
|
|
6029
|
-
|
|
6030
|
-
|
|
6031
|
-
|
|
6032
|
-
|
|
6033
|
-
|
|
6034
|
-
|
|
6035
|
-
|
|
6036
|
-
|
|
6192
|
+
};
|
|
6193
|
+
if (options.config.destinations.ga4) {
|
|
6194
|
+
const ga4 = options.config.destinations.ga4;
|
|
6195
|
+
dispatch(
|
|
6196
|
+
"GA4",
|
|
6197
|
+
() => sendGa4(event, ga4.diagnosticMode, ga4.measurementId)
|
|
6198
|
+
);
|
|
6199
|
+
}
|
|
6200
|
+
if (options.config.destinations.meta) {
|
|
6201
|
+
const meta = options.config.destinations.meta;
|
|
6202
|
+
dispatch("Meta", () => sendMeta(event, meta.pixelId));
|
|
6203
|
+
}
|
|
6204
|
+
if (options.config.destinations.clarity) {
|
|
6205
|
+
dispatch("Clarity", () => sendClarityEvent(event.eventName));
|
|
6037
6206
|
}
|
|
6038
6207
|
},
|
|
6039
6208
|
captureAttribution() {
|
|
6040
|
-
if (!enabled) return null;
|
|
6209
|
+
if (!enabled || destroyed) return null;
|
|
6041
6210
|
captureWebsiteMarketingAttribution(
|
|
6042
6211
|
options.websiteId,
|
|
6043
6212
|
options.config.attributionWindowDays
|
|
@@ -6048,7 +6217,7 @@ function createWebsiteMarketingRuntime(options) {
|
|
|
6048
6217
|
return getWebsiteMarketingAttribution(options.websiteId);
|
|
6049
6218
|
},
|
|
6050
6219
|
getAttribution() {
|
|
6051
|
-
if (!enabled) return null;
|
|
6220
|
+
if (!enabled || destroyed) return null;
|
|
6052
6221
|
if (options.config.destinations.meta) {
|
|
6053
6222
|
refreshWebsiteMarketingMetaContext(options.websiteId);
|
|
6054
6223
|
}
|
|
@@ -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
|
+
};
|
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
|
/**
|
package/package.json
CHANGED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { WebsiteInteractionContext } from "../marketing/interactions";
|
|
2
|
+
/** Invoke inside the existing onClick; never cancels or delays navigation. */
|
|
3
|
+
export declare function useBacanoCta(context: Pick<WebsiteInteractionContext, "formKey" | "placement"> & {
|
|
4
|
+
ctaId: string;
|
|
5
|
+
}): () => void;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { WebsiteFormField, WebsiteFormSection } from "../types";
|
|
2
|
+
import type { BacanoFormClassNames, BacanoFormValues } from "./forms";
|
|
3
|
+
export declare function FormFields(props: {
|
|
4
|
+
fields: WebsiteFormField[];
|
|
5
|
+
idPrefix: string;
|
|
6
|
+
values: BacanoFormValues;
|
|
7
|
+
classNames?: BacanoFormClassNames;
|
|
8
|
+
onChange(key: string, value: string | string[] | boolean): void;
|
|
9
|
+
}): import("react").JSX.Element[];
|
|
10
|
+
export declare function FormSections(props: {
|
|
11
|
+
sections: WebsiteFormSection[];
|
|
12
|
+
idPrefix: string;
|
|
13
|
+
values: BacanoFormValues;
|
|
14
|
+
classNames?: BacanoFormClassNames;
|
|
15
|
+
onChange(key: string, value: string | string[] | boolean): void;
|
|
16
|
+
}): import("react").JSX.Element[];
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/** Use with custom renderers; BacanoForm already owns this instrumentation. */
|
|
2
|
+
export declare function useBacanoFormTracking(formKey: string, options?: {
|
|
3
|
+
visible?: boolean;
|
|
4
|
+
}): {
|
|
5
|
+
setVisible(next: boolean): void;
|
|
6
|
+
start(): void;
|
|
7
|
+
stepComplete(stepKey: string): void;
|
|
8
|
+
beginSubmit(): boolean;
|
|
9
|
+
fail(errorCategory: import("../marketing").WebsiteFormErrorCategory): void;
|
|
10
|
+
complete(result: import("..").WebsiteFormResponseResult | void): void;
|
|
11
|
+
};
|