@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/index.cjs CHANGED
@@ -394,7 +394,8 @@ async function getWebsitePublicConfig(options) {
394
394
  pixelId: marketing.destinations.meta.pixel_id,
395
395
  diagnosticMode: marketing.destinations.meta.diagnostic_mode ?? false
396
396
  } : null,
397
- clarity: marketing.destinations.clarity ? { projectId: marketing.destinations.clarity.project_id } : null
397
+ clarity: marketing.destinations.clarity ? { projectId: marketing.destinations.clarity.project_id } : null,
398
+ ...marketing.destinations.gtm ? { gtm: { containerId: marketing.destinations.gtm.container_id } } : {}
398
399
  }
399
400
  },
400
401
  legal: {
@@ -2957,6 +2958,9 @@ var ALLOWED_MARKETING_PARAMETERS = /* @__PURE__ */ new Set([
2957
2958
  function sanitizeMarketingUrl(input, baseUrl) {
2958
2959
  try {
2959
2960
  const url = new URL(input, baseUrl);
2961
+ if (url.protocol !== "http:" && url.protocol !== "https:") return "";
2962
+ url.username = "";
2963
+ url.password = "";
2960
2964
  const safeParameters = new URLSearchParams();
2961
2965
  for (const [key, value] of url.searchParams.entries()) {
2962
2966
  if (ALLOWED_MARKETING_PARAMETERS.has(key.toLowerCase()) && !SENSITIVE_PARAMETER_PATTERNS.some((pattern) => pattern.test(key))) {
@@ -5732,50 +5736,65 @@ function createContentModule(graphqlUrl, getHeaders, getContext, storageUrl) {
5732
5736
  };
5733
5737
  }
5734
5738
 
5735
- // libs/sdk/src/marketing/clarity.ts
5736
- var initializedDestinations = /* @__PURE__ */ new Set();
5737
- function clarityWindow() {
5738
- return typeof window === "undefined" ? null : window;
5739
- }
5740
- function prepareClarity(projectId) {
5741
- const target = clarityWindow();
5742
- if (!target || initializedDestinations.has(projectId)) return;
5743
- if (!target.clarity) {
5744
- const clarity = function() {
5745
- clarity.q ??= [];
5746
- clarity.q.push(arguments);
5747
- };
5748
- target.clarity = clarity;
5739
+ // libs/sdk/src/marketing/interactions.ts
5740
+ var WEBSITE_INTERACTION_EVENTS = [
5741
+ "cta_click",
5742
+ "form_open",
5743
+ "form_start",
5744
+ "form_step_complete",
5745
+ "form_submit_attempt",
5746
+ "form_submit_error",
5747
+ "form_submit_success"
5748
+ ];
5749
+ function isInteractionEvent(name) {
5750
+ return WEBSITE_INTERACTION_EVENTS.some((candidate) => candidate === name);
5751
+ }
5752
+ function isInteractionIdentifier(value) {
5753
+ return typeof value === "string" && /^[a-z][a-z0-9_-]{0,63}$/.test(value);
5754
+ }
5755
+ function isBrowserMarketingEvent(name) {
5756
+ return isInteractionEvent(name) || [
5757
+ "page_view",
5758
+ "search",
5759
+ "view_item_list",
5760
+ "select_item",
5761
+ "view_item",
5762
+ "add_to_cart",
5763
+ "remove_from_cart",
5764
+ "view_cart",
5765
+ "begin_checkout",
5766
+ "add_shipping_info",
5767
+ "add_payment_info",
5768
+ "contact"
5769
+ ].includes(name);
5770
+ }
5771
+ function interactionParameters(event) {
5772
+ if (!isInteractionEvent(event.eventName)) return {};
5773
+ if (event.eventName === "cta_click" ? !isInteractionIdentifier(event.ctaId) : !isInteractionIdentifier(event.formKey))
5774
+ return null;
5775
+ if (event.eventName === "form_step_complete" && !isInteractionIdentifier(event.stepKey))
5776
+ return null;
5777
+ if (event.eventName === "form_submit_error" && !["validation", "network", "rejected", "unavailable"].includes(
5778
+ event.errorCategory ?? ""
5779
+ ))
5780
+ return null;
5781
+ const params = {};
5782
+ for (const [key, value] of Object.entries({
5783
+ form_key: event.formKey,
5784
+ cta_id: event.ctaId,
5785
+ placement: event.placement,
5786
+ step_key: event.stepKey
5787
+ })) {
5788
+ if (value === void 0) continue;
5789
+ if (!isInteractionIdentifier(value)) return null;
5790
+ params[key] = value;
5749
5791
  }
5750
- initializedDestinations.add(projectId);
5751
- }
5752
- function loadClarityScript(projectId) {
5753
- if (typeof document === "undefined") return;
5754
- const id = `bacano-clarity-${projectId}`;
5755
- if (document.getElementById(id)) return;
5756
- const script = document.createElement("script");
5757
- script.id = id;
5758
- script.async = true;
5759
- script.src = `https://www.clarity.ms/tag/${encodeURIComponent(projectId)}`;
5760
- script.referrerPolicy = "strict-origin-when-cross-origin";
5761
- document.head.appendChild(script);
5762
- }
5763
- function sendClarityEvent(eventName) {
5764
- clarityWindow()?.clarity?.("event", eventName);
5792
+ if (event.eventName === "form_submit_error" && event.errorCategory)
5793
+ params.error_category = event.errorCategory;
5794
+ return params;
5765
5795
  }
5766
-
5767
- // libs/sdk/src/marketing/domain.ts
5768
- function matchesCanonicalMarketingHost(canonicalDomain, currentHostname) {
5769
- if (canonicalDomain === void 0) return true;
5770
- if (!canonicalDomain?.trim()) return false;
5771
- try {
5772
- const configuredHost = new URL(
5773
- canonicalDomain.includes("://") ? canonicalDomain : `https://${canonicalDomain}`
5774
- ).hostname.toLowerCase();
5775
- return currentHostname.toLowerCase() === configuredHost;
5776
- } catch {
5777
- return false;
5778
- }
5796
+ function newMarketingEventId() {
5797
+ return typeof crypto !== "undefined" && "randomUUID" in crypto ? crypto.randomUUID() : `evt_${Date.now()}_${Math.random().toString(16).slice(2)}`;
5779
5798
  }
5780
5799
 
5781
5800
  // libs/sdk/src/marketing/ga4.ts
@@ -5803,19 +5822,20 @@ var WEBSITE_MARKETING_CONTACT_PLACEMENTS = [
5803
5822
  "contact_page"
5804
5823
  ];
5805
5824
 
5806
- // libs/sdk/src/marketing/runtime.ts
5825
+ // libs/sdk/src/marketing/browser-adapters.ts
5807
5826
  var initializedGa4Destinations = /* @__PURE__ */ new Set();
5808
5827
  var initializedMetaDestinations = /* @__PURE__ */ new Set();
5809
5828
  function marketingWindow() {
5810
5829
  return typeof window === "undefined" ? null : window;
5811
5830
  }
5812
- function loadScriptOnce(id, source) {
5831
+ function loadScriptOnce(id, source, onError) {
5813
5832
  if (typeof document === "undefined" || document.getElementById(id)) return;
5814
5833
  const script = document.createElement("script");
5815
5834
  script.id = id;
5816
5835
  script.async = true;
5817
5836
  script.src = source;
5818
5837
  script.referrerPolicy = "strict-origin-when-cross-origin";
5838
+ script.onerror = () => onError?.();
5819
5839
  document.head.appendChild(script);
5820
5840
  }
5821
5841
  function prepareGa4(measurementId) {
@@ -5830,10 +5850,11 @@ function prepareGa4(measurementId) {
5830
5850
  target.gtag("config", measurementId, { send_page_view: false });
5831
5851
  initializedGa4Destinations.add(measurementId);
5832
5852
  }
5833
- function loadGa4Script(measurementId) {
5853
+ function loadGa4Script(measurementId, onError) {
5834
5854
  loadScriptOnce(
5835
5855
  `bacano-ga4-${measurementId}`,
5836
- `https://www.googletagmanager.com/gtag/js?id=${encodeURIComponent(measurementId)}`
5856
+ `https://www.googletagmanager.com/gtag/js?id=${encodeURIComponent(measurementId)}`,
5857
+ onError
5837
5858
  );
5838
5859
  }
5839
5860
  function captureGa4ClientId(measurementId, websiteId, diagnostic) {
@@ -5881,10 +5902,11 @@ function prepareMeta(pixelId) {
5881
5902
  target.fbq("init", pixelId);
5882
5903
  initializedMetaDestinations.add(pixelId);
5883
5904
  }
5884
- function loadMetaScript() {
5905
+ function loadMetaScript(onError) {
5885
5906
  loadScriptOnce(
5886
5907
  "bacano-meta-pixel",
5887
- "https://connect.facebook.net/en_US/fbevents.js"
5908
+ "https://connect.facebook.net/en_US/fbevents.js",
5909
+ onError
5888
5910
  );
5889
5911
  }
5890
5912
  function scheduleScriptLoad(load, strategy) {
@@ -5948,10 +5970,12 @@ function contactPlacementFor(event) {
5948
5970
  }
5949
5971
  return placement;
5950
5972
  }
5951
- function sendGa4(event, diagnosticMode) {
5973
+ function sendGa4(event, diagnosticMode, measurementId) {
5952
5974
  const target = marketingWindow();
5953
5975
  const contactPlacement = contactPlacementFor(event);
5954
5976
  target?.gtag?.("event", event.eventName, {
5977
+ send_to: measurementId,
5978
+ ...interactionParameters(event),
5955
5979
  currency: event.currency,
5956
5980
  value: event.value,
5957
5981
  search_term: event.searchTerm,
@@ -5968,15 +5992,17 @@ function sendGa4(event, diagnosticMode) {
5968
5992
  debug_mode: diagnosticMode ? true : void 0
5969
5993
  });
5970
5994
  }
5971
- function sendMeta(event) {
5995
+ function sendMeta(event, pixelId) {
5972
5996
  const target = marketingWindow();
5973
5997
  const eventName = metaEventName(event.eventName);
5974
5998
  const isStandard = eventName !== event.eventName;
5975
5999
  const contactPlacement = contactPlacementFor(event);
5976
6000
  target?.fbq?.(
5977
- isStandard ? "track" : "trackCustom",
6001
+ isStandard ? "trackSingle" : "trackSingleCustom",
6002
+ pixelId,
5978
6003
  eventName,
5979
6004
  {
6005
+ ...interactionParameters(event),
5980
6006
  currency: event.currency,
5981
6007
  value: event.value,
5982
6008
  search_string: event.searchTerm,
@@ -5993,40 +6019,192 @@ function sendMeta(event) {
5993
6019
  { eventID: event.eventId }
5994
6020
  );
5995
6021
  }
5996
- function newEventId() {
5997
- return typeof crypto !== "undefined" && "randomUUID" in crypto ? crypto.randomUUID() : `evt_${Date.now()}_${Math.random().toString(16).slice(2)}`;
6022
+
6023
+ // libs/sdk/src/marketing/clarity.ts
6024
+ var initializedDestinations = /* @__PURE__ */ new Set();
6025
+ function clarityWindow() {
6026
+ return typeof window === "undefined" ? null : window;
6027
+ }
6028
+ function prepareClarity(projectId) {
6029
+ const target = clarityWindow();
6030
+ if (!target || initializedDestinations.has(projectId)) return;
6031
+ if (!target.clarity) {
6032
+ const clarity = function() {
6033
+ clarity.q ??= [];
6034
+ clarity.q.push(arguments);
6035
+ };
6036
+ target.clarity = clarity;
6037
+ }
6038
+ initializedDestinations.add(projectId);
6039
+ }
6040
+ function loadClarityScript(projectId, onError) {
6041
+ if (typeof document === "undefined") return;
6042
+ const id = `bacano-clarity-${projectId}`;
6043
+ if (document.getElementById(id)) return;
6044
+ const script = document.createElement("script");
6045
+ script.id = id;
6046
+ script.async = true;
6047
+ script.src = `https://www.clarity.ms/tag/${encodeURIComponent(projectId)}`;
6048
+ script.referrerPolicy = "strict-origin-when-cross-origin";
6049
+ script.onerror = () => onError?.();
6050
+ document.head.appendChild(script);
6051
+ }
6052
+ function sendClarityEvent(eventName) {
6053
+ clarityWindow()?.clarity?.("event", eventName);
6054
+ }
6055
+
6056
+ // libs/sdk/src/marketing/domain.ts
6057
+ function matchesCanonicalMarketingHost(canonicalDomain, currentHostname) {
6058
+ if (canonicalDomain === void 0) return true;
6059
+ if (!canonicalDomain?.trim()) return false;
6060
+ try {
6061
+ const configuredHost = new URL(
6062
+ canonicalDomain.includes("://") ? canonicalDomain : `https://${canonicalDomain}`
6063
+ ).hostname.toLowerCase();
6064
+ return currentHostname.toLowerCase() === configuredHost;
6065
+ } catch {
6066
+ return false;
6067
+ }
6068
+ }
6069
+
6070
+ // libs/sdk/src/marketing/gtm.ts
6071
+ var CONTAINER_ID = /^GTM-[A-Z0-9]{4,32}$/;
6072
+ var preparedQueues = /* @__PURE__ */ new WeakMap();
6073
+ function prepareGtm(containerId) {
6074
+ const target = marketingWindow();
6075
+ if (!target || !CONTAINER_ID.test(containerId)) return;
6076
+ target.dataLayer ??= [];
6077
+ const containers = preparedQueues.get(target.dataLayer) ?? /* @__PURE__ */ new Set();
6078
+ if (containers.has(containerId)) return;
6079
+ target.dataLayer.push({ "gtm.start": Date.now(), event: "gtm.js" });
6080
+ containers.add(containerId);
6081
+ preparedQueues.set(target.dataLayer, containers);
6082
+ }
6083
+ function loadGtmScript(containerId, onError) {
6084
+ const target = marketingWindow();
6085
+ if (!target || typeof document === "undefined" || !CONTAINER_ID.test(containerId))
6086
+ return;
6087
+ const id = `bacano-gtm-${containerId}`;
6088
+ if (document.getElementById(id)) return;
6089
+ prepareGtm(containerId);
6090
+ const script = document.createElement("script");
6091
+ script.id = id;
6092
+ script.async = true;
6093
+ script.src = `https://www.googletagmanager.com/gtm.js?id=${encodeURIComponent(containerId)}`;
6094
+ script.referrerPolicy = "strict-origin-when-cross-origin";
6095
+ script.onerror = () => onError();
6096
+ document.head.appendChild(script);
6097
+ }
6098
+ function sendGtm(event) {
6099
+ const target = marketingWindow();
6100
+ if (!target?.dataLayer) return;
6101
+ target.dataLayer.push({ bacano: null });
6102
+ target.dataLayer.push({
6103
+ event: `bacano_${event.eventName}`,
6104
+ bacano: {
6105
+ schema_version: event.schemaVersion,
6106
+ event_name: event.eventName,
6107
+ event_id: event.eventId,
6108
+ ...interactionParameters(event),
6109
+ page_location: event.pageLocation,
6110
+ currency: event.currency,
6111
+ value: event.value,
6112
+ items: mapGa4Items(event.items),
6113
+ item_list_id: event.itemListId,
6114
+ item_list_name: event.itemListName,
6115
+ shipping_tier: event.shippingTier,
6116
+ payment_type: event.paymentType,
6117
+ method: event.contactMethod,
6118
+ contact_placement: event.eventName === "contact" && WEBSITE_MARKETING_CONTACT_PLACEMENTS.some(
6119
+ (value) => value === event.contactPlacement
6120
+ ) ? event.contactPlacement : void 0
6121
+ }
6122
+ });
5998
6123
  }
6124
+
6125
+ // libs/sdk/src/marketing/runtime.ts
5999
6126
  function createWebsiteMarketingRuntime(options) {
6000
6127
  let destroyed = false;
6128
+ const dispatched = /* @__PURE__ */ new Set();
6129
+ const observer = options.diagnostic;
6130
+ const diagnostic = (value) => {
6131
+ try {
6132
+ observer?.(value);
6133
+ } catch {
6134
+ }
6135
+ };
6136
+ options = { ...options, diagnostic };
6001
6137
  let cancelScheduledLoad = () => void 0;
6002
6138
  const target = marketingWindow();
6003
- const enabled = options.config.enabled && Boolean(
6139
+ const enabled = options.config.enabled && options.measurementAllowed !== false && Boolean(
6004
6140
  target && matchesCanonicalMarketingHost(
6005
6141
  options.canonicalDomain,
6006
6142
  target.location.hostname
6007
6143
  )
6008
6144
  ) && Boolean(
6009
- options.config.destinations.ga4 || options.config.destinations.meta || options.config.destinations.clarity
6145
+ options.config.destinations.ga4 || options.config.destinations.meta || options.config.destinations.clarity || options.config.destinations.gtm
6010
6146
  );
6147
+ if (!enabled)
6148
+ diagnostic({
6149
+ level: "debug",
6150
+ code: !options.config.enabled ? "MARKETING_DISABLED" : options.measurementAllowed === false ? "MEASUREMENT_NOT_ALLOWED" : !target ? "SERVER_RENDER" : !matchesCanonicalMarketingHost(
6151
+ options.canonicalDomain,
6152
+ target.location.hostname
6153
+ ) ? "DOMAIN_NOT_ALLOWED" : "PROVIDER_NOT_CONFIGURED",
6154
+ message: "Measurement is inactive; no events are emitted"
6155
+ });
6011
6156
  if (enabled) {
6157
+ const guard = (provider, action) => {
6158
+ try {
6159
+ action();
6160
+ } catch {
6161
+ diagnostic({
6162
+ level: "warning",
6163
+ code: "RUNTIME_INITIALIZATION_FAILED",
6164
+ message: `${provider}: initialization failed`
6165
+ });
6166
+ }
6167
+ };
6168
+ const scriptError = (provider) => () => diagnostic({
6169
+ level: "warning",
6170
+ code: "SCRIPT_LOAD_FAILED",
6171
+ message: `${provider}: script could not load (network, CSP or blocker); external reception is unverified`
6172
+ });
6012
6173
  try {
6013
6174
  if (options.config.destinations.ga4) {
6014
- prepareGa4(options.config.destinations.ga4.measurementId);
6175
+ const { measurementId } = options.config.destinations.ga4;
6176
+ guard("GA4", () => prepareGa4(measurementId));
6015
6177
  }
6016
6178
  if (options.config.destinations.meta) {
6017
- prepareMeta(options.config.destinations.meta.pixelId);
6179
+ const { pixelId } = options.config.destinations.meta;
6180
+ guard("Meta", () => prepareMeta(pixelId));
6018
6181
  }
6019
6182
  if (options.config.destinations.clarity) {
6020
- prepareClarity(options.config.destinations.clarity.projectId);
6183
+ const { projectId } = options.config.destinations.clarity;
6184
+ guard("Clarity", () => prepareClarity(projectId));
6185
+ }
6186
+ if (options.config.destinations.gtm) {
6187
+ const { containerId } = options.config.destinations.gtm;
6188
+ guard("GTM", () => prepareGtm(containerId));
6021
6189
  }
6022
6190
  cancelScheduledLoad = scheduleScriptLoad(() => {
6023
6191
  if (destroyed) return;
6024
6192
  if (options.config.destinations.ga4) {
6025
- loadGa4Script(options.config.destinations.ga4.measurementId);
6193
+ const { measurementId } = options.config.destinations.ga4;
6194
+ guard("GA4", () => loadGa4Script(measurementId, scriptError("GA4")));
6026
6195
  }
6027
- if (options.config.destinations.meta) loadMetaScript();
6196
+ if (options.config.destinations.meta)
6197
+ guard("Meta", () => loadMetaScript(scriptError("Meta")));
6028
6198
  if (options.config.destinations.clarity) {
6029
- loadClarityScript(options.config.destinations.clarity.projectId);
6199
+ const { projectId } = options.config.destinations.clarity;
6200
+ guard(
6201
+ "Clarity",
6202
+ () => loadClarityScript(projectId, scriptError("Clarity"))
6203
+ );
6204
+ }
6205
+ if (options.config.destinations.gtm) {
6206
+ const { containerId } = options.config.destinations.gtm;
6207
+ guard("GTM", () => loadGtmScript(containerId, scriptError("GTM")));
6030
6208
  }
6031
6209
  }, options.scriptLoadStrategy);
6032
6210
  captureWebsiteMarketingAttribution(
@@ -6048,11 +6226,11 @@ function createWebsiteMarketingRuntime(options) {
6048
6226
  if (options.config.destinations.meta) {
6049
6227
  refreshWebsiteMarketingMetaContext(options.websiteId);
6050
6228
  }
6051
- } catch (error) {
6229
+ } catch {
6052
6230
  options.diagnostic?.({
6053
6231
  level: "warning",
6054
6232
  code: "RUNTIME_INITIALIZATION_FAILED",
6055
- message: error instanceof Error ? error.message : "Initialization failed"
6233
+ message: "Marketing initialization failed; external reception is unverified"
6056
6234
  });
6057
6235
  }
6058
6236
  }
@@ -6060,41 +6238,99 @@ function createWebsiteMarketingRuntime(options) {
6060
6238
  enabled,
6061
6239
  track(input) {
6062
6240
  if (!enabled || destroyed) return;
6241
+ if (!isBrowserMarketingEvent(input.eventName)) {
6242
+ diagnostic({
6243
+ level: "warning",
6244
+ code: "INVALID_EVENT",
6245
+ message: "Unknown or server-owned event rejected"
6246
+ });
6247
+ return;
6248
+ }
6249
+ try {
6250
+ captureWebsiteMarketingAttribution(
6251
+ options.websiteId,
6252
+ options.config.attributionWindowDays
6253
+ );
6254
+ } catch {
6255
+ }
6063
6256
  const event = {
6064
6257
  ...input,
6065
6258
  schemaVersion: 1,
6066
- eventId: input.eventId ?? newEventId(),
6259
+ eventId: input.eventId ?? newMarketingEventId(),
6067
6260
  occurredAt: input.occurredAt ?? (/* @__PURE__ */ new Date()).toISOString(),
6068
6261
  currency: input.currency ?? options.config.currencyCode,
6069
6262
  pageLocation: sanitizeMarketingUrl(
6070
6263
  input.pageLocation ?? (typeof window === "undefined" ? "" : window.location.href)
6071
6264
  )
6072
6265
  };
6073
- try {
6074
- if (options.config.destinations.ga4) {
6075
- sendGa4(event, options.config.destinations.ga4.diagnosticMode);
6266
+ if (isInteractionEvent(event.eventName)) {
6267
+ if (!interactionParameters(event) || !/^[a-zA-Z0-9_-]{1,100}$/.test(event.eventId)) {
6268
+ diagnostic({
6269
+ level: "warning",
6270
+ code: "INVALID_INTERACTION",
6271
+ message: "Interaction rejected by the public contract",
6272
+ eventName: event.eventName
6273
+ });
6274
+ return;
6076
6275
  }
6077
- if (options.config.destinations.meta) sendMeta(event);
6078
- if (options.config.destinations.clarity) {
6079
- sendClarityEvent(event.eventName);
6276
+ for (const key of [
6277
+ "currency",
6278
+ "value",
6279
+ "searchTerm",
6280
+ "itemListId",
6281
+ "itemListName",
6282
+ "shippingTier",
6283
+ "paymentType",
6284
+ "contactMethod",
6285
+ "contactPlacement",
6286
+ "items",
6287
+ "pageTitle"
6288
+ ])
6289
+ delete event[key];
6290
+ }
6291
+ const identity = `${event.eventName}:${event.eventId}`;
6292
+ if (dispatched.has(identity)) return;
6293
+ dispatched.add(identity);
6294
+ const oldest = dispatched.values().next().value;
6295
+ if (dispatched.size > 1e3 && oldest) dispatched.delete(oldest);
6296
+ const dispatch = (provider, send) => {
6297
+ try {
6298
+ send();
6299
+ diagnostic({
6300
+ level: "debug",
6301
+ code: "EVENT_DISPATCHED",
6302
+ message: `${provider}: passed to adapter; external reception is unverified`,
6303
+ eventName: event.eventName
6304
+ });
6305
+ } catch {
6306
+ diagnostic({
6307
+ level: "warning",
6308
+ code: "EVENT_DISPATCH_FAILED",
6309
+ message: `${provider}: adapter failed`,
6310
+ eventName: event.eventName
6311
+ });
6080
6312
  }
6081
- options.diagnostic?.({
6082
- level: "debug",
6083
- code: "EVENT_DISPATCHED",
6084
- message: "Marketing event dispatched",
6085
- eventName: event.eventName
6086
- });
6087
- } catch (error) {
6088
- options.diagnostic?.({
6089
- level: "warning",
6090
- code: "EVENT_DISPATCH_FAILED",
6091
- message: error instanceof Error ? error.message : "Dispatch failed",
6092
- eventName: event.eventName
6093
- });
6313
+ };
6314
+ if (options.config.destinations.ga4) {
6315
+ const ga4 = options.config.destinations.ga4;
6316
+ dispatch(
6317
+ "GA4",
6318
+ () => sendGa4(event, ga4.diagnosticMode, ga4.measurementId)
6319
+ );
6320
+ }
6321
+ if (options.config.destinations.meta) {
6322
+ const meta = options.config.destinations.meta;
6323
+ dispatch("Meta", () => sendMeta(event, meta.pixelId));
6324
+ }
6325
+ if (options.config.destinations.clarity) {
6326
+ dispatch("Clarity", () => sendClarityEvent(event.eventName));
6327
+ }
6328
+ if (options.config.destinations.gtm && /^GTM-[A-Z0-9]{4,32}$/.test(options.config.destinations.gtm.containerId)) {
6329
+ dispatch("GTM", () => sendGtm(event));
6094
6330
  }
6095
6331
  },
6096
6332
  captureAttribution() {
6097
- if (!enabled) return null;
6333
+ if (!enabled || destroyed) return null;
6098
6334
  captureWebsiteMarketingAttribution(
6099
6335
  options.websiteId,
6100
6336
  options.config.attributionWindowDays
@@ -6105,7 +6341,7 @@ function createWebsiteMarketingRuntime(options) {
6105
6341
  return getWebsiteMarketingAttribution(options.websiteId);
6106
6342
  },
6107
6343
  getAttribution() {
6108
- if (!enabled) return null;
6344
+ if (!enabled || destroyed) return null;
6109
6345
  if (options.config.destinations.meta) {
6110
6346
  refreshWebsiteMarketingMetaContext(options.websiteId);
6111
6347
  }