@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/react.cjs
CHANGED
|
@@ -33,6 +33,7 @@ __export(react_exports, {
|
|
|
33
33
|
useAttributeList: () => useAttributeList,
|
|
34
34
|
useAvailability: () => useAvailability,
|
|
35
35
|
useBacano: () => useBacano,
|
|
36
|
+
useBacanoCta: () => useBacanoCta,
|
|
36
37
|
useBacanoState: () => useBacanoState,
|
|
37
38
|
useBrands: () => useBrands,
|
|
38
39
|
useCart: () => useCart,
|
|
@@ -4149,7 +4150,7 @@ function createCatalogModule(graphqlUrl, getHeaders, getContext, storageUrl) {
|
|
|
4149
4150
|
};
|
|
4150
4151
|
}
|
|
4151
4152
|
|
|
4152
|
-
// libs/sdk/src/react/
|
|
4153
|
+
// libs/sdk/src/react/cta-tracking.ts
|
|
4153
4154
|
var import_react2 = require("react");
|
|
4154
4155
|
|
|
4155
4156
|
// libs/sdk/src/react/context-registry.ts
|
|
@@ -4178,13 +4179,6 @@ function getOrCreateReactContext(key, defaultValue) {
|
|
|
4178
4179
|
return context;
|
|
4179
4180
|
}
|
|
4180
4181
|
|
|
4181
|
-
// libs/sdk/src/react/context.ts
|
|
4182
|
-
var BacanoReactContext = getOrCreateReactContext("bacano-client-v1", {
|
|
4183
|
-
client: null,
|
|
4184
|
-
loading: true,
|
|
4185
|
-
error: null
|
|
4186
|
-
});
|
|
4187
|
-
|
|
4188
4182
|
// libs/sdk/src/react/marketing-context.ts
|
|
4189
4183
|
var NOOP_MARKETING_RUNTIME = {
|
|
4190
4184
|
enabled: false,
|
|
@@ -4198,26 +4192,55 @@ var BacanoMarketingContext = getOrCreateReactContext(
|
|
|
4198
4192
|
NOOP_MARKETING_RUNTIME
|
|
4199
4193
|
);
|
|
4200
4194
|
|
|
4195
|
+
// libs/sdk/src/react/cta-tracking.ts
|
|
4196
|
+
function useBacanoCta(context) {
|
|
4197
|
+
const runtime = (0, import_react2.useContext)(BacanoMarketingContext);
|
|
4198
|
+
const lastClick = (0, import_react2.useRef)({ identity: "", at: -Infinity });
|
|
4199
|
+
const { ctaId, formKey, placement } = context;
|
|
4200
|
+
return (0, import_react2.useCallback)(() => {
|
|
4201
|
+
if (!runtime.enabled) return;
|
|
4202
|
+
const identity = `${ctaId}:${formKey ?? ""}:${placement ?? ""}`;
|
|
4203
|
+
const at = Date.now();
|
|
4204
|
+
if (identity === lastClick.current.identity && at - lastClick.current.at < 400)
|
|
4205
|
+
return;
|
|
4206
|
+
lastClick.current = { identity, at };
|
|
4207
|
+
try {
|
|
4208
|
+
runtime.track({ eventName: "cta_click", ctaId, formKey, placement });
|
|
4209
|
+
} catch {
|
|
4210
|
+
}
|
|
4211
|
+
}, [runtime, ctaId, formKey, placement]);
|
|
4212
|
+
}
|
|
4213
|
+
|
|
4214
|
+
// libs/sdk/src/react/hooks.ts
|
|
4215
|
+
var import_react3 = require("react");
|
|
4216
|
+
|
|
4217
|
+
// libs/sdk/src/react/context.ts
|
|
4218
|
+
var BacanoReactContext = getOrCreateReactContext("bacano-client-v1", {
|
|
4219
|
+
client: null,
|
|
4220
|
+
loading: true,
|
|
4221
|
+
error: null
|
|
4222
|
+
});
|
|
4223
|
+
|
|
4201
4224
|
// libs/sdk/src/react/hooks.ts
|
|
4202
4225
|
function useClient() {
|
|
4203
|
-
const { client, loading, error } = (0,
|
|
4226
|
+
const { client, loading, error } = (0, import_react3.useContext)(BacanoReactContext);
|
|
4204
4227
|
if (error) throw error;
|
|
4205
4228
|
if (loading || !client) throw new NotInitializedError();
|
|
4206
4229
|
return client;
|
|
4207
4230
|
}
|
|
4208
4231
|
function useSafeClient() {
|
|
4209
|
-
const { client } = (0,
|
|
4232
|
+
const { client } = (0, import_react3.useContext)(BacanoReactContext);
|
|
4210
4233
|
return client;
|
|
4211
4234
|
}
|
|
4212
4235
|
function useAsync(fetcher, deps = []) {
|
|
4213
4236
|
const client = useSafeClient();
|
|
4214
|
-
const [state, setState] = (0,
|
|
4237
|
+
const [state, setState] = (0, import_react3.useState)({
|
|
4215
4238
|
data: null,
|
|
4216
4239
|
loading: true,
|
|
4217
4240
|
error: null
|
|
4218
4241
|
});
|
|
4219
|
-
const mountedRef = (0,
|
|
4220
|
-
const fetchData = (0,
|
|
4242
|
+
const mountedRef = (0, import_react3.useRef)(true);
|
|
4243
|
+
const fetchData = (0, import_react3.useCallback)(async () => {
|
|
4221
4244
|
if (!client) return;
|
|
4222
4245
|
setState((prev) => ({ ...prev, loading: true, error: null }));
|
|
4223
4246
|
try {
|
|
@@ -4235,7 +4258,7 @@ function useAsync(fetcher, deps = []) {
|
|
|
4235
4258
|
}
|
|
4236
4259
|
}
|
|
4237
4260
|
}, [client, ...deps]);
|
|
4238
|
-
(0,
|
|
4261
|
+
(0, import_react3.useEffect)(() => {
|
|
4239
4262
|
mountedRef.current = true;
|
|
4240
4263
|
fetchData();
|
|
4241
4264
|
return () => {
|
|
@@ -4248,10 +4271,10 @@ function useBacano() {
|
|
|
4248
4271
|
return useClient();
|
|
4249
4272
|
}
|
|
4250
4273
|
function useBacanoState() {
|
|
4251
|
-
return (0,
|
|
4274
|
+
return (0, import_react3.useContext)(BacanoReactContext);
|
|
4252
4275
|
}
|
|
4253
4276
|
function useMarketing() {
|
|
4254
|
-
return (0,
|
|
4277
|
+
return (0, import_react3.useContext)(BacanoMarketingContext);
|
|
4255
4278
|
}
|
|
4256
4279
|
function useProducts(opts) {
|
|
4257
4280
|
return useAsync(
|
|
@@ -4342,10 +4365,10 @@ function useContentByTag(tag, opts) {
|
|
|
4342
4365
|
}
|
|
4343
4366
|
function useCart() {
|
|
4344
4367
|
const client = useSafeClient();
|
|
4345
|
-
const [cart, setCart] = (0,
|
|
4346
|
-
const [loading, setLoading] = (0,
|
|
4347
|
-
const [error, setError] = (0,
|
|
4348
|
-
const refreshCart = (0,
|
|
4368
|
+
const [cart, setCart] = (0, import_react3.useState)(null);
|
|
4369
|
+
const [loading, setLoading] = (0, import_react3.useState)(false);
|
|
4370
|
+
const [error, setError] = (0, import_react3.useState)(null);
|
|
4371
|
+
const refreshCart = (0, import_react3.useCallback)(async () => {
|
|
4349
4372
|
if (!client) return;
|
|
4350
4373
|
setLoading(true);
|
|
4351
4374
|
try {
|
|
@@ -4358,10 +4381,10 @@ function useCart() {
|
|
|
4358
4381
|
setLoading(false);
|
|
4359
4382
|
}
|
|
4360
4383
|
}, [client]);
|
|
4361
|
-
(0,
|
|
4384
|
+
(0, import_react3.useEffect)(() => {
|
|
4362
4385
|
refreshCart();
|
|
4363
4386
|
}, [refreshCart]);
|
|
4364
|
-
const addItem = (0,
|
|
4387
|
+
const addItem = (0, import_react3.useCallback)(
|
|
4365
4388
|
async (productVariantId, quantity) => {
|
|
4366
4389
|
if (!client) throw new NotInitializedError();
|
|
4367
4390
|
const item = await client.cart.addItem(productVariantId, quantity);
|
|
@@ -4370,7 +4393,7 @@ function useCart() {
|
|
|
4370
4393
|
},
|
|
4371
4394
|
[client, refreshCart]
|
|
4372
4395
|
);
|
|
4373
|
-
const updateItem = (0,
|
|
4396
|
+
const updateItem = (0, import_react3.useCallback)(
|
|
4374
4397
|
async (cartItemId, quantity) => {
|
|
4375
4398
|
if (!client) throw new NotInitializedError();
|
|
4376
4399
|
const result = await client.cart.updateItem(cartItemId, quantity);
|
|
@@ -4379,7 +4402,7 @@ function useCart() {
|
|
|
4379
4402
|
},
|
|
4380
4403
|
[client, refreshCart]
|
|
4381
4404
|
);
|
|
4382
|
-
const removeItem = (0,
|
|
4405
|
+
const removeItem = (0, import_react3.useCallback)(
|
|
4383
4406
|
async (cartItemId) => {
|
|
4384
4407
|
if (!client) throw new NotInitializedError();
|
|
4385
4408
|
await client.cart.removeItem(cartItemId);
|
|
@@ -4387,7 +4410,7 @@ function useCart() {
|
|
|
4387
4410
|
},
|
|
4388
4411
|
[client, refreshCart]
|
|
4389
4412
|
);
|
|
4390
|
-
const clear = (0,
|
|
4413
|
+
const clear = (0, import_react3.useCallback)(async () => {
|
|
4391
4414
|
if (!client) throw new NotInitializedError();
|
|
4392
4415
|
setLoading(true);
|
|
4393
4416
|
try {
|
|
@@ -4402,7 +4425,7 @@ function useCart() {
|
|
|
4402
4425
|
setLoading(false);
|
|
4403
4426
|
}
|
|
4404
4427
|
}, [client]);
|
|
4405
|
-
const getOrCreate = (0,
|
|
4428
|
+
const getOrCreate = (0, import_react3.useCallback)(
|
|
4406
4429
|
async (contact, fulfillmentFamily) => {
|
|
4407
4430
|
if (!client) throw new NotInitializedError();
|
|
4408
4431
|
const c = await client.cart.getOrCreate(contact, fulfillmentFamily);
|
|
@@ -4411,7 +4434,7 @@ function useCart() {
|
|
|
4411
4434
|
},
|
|
4412
4435
|
[client]
|
|
4413
4436
|
);
|
|
4414
|
-
const startCheckout = (0,
|
|
4437
|
+
const startCheckout = (0, import_react3.useCallback)(
|
|
4415
4438
|
async (input) => {
|
|
4416
4439
|
if (!client) throw new NotInitializedError();
|
|
4417
4440
|
const c = await client.cart.startCheckout(input);
|
|
@@ -4435,9 +4458,9 @@ function useCart() {
|
|
|
4435
4458
|
}
|
|
4436
4459
|
function useCheckout() {
|
|
4437
4460
|
const client = useSafeClient();
|
|
4438
|
-
const [loading, setLoading] = (0,
|
|
4439
|
-
const [error, setError] = (0,
|
|
4440
|
-
const checkout = (0,
|
|
4461
|
+
const [loading, setLoading] = (0, import_react3.useState)(false);
|
|
4462
|
+
const [error, setError] = (0, import_react3.useState)(null);
|
|
4463
|
+
const checkout = (0, import_react3.useCallback)(
|
|
4441
4464
|
async (input) => {
|
|
4442
4465
|
if (!client) throw new NotInitializedError();
|
|
4443
4466
|
setLoading(true);
|
|
@@ -4477,7 +4500,7 @@ function useProfile() {
|
|
|
4477
4500
|
error,
|
|
4478
4501
|
refetch
|
|
4479
4502
|
} = useAsync((client2) => client2.profile.get());
|
|
4480
|
-
const updateProfile = (0,
|
|
4503
|
+
const updateProfile = (0, import_react3.useCallback)(
|
|
4481
4504
|
async (data) => {
|
|
4482
4505
|
if (!client) throw new NotInitializedError();
|
|
4483
4506
|
const updated = await client.profile.update(data);
|
|
@@ -4490,7 +4513,7 @@ function useProfile() {
|
|
|
4490
4513
|
}
|
|
4491
4514
|
|
|
4492
4515
|
// libs/sdk/src/react/marketing-provider.tsx
|
|
4493
|
-
var
|
|
4516
|
+
var import_react5 = require("react");
|
|
4494
4517
|
|
|
4495
4518
|
// libs/sdk/src/marketing/sanitize.ts
|
|
4496
4519
|
var SENSITIVE_PARAMETER_PATTERNS = [
|
|
@@ -4530,6 +4553,9 @@ var ALLOWED_MARKETING_PARAMETERS = /* @__PURE__ */ new Set([
|
|
|
4530
4553
|
function sanitizeMarketingUrl(input, baseUrl) {
|
|
4531
4554
|
try {
|
|
4532
4555
|
const url = new URL(input, baseUrl);
|
|
4556
|
+
if (url.protocol !== "http:" && url.protocol !== "https:") return "";
|
|
4557
|
+
url.username = "";
|
|
4558
|
+
url.password = "";
|
|
4533
4559
|
const safeParameters = new URLSearchParams();
|
|
4534
4560
|
for (const [key, value] of url.searchParams.entries()) {
|
|
4535
4561
|
if (ALLOWED_MARKETING_PARAMETERS.has(key.toLowerCase()) && !SENSITIVE_PARAMETER_PATTERNS.some((pattern) => pattern.test(key))) {
|
|
@@ -4749,50 +4775,65 @@ function serializeWebsiteMarketingAttribution(attribution) {
|
|
|
4749
4775
|
};
|
|
4750
4776
|
}
|
|
4751
4777
|
|
|
4752
|
-
// libs/sdk/src/marketing/
|
|
4753
|
-
var
|
|
4754
|
-
|
|
4755
|
-
|
|
4756
|
-
|
|
4757
|
-
|
|
4758
|
-
|
|
4759
|
-
|
|
4760
|
-
|
|
4761
|
-
|
|
4762
|
-
|
|
4763
|
-
|
|
4764
|
-
|
|
4765
|
-
|
|
4778
|
+
// libs/sdk/src/marketing/interactions.ts
|
|
4779
|
+
var WEBSITE_INTERACTION_EVENTS = [
|
|
4780
|
+
"cta_click",
|
|
4781
|
+
"form_open",
|
|
4782
|
+
"form_start",
|
|
4783
|
+
"form_step_complete",
|
|
4784
|
+
"form_submit_attempt",
|
|
4785
|
+
"form_submit_error",
|
|
4786
|
+
"form_submit_success"
|
|
4787
|
+
];
|
|
4788
|
+
function isInteractionEvent(name) {
|
|
4789
|
+
return WEBSITE_INTERACTION_EVENTS.some((candidate) => candidate === name);
|
|
4790
|
+
}
|
|
4791
|
+
function isInteractionIdentifier(value) {
|
|
4792
|
+
return typeof value === "string" && /^[a-z][a-z0-9_-]{0,63}$/.test(value);
|
|
4793
|
+
}
|
|
4794
|
+
function isBrowserMarketingEvent(name) {
|
|
4795
|
+
return isInteractionEvent(name) || [
|
|
4796
|
+
"page_view",
|
|
4797
|
+
"search",
|
|
4798
|
+
"view_item_list",
|
|
4799
|
+
"select_item",
|
|
4800
|
+
"view_item",
|
|
4801
|
+
"add_to_cart",
|
|
4802
|
+
"remove_from_cart",
|
|
4803
|
+
"view_cart",
|
|
4804
|
+
"begin_checkout",
|
|
4805
|
+
"add_shipping_info",
|
|
4806
|
+
"add_payment_info",
|
|
4807
|
+
"contact"
|
|
4808
|
+
].includes(name);
|
|
4809
|
+
}
|
|
4810
|
+
function interactionParameters(event) {
|
|
4811
|
+
if (!isInteractionEvent(event.eventName)) return {};
|
|
4812
|
+
if (event.eventName === "cta_click" ? !isInteractionIdentifier(event.ctaId) : !isInteractionIdentifier(event.formKey))
|
|
4813
|
+
return null;
|
|
4814
|
+
if (event.eventName === "form_step_complete" && !isInteractionIdentifier(event.stepKey))
|
|
4815
|
+
return null;
|
|
4816
|
+
if (event.eventName === "form_submit_error" && !["validation", "network", "rejected", "unavailable"].includes(
|
|
4817
|
+
event.errorCategory ?? ""
|
|
4818
|
+
))
|
|
4819
|
+
return null;
|
|
4820
|
+
const params = {};
|
|
4821
|
+
for (const [key, value] of Object.entries({
|
|
4822
|
+
form_key: event.formKey,
|
|
4823
|
+
cta_id: event.ctaId,
|
|
4824
|
+
placement: event.placement,
|
|
4825
|
+
step_key: event.stepKey
|
|
4826
|
+
})) {
|
|
4827
|
+
if (value === void 0) continue;
|
|
4828
|
+
if (!isInteractionIdentifier(value)) return null;
|
|
4829
|
+
params[key] = value;
|
|
4766
4830
|
}
|
|
4767
|
-
|
|
4768
|
-
|
|
4769
|
-
|
|
4770
|
-
if (typeof document === "undefined") return;
|
|
4771
|
-
const id = `bacano-clarity-${projectId}`;
|
|
4772
|
-
if (document.getElementById(id)) return;
|
|
4773
|
-
const script = document.createElement("script");
|
|
4774
|
-
script.id = id;
|
|
4775
|
-
script.async = true;
|
|
4776
|
-
script.src = `https://www.clarity.ms/tag/${encodeURIComponent(projectId)}`;
|
|
4777
|
-
script.referrerPolicy = "strict-origin-when-cross-origin";
|
|
4778
|
-
document.head.appendChild(script);
|
|
4779
|
-
}
|
|
4780
|
-
function sendClarityEvent(eventName) {
|
|
4781
|
-
clarityWindow()?.clarity?.("event", eventName);
|
|
4831
|
+
if (event.eventName === "form_submit_error" && event.errorCategory)
|
|
4832
|
+
params.error_category = event.errorCategory;
|
|
4833
|
+
return params;
|
|
4782
4834
|
}
|
|
4783
|
-
|
|
4784
|
-
|
|
4785
|
-
function matchesCanonicalMarketingHost(canonicalDomain, currentHostname) {
|
|
4786
|
-
if (canonicalDomain === void 0) return true;
|
|
4787
|
-
if (!canonicalDomain?.trim()) return false;
|
|
4788
|
-
try {
|
|
4789
|
-
const configuredHost = new URL(
|
|
4790
|
-
canonicalDomain.includes("://") ? canonicalDomain : `https://${canonicalDomain}`
|
|
4791
|
-
).hostname.toLowerCase();
|
|
4792
|
-
return currentHostname.toLowerCase() === configuredHost;
|
|
4793
|
-
} catch {
|
|
4794
|
-
return false;
|
|
4795
|
-
}
|
|
4835
|
+
function newMarketingEventId() {
|
|
4836
|
+
return typeof crypto !== "undefined" && "randomUUID" in crypto ? crypto.randomUUID() : `evt_${Date.now()}_${Math.random().toString(16).slice(2)}`;
|
|
4796
4837
|
}
|
|
4797
4838
|
|
|
4798
4839
|
// libs/sdk/src/marketing/ga4.ts
|
|
@@ -4819,19 +4860,20 @@ var WEBSITE_MARKETING_CONTACT_PLACEMENTS = [
|
|
|
4819
4860
|
"contact_page"
|
|
4820
4861
|
];
|
|
4821
4862
|
|
|
4822
|
-
// libs/sdk/src/marketing/
|
|
4863
|
+
// libs/sdk/src/marketing/browser-adapters.ts
|
|
4823
4864
|
var initializedGa4Destinations = /* @__PURE__ */ new Set();
|
|
4824
4865
|
var initializedMetaDestinations = /* @__PURE__ */ new Set();
|
|
4825
4866
|
function marketingWindow() {
|
|
4826
4867
|
return typeof window === "undefined" ? null : window;
|
|
4827
4868
|
}
|
|
4828
|
-
function loadScriptOnce(id, source) {
|
|
4869
|
+
function loadScriptOnce(id, source, onError) {
|
|
4829
4870
|
if (typeof document === "undefined" || document.getElementById(id)) return;
|
|
4830
4871
|
const script = document.createElement("script");
|
|
4831
4872
|
script.id = id;
|
|
4832
4873
|
script.async = true;
|
|
4833
4874
|
script.src = source;
|
|
4834
4875
|
script.referrerPolicy = "strict-origin-when-cross-origin";
|
|
4876
|
+
script.onerror = () => onError?.();
|
|
4835
4877
|
document.head.appendChild(script);
|
|
4836
4878
|
}
|
|
4837
4879
|
function prepareGa4(measurementId) {
|
|
@@ -4846,10 +4888,11 @@ function prepareGa4(measurementId) {
|
|
|
4846
4888
|
target.gtag("config", measurementId, { send_page_view: false });
|
|
4847
4889
|
initializedGa4Destinations.add(measurementId);
|
|
4848
4890
|
}
|
|
4849
|
-
function loadGa4Script(measurementId) {
|
|
4891
|
+
function loadGa4Script(measurementId, onError) {
|
|
4850
4892
|
loadScriptOnce(
|
|
4851
4893
|
`bacano-ga4-${measurementId}`,
|
|
4852
|
-
`https://www.googletagmanager.com/gtag/js?id=${encodeURIComponent(measurementId)}
|
|
4894
|
+
`https://www.googletagmanager.com/gtag/js?id=${encodeURIComponent(measurementId)}`,
|
|
4895
|
+
onError
|
|
4853
4896
|
);
|
|
4854
4897
|
}
|
|
4855
4898
|
function captureGa4ClientId(measurementId, websiteId, diagnostic) {
|
|
@@ -4897,10 +4940,11 @@ function prepareMeta(pixelId) {
|
|
|
4897
4940
|
target.fbq("init", pixelId);
|
|
4898
4941
|
initializedMetaDestinations.add(pixelId);
|
|
4899
4942
|
}
|
|
4900
|
-
function loadMetaScript() {
|
|
4943
|
+
function loadMetaScript(onError) {
|
|
4901
4944
|
loadScriptOnce(
|
|
4902
4945
|
"bacano-meta-pixel",
|
|
4903
|
-
"https://connect.facebook.net/en_US/fbevents.js"
|
|
4946
|
+
"https://connect.facebook.net/en_US/fbevents.js",
|
|
4947
|
+
onError
|
|
4904
4948
|
);
|
|
4905
4949
|
}
|
|
4906
4950
|
function scheduleScriptLoad(load, strategy) {
|
|
@@ -4964,10 +5008,12 @@ function contactPlacementFor(event) {
|
|
|
4964
5008
|
}
|
|
4965
5009
|
return placement;
|
|
4966
5010
|
}
|
|
4967
|
-
function sendGa4(event, diagnosticMode) {
|
|
5011
|
+
function sendGa4(event, diagnosticMode, measurementId) {
|
|
4968
5012
|
const target = marketingWindow();
|
|
4969
5013
|
const contactPlacement = contactPlacementFor(event);
|
|
4970
5014
|
target?.gtag?.("event", event.eventName, {
|
|
5015
|
+
send_to: measurementId,
|
|
5016
|
+
...interactionParameters(event),
|
|
4971
5017
|
currency: event.currency,
|
|
4972
5018
|
value: event.value,
|
|
4973
5019
|
search_term: event.searchTerm,
|
|
@@ -4984,15 +5030,17 @@ function sendGa4(event, diagnosticMode) {
|
|
|
4984
5030
|
debug_mode: diagnosticMode ? true : void 0
|
|
4985
5031
|
});
|
|
4986
5032
|
}
|
|
4987
|
-
function sendMeta(event) {
|
|
5033
|
+
function sendMeta(event, pixelId) {
|
|
4988
5034
|
const target = marketingWindow();
|
|
4989
5035
|
const eventName = metaEventName(event.eventName);
|
|
4990
5036
|
const isStandard = eventName !== event.eventName;
|
|
4991
5037
|
const contactPlacement = contactPlacementFor(event);
|
|
4992
5038
|
target?.fbq?.(
|
|
4993
|
-
isStandard ? "
|
|
5039
|
+
isStandard ? "trackSingle" : "trackSingleCustom",
|
|
5040
|
+
pixelId,
|
|
4994
5041
|
eventName,
|
|
4995
5042
|
{
|
|
5043
|
+
...interactionParameters(event),
|
|
4996
5044
|
currency: event.currency,
|
|
4997
5045
|
value: event.value,
|
|
4998
5046
|
search_string: event.searchTerm,
|
|
@@ -5009,14 +5057,69 @@ function sendMeta(event) {
|
|
|
5009
5057
|
{ eventID: event.eventId }
|
|
5010
5058
|
);
|
|
5011
5059
|
}
|
|
5012
|
-
|
|
5013
|
-
|
|
5060
|
+
|
|
5061
|
+
// libs/sdk/src/marketing/clarity.ts
|
|
5062
|
+
var initializedDestinations = /* @__PURE__ */ new Set();
|
|
5063
|
+
function clarityWindow() {
|
|
5064
|
+
return typeof window === "undefined" ? null : window;
|
|
5014
5065
|
}
|
|
5066
|
+
function prepareClarity(projectId) {
|
|
5067
|
+
const target = clarityWindow();
|
|
5068
|
+
if (!target || initializedDestinations.has(projectId)) return;
|
|
5069
|
+
if (!target.clarity) {
|
|
5070
|
+
const clarity = function() {
|
|
5071
|
+
clarity.q ??= [];
|
|
5072
|
+
clarity.q.push(arguments);
|
|
5073
|
+
};
|
|
5074
|
+
target.clarity = clarity;
|
|
5075
|
+
}
|
|
5076
|
+
initializedDestinations.add(projectId);
|
|
5077
|
+
}
|
|
5078
|
+
function loadClarityScript(projectId, onError) {
|
|
5079
|
+
if (typeof document === "undefined") return;
|
|
5080
|
+
const id = `bacano-clarity-${projectId}`;
|
|
5081
|
+
if (document.getElementById(id)) return;
|
|
5082
|
+
const script = document.createElement("script");
|
|
5083
|
+
script.id = id;
|
|
5084
|
+
script.async = true;
|
|
5085
|
+
script.src = `https://www.clarity.ms/tag/${encodeURIComponent(projectId)}`;
|
|
5086
|
+
script.referrerPolicy = "strict-origin-when-cross-origin";
|
|
5087
|
+
script.onerror = () => onError?.();
|
|
5088
|
+
document.head.appendChild(script);
|
|
5089
|
+
}
|
|
5090
|
+
function sendClarityEvent(eventName) {
|
|
5091
|
+
clarityWindow()?.clarity?.("event", eventName);
|
|
5092
|
+
}
|
|
5093
|
+
|
|
5094
|
+
// libs/sdk/src/marketing/domain.ts
|
|
5095
|
+
function matchesCanonicalMarketingHost(canonicalDomain, currentHostname) {
|
|
5096
|
+
if (canonicalDomain === void 0) return true;
|
|
5097
|
+
if (!canonicalDomain?.trim()) return false;
|
|
5098
|
+
try {
|
|
5099
|
+
const configuredHost = new URL(
|
|
5100
|
+
canonicalDomain.includes("://") ? canonicalDomain : `https://${canonicalDomain}`
|
|
5101
|
+
).hostname.toLowerCase();
|
|
5102
|
+
return currentHostname.toLowerCase() === configuredHost;
|
|
5103
|
+
} catch {
|
|
5104
|
+
return false;
|
|
5105
|
+
}
|
|
5106
|
+
}
|
|
5107
|
+
|
|
5108
|
+
// libs/sdk/src/marketing/runtime.ts
|
|
5015
5109
|
function createWebsiteMarketingRuntime(options) {
|
|
5016
5110
|
let destroyed = false;
|
|
5111
|
+
const dispatched = /* @__PURE__ */ new Set();
|
|
5112
|
+
const observer = options.diagnostic;
|
|
5113
|
+
const diagnostic = (value) => {
|
|
5114
|
+
try {
|
|
5115
|
+
observer?.(value);
|
|
5116
|
+
} catch {
|
|
5117
|
+
}
|
|
5118
|
+
};
|
|
5119
|
+
options = { ...options, diagnostic };
|
|
5017
5120
|
let cancelScheduledLoad = () => void 0;
|
|
5018
5121
|
const target = marketingWindow();
|
|
5019
|
-
const enabled = options.config.enabled && Boolean(
|
|
5122
|
+
const enabled = options.config.enabled && options.measurementAllowed !== false && Boolean(
|
|
5020
5123
|
target && matchesCanonicalMarketingHost(
|
|
5021
5124
|
options.canonicalDomain,
|
|
5022
5125
|
target.location.hostname
|
|
@@ -5024,25 +5127,59 @@ function createWebsiteMarketingRuntime(options) {
|
|
|
5024
5127
|
) && Boolean(
|
|
5025
5128
|
options.config.destinations.ga4 || options.config.destinations.meta || options.config.destinations.clarity
|
|
5026
5129
|
);
|
|
5130
|
+
if (!enabled)
|
|
5131
|
+
diagnostic({
|
|
5132
|
+
level: "debug",
|
|
5133
|
+
code: !options.config.enabled ? "MARKETING_DISABLED" : options.measurementAllowed === false ? "MEASUREMENT_NOT_ALLOWED" : !target ? "SERVER_RENDER" : !matchesCanonicalMarketingHost(
|
|
5134
|
+
options.canonicalDomain,
|
|
5135
|
+
target.location.hostname
|
|
5136
|
+
) ? "DOMAIN_NOT_ALLOWED" : "PROVIDER_NOT_CONFIGURED",
|
|
5137
|
+
message: "Measurement is inactive; no events are emitted"
|
|
5138
|
+
});
|
|
5027
5139
|
if (enabled) {
|
|
5140
|
+
const guard = (provider, action) => {
|
|
5141
|
+
try {
|
|
5142
|
+
action();
|
|
5143
|
+
} catch {
|
|
5144
|
+
diagnostic({
|
|
5145
|
+
level: "warning",
|
|
5146
|
+
code: "RUNTIME_INITIALIZATION_FAILED",
|
|
5147
|
+
message: `${provider}: initialization failed`
|
|
5148
|
+
});
|
|
5149
|
+
}
|
|
5150
|
+
};
|
|
5151
|
+
const scriptError = (provider) => () => diagnostic({
|
|
5152
|
+
level: "warning",
|
|
5153
|
+
code: "SCRIPT_LOAD_FAILED",
|
|
5154
|
+
message: `${provider}: script could not load (network, CSP or blocker); external reception is unverified`
|
|
5155
|
+
});
|
|
5028
5156
|
try {
|
|
5029
5157
|
if (options.config.destinations.ga4) {
|
|
5030
|
-
|
|
5158
|
+
const { measurementId } = options.config.destinations.ga4;
|
|
5159
|
+
guard("GA4", () => prepareGa4(measurementId));
|
|
5031
5160
|
}
|
|
5032
5161
|
if (options.config.destinations.meta) {
|
|
5033
|
-
|
|
5162
|
+
const { pixelId } = options.config.destinations.meta;
|
|
5163
|
+
guard("Meta", () => prepareMeta(pixelId));
|
|
5034
5164
|
}
|
|
5035
5165
|
if (options.config.destinations.clarity) {
|
|
5036
|
-
|
|
5166
|
+
const { projectId } = options.config.destinations.clarity;
|
|
5167
|
+
guard("Clarity", () => prepareClarity(projectId));
|
|
5037
5168
|
}
|
|
5038
5169
|
cancelScheduledLoad = scheduleScriptLoad(() => {
|
|
5039
5170
|
if (destroyed) return;
|
|
5040
5171
|
if (options.config.destinations.ga4) {
|
|
5041
|
-
|
|
5172
|
+
const { measurementId } = options.config.destinations.ga4;
|
|
5173
|
+
guard("GA4", () => loadGa4Script(measurementId, scriptError("GA4")));
|
|
5042
5174
|
}
|
|
5043
|
-
if (options.config.destinations.meta)
|
|
5175
|
+
if (options.config.destinations.meta)
|
|
5176
|
+
guard("Meta", () => loadMetaScript(scriptError("Meta")));
|
|
5044
5177
|
if (options.config.destinations.clarity) {
|
|
5045
|
-
|
|
5178
|
+
const { projectId } = options.config.destinations.clarity;
|
|
5179
|
+
guard(
|
|
5180
|
+
"Clarity",
|
|
5181
|
+
() => loadClarityScript(projectId, scriptError("Clarity"))
|
|
5182
|
+
);
|
|
5046
5183
|
}
|
|
5047
5184
|
}, options.scriptLoadStrategy);
|
|
5048
5185
|
captureWebsiteMarketingAttribution(
|
|
@@ -5064,11 +5201,11 @@ function createWebsiteMarketingRuntime(options) {
|
|
|
5064
5201
|
if (options.config.destinations.meta) {
|
|
5065
5202
|
refreshWebsiteMarketingMetaContext(options.websiteId);
|
|
5066
5203
|
}
|
|
5067
|
-
} catch
|
|
5204
|
+
} catch {
|
|
5068
5205
|
options.diagnostic?.({
|
|
5069
5206
|
level: "warning",
|
|
5070
5207
|
code: "RUNTIME_INITIALIZATION_FAILED",
|
|
5071
|
-
message:
|
|
5208
|
+
message: "Marketing initialization failed; external reception is unverified"
|
|
5072
5209
|
});
|
|
5073
5210
|
}
|
|
5074
5211
|
}
|
|
@@ -5076,41 +5213,96 @@ function createWebsiteMarketingRuntime(options) {
|
|
|
5076
5213
|
enabled,
|
|
5077
5214
|
track(input) {
|
|
5078
5215
|
if (!enabled || destroyed) return;
|
|
5216
|
+
if (!isBrowserMarketingEvent(input.eventName)) {
|
|
5217
|
+
diagnostic({
|
|
5218
|
+
level: "warning",
|
|
5219
|
+
code: "INVALID_EVENT",
|
|
5220
|
+
message: "Unknown or server-owned event rejected"
|
|
5221
|
+
});
|
|
5222
|
+
return;
|
|
5223
|
+
}
|
|
5224
|
+
try {
|
|
5225
|
+
captureWebsiteMarketingAttribution(
|
|
5226
|
+
options.websiteId,
|
|
5227
|
+
options.config.attributionWindowDays
|
|
5228
|
+
);
|
|
5229
|
+
} catch {
|
|
5230
|
+
}
|
|
5079
5231
|
const event = {
|
|
5080
5232
|
...input,
|
|
5081
5233
|
schemaVersion: 1,
|
|
5082
|
-
eventId: input.eventId ??
|
|
5234
|
+
eventId: input.eventId ?? newMarketingEventId(),
|
|
5083
5235
|
occurredAt: input.occurredAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
5084
5236
|
currency: input.currency ?? options.config.currencyCode,
|
|
5085
5237
|
pageLocation: sanitizeMarketingUrl(
|
|
5086
5238
|
input.pageLocation ?? (typeof window === "undefined" ? "" : window.location.href)
|
|
5087
5239
|
)
|
|
5088
5240
|
};
|
|
5089
|
-
|
|
5090
|
-
if (
|
|
5091
|
-
|
|
5241
|
+
if (isInteractionEvent(event.eventName)) {
|
|
5242
|
+
if (!interactionParameters(event) || !/^[a-zA-Z0-9_-]{1,100}$/.test(event.eventId)) {
|
|
5243
|
+
diagnostic({
|
|
5244
|
+
level: "warning",
|
|
5245
|
+
code: "INVALID_INTERACTION",
|
|
5246
|
+
message: "Interaction rejected by the public contract",
|
|
5247
|
+
eventName: event.eventName
|
|
5248
|
+
});
|
|
5249
|
+
return;
|
|
5092
5250
|
}
|
|
5093
|
-
|
|
5094
|
-
|
|
5095
|
-
|
|
5251
|
+
for (const key of [
|
|
5252
|
+
"currency",
|
|
5253
|
+
"value",
|
|
5254
|
+
"searchTerm",
|
|
5255
|
+
"itemListId",
|
|
5256
|
+
"itemListName",
|
|
5257
|
+
"shippingTier",
|
|
5258
|
+
"paymentType",
|
|
5259
|
+
"contactMethod",
|
|
5260
|
+
"contactPlacement",
|
|
5261
|
+
"items",
|
|
5262
|
+
"pageTitle"
|
|
5263
|
+
])
|
|
5264
|
+
delete event[key];
|
|
5265
|
+
}
|
|
5266
|
+
const identity = `${event.eventName}:${event.eventId}`;
|
|
5267
|
+
if (dispatched.has(identity)) return;
|
|
5268
|
+
dispatched.add(identity);
|
|
5269
|
+
const oldest = dispatched.values().next().value;
|
|
5270
|
+
if (dispatched.size > 1e3 && oldest) dispatched.delete(oldest);
|
|
5271
|
+
const dispatch = (provider, send) => {
|
|
5272
|
+
try {
|
|
5273
|
+
send();
|
|
5274
|
+
diagnostic({
|
|
5275
|
+
level: "debug",
|
|
5276
|
+
code: "EVENT_DISPATCHED",
|
|
5277
|
+
message: `${provider}: passed to adapter; external reception is unverified`,
|
|
5278
|
+
eventName: event.eventName
|
|
5279
|
+
});
|
|
5280
|
+
} catch {
|
|
5281
|
+
diagnostic({
|
|
5282
|
+
level: "warning",
|
|
5283
|
+
code: "EVENT_DISPATCH_FAILED",
|
|
5284
|
+
message: `${provider}: adapter failed`,
|
|
5285
|
+
eventName: event.eventName
|
|
5286
|
+
});
|
|
5096
5287
|
}
|
|
5097
|
-
|
|
5098
|
-
|
|
5099
|
-
|
|
5100
|
-
|
|
5101
|
-
|
|
5102
|
-
|
|
5103
|
-
|
|
5104
|
-
|
|
5105
|
-
|
|
5106
|
-
|
|
5107
|
-
|
|
5108
|
-
|
|
5109
|
-
|
|
5288
|
+
};
|
|
5289
|
+
if (options.config.destinations.ga4) {
|
|
5290
|
+
const ga4 = options.config.destinations.ga4;
|
|
5291
|
+
dispatch(
|
|
5292
|
+
"GA4",
|
|
5293
|
+
() => sendGa4(event, ga4.diagnosticMode, ga4.measurementId)
|
|
5294
|
+
);
|
|
5295
|
+
}
|
|
5296
|
+
if (options.config.destinations.meta) {
|
|
5297
|
+
const meta = options.config.destinations.meta;
|
|
5298
|
+
dispatch("Meta", () => sendMeta(event, meta.pixelId));
|
|
5299
|
+
}
|
|
5300
|
+
if (options.config.destinations.clarity) {
|
|
5301
|
+
dispatch("Clarity", () => sendClarityEvent(event.eventName));
|
|
5110
5302
|
}
|
|
5111
5303
|
},
|
|
5112
5304
|
captureAttribution() {
|
|
5113
|
-
if (!enabled) return null;
|
|
5305
|
+
if (!enabled || destroyed) return null;
|
|
5114
5306
|
captureWebsiteMarketingAttribution(
|
|
5115
5307
|
options.websiteId,
|
|
5116
5308
|
options.config.attributionWindowDays
|
|
@@ -5121,7 +5313,7 @@ function createWebsiteMarketingRuntime(options) {
|
|
|
5121
5313
|
return getWebsiteMarketingAttribution(options.websiteId);
|
|
5122
5314
|
},
|
|
5123
5315
|
getAttribution() {
|
|
5124
|
-
if (!enabled) return null;
|
|
5316
|
+
if (!enabled || destroyed) return null;
|
|
5125
5317
|
if (options.config.destinations.meta) {
|
|
5126
5318
|
refreshWebsiteMarketingMetaContext(options.websiteId);
|
|
5127
5319
|
}
|
|
@@ -5135,7 +5327,7 @@ function createWebsiteMarketingRuntime(options) {
|
|
|
5135
5327
|
}
|
|
5136
5328
|
|
|
5137
5329
|
// libs/sdk/src/react/preview/context.ts
|
|
5138
|
-
var
|
|
5330
|
+
var import_react4 = require("react");
|
|
5139
5331
|
var BacanoPreviewModeContext = getOrCreateReactContext(
|
|
5140
5332
|
"bacano-preview-mode-v1",
|
|
5141
5333
|
false
|
|
@@ -5151,15 +5343,24 @@ function BacanoMarketingProvider({
|
|
|
5151
5343
|
publicConfig,
|
|
5152
5344
|
children,
|
|
5153
5345
|
scriptLoadStrategy,
|
|
5154
|
-
diagnostic
|
|
5346
|
+
diagnostic,
|
|
5347
|
+
measurementAllowed
|
|
5155
5348
|
}) {
|
|
5156
|
-
const isPreview = (0,
|
|
5157
|
-
const [runtime, setRuntime] = (0,
|
|
5349
|
+
const isPreview = (0, import_react5.useContext)(BacanoPreviewModeContext);
|
|
5350
|
+
const [runtime, setRuntime] = (0, import_react5.useState)(
|
|
5158
5351
|
NOOP_MARKETING_RUNTIME
|
|
5159
5352
|
);
|
|
5160
|
-
(0,
|
|
5353
|
+
(0, import_react5.useEffect)(() => {
|
|
5161
5354
|
if (isPreview || !publicConfig) {
|
|
5162
5355
|
setRuntime(NOOP_MARKETING_RUNTIME);
|
|
5356
|
+
try {
|
|
5357
|
+
diagnostic?.({
|
|
5358
|
+
level: "debug",
|
|
5359
|
+
code: isPreview ? "PREVIEW_DISABLED" : "PUBLIC_CONFIG_UNAVAILABLE",
|
|
5360
|
+
message: "Measurement is inactive"
|
|
5361
|
+
});
|
|
5362
|
+
} catch {
|
|
5363
|
+
}
|
|
5163
5364
|
return;
|
|
5164
5365
|
}
|
|
5165
5366
|
const next = createWebsiteMarketingRuntime({
|
|
@@ -5167,22 +5368,29 @@ function BacanoMarketingProvider({
|
|
|
5167
5368
|
config: publicConfig.marketing,
|
|
5168
5369
|
canonicalDomain: publicConfig.domain,
|
|
5169
5370
|
scriptLoadStrategy,
|
|
5170
|
-
diagnostic
|
|
5371
|
+
diagnostic,
|
|
5372
|
+
measurementAllowed
|
|
5171
5373
|
});
|
|
5172
5374
|
setRuntime(next);
|
|
5173
5375
|
return () => next.destroy();
|
|
5174
|
-
}, [
|
|
5376
|
+
}, [
|
|
5377
|
+
diagnostic,
|
|
5378
|
+
isPreview,
|
|
5379
|
+
publicConfig,
|
|
5380
|
+
scriptLoadStrategy,
|
|
5381
|
+
measurementAllowed
|
|
5382
|
+
]);
|
|
5175
5383
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
5176
5384
|
BacanoMarketingContext.Provider,
|
|
5177
5385
|
{
|
|
5178
|
-
value: isPreview ? NOOP_MARKETING_RUNTIME : runtime,
|
|
5386
|
+
value: isPreview || measurementAllowed === false ? NOOP_MARKETING_RUNTIME : runtime,
|
|
5179
5387
|
children
|
|
5180
5388
|
}
|
|
5181
5389
|
);
|
|
5182
5390
|
}
|
|
5183
5391
|
|
|
5184
5392
|
// libs/sdk/src/react/provider.tsx
|
|
5185
|
-
var
|
|
5393
|
+
var import_react6 = require("react");
|
|
5186
5394
|
|
|
5187
5395
|
// libs/sdk/src/modules/auth.ts
|
|
5188
5396
|
function createAuthHeaders(accessToken) {
|
|
@@ -7293,14 +7501,14 @@ function BacanoProvider({
|
|
|
7293
7501
|
config,
|
|
7294
7502
|
children
|
|
7295
7503
|
}) {
|
|
7296
|
-
const getAccessTokenRef = (0,
|
|
7504
|
+
const getAccessTokenRef = (0, import_react6.useRef)(config.getAccessToken);
|
|
7297
7505
|
getAccessTokenRef.current = config.getAccessToken;
|
|
7298
|
-
const [state, setState] = (0,
|
|
7506
|
+
const [state, setState] = (0, import_react6.useState)({
|
|
7299
7507
|
client: null,
|
|
7300
7508
|
loading: true,
|
|
7301
7509
|
error: null
|
|
7302
7510
|
});
|
|
7303
|
-
(0,
|
|
7511
|
+
(0, import_react6.useEffect)(() => {
|
|
7304
7512
|
let cancelled = false;
|
|
7305
7513
|
const client = createBacanoClient({
|
|
7306
7514
|
...config,
|
|
@@ -7334,7 +7542,7 @@ function BacanoProvider({
|
|
|
7334
7542
|
}
|
|
7335
7543
|
|
|
7336
7544
|
// libs/sdk/src/react/website-gate.tsx
|
|
7337
|
-
var
|
|
7545
|
+
var import_react7 = require("react");
|
|
7338
7546
|
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
7339
7547
|
var PREVIEW_HASH_KEY = "bacano-preview";
|
|
7340
7548
|
function previewStorageKey(websiteSlug) {
|
|
@@ -7453,11 +7661,11 @@ function BacanoWebsiteGate({
|
|
|
7453
7661
|
loadingFallback = null,
|
|
7454
7662
|
constructionFallback
|
|
7455
7663
|
}) {
|
|
7456
|
-
const [publicConfig, setPublicConfig] = (0,
|
|
7457
|
-
const [isLoading, setIsLoading] = (0,
|
|
7458
|
-
const [failedOpen, setFailedOpen] = (0,
|
|
7664
|
+
const [publicConfig, setPublicConfig] = (0, import_react7.useState)(initialConfig);
|
|
7665
|
+
const [isLoading, setIsLoading] = (0, import_react7.useState)(true);
|
|
7666
|
+
const [failedOpen, setFailedOpen] = (0, import_react7.useState)(false);
|
|
7459
7667
|
const isConstructionEnforced = Boolean(publicConfig?.construction.enforced);
|
|
7460
|
-
(0,
|
|
7668
|
+
(0, import_react7.useEffect)(() => {
|
|
7461
7669
|
let active = true;
|
|
7462
7670
|
const previewToken = readPreviewToken(config.websiteSlug);
|
|
7463
7671
|
setIsLoading(true);
|
|
@@ -7477,7 +7685,7 @@ function BacanoWebsiteGate({
|
|
|
7477
7685
|
active = false;
|
|
7478
7686
|
};
|
|
7479
7687
|
}, [config.apiUrl, config.origin, config.websiteSlug]);
|
|
7480
|
-
(0,
|
|
7688
|
+
(0, import_react7.useEffect)(() => {
|
|
7481
7689
|
if (typeof document === "undefined" || !isConstructionEnforced) return;
|
|
7482
7690
|
const robots = document.createElement("meta");
|
|
7483
7691
|
robots.name = "robots";
|