@bacano/sdk 1.0.0 → 1.1.0
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 +342 -7
- package/STOREFRONT_CLARITY.md +140 -0
- package/STOREFRONT_CONSTRUCTION.md +92 -0
- package/STOREFRONT_LEADS.md +130 -0
- package/STOREFRONT_LEGAL.md +66 -0
- package/STOREFRONT_MARKETING.md +286 -0
- package/STOREFRONT_PERFORMANCE.md +265 -0
- package/STOREFRONT_PREVIEW.md +474 -0
- package/client.d.ts +7 -1
- package/errors.d.ts +9 -1
- package/graphql.cjs +63 -0
- package/graphql.d.ts +2 -2
- package/graphql.js +63 -0
- package/index.cjs +1542 -192
- package/index.d.ts +8 -6
- package/index.js +1542 -192
- package/marketing/attribution.d.ts +51 -0
- package/marketing/clarity.d.ts +3 -0
- package/marketing/domain.d.ts +1 -0
- package/marketing/ga4.d.ts +2 -0
- package/marketing/index.d.ts +5 -0
- package/marketing/runtime.d.ts +2 -0
- package/marketing/sanitize.d.ts +2 -0
- package/marketing/types.d.ts +109 -0
- package/media.d.ts +45 -2
- package/modules/auth.d.ts +3 -2
- package/modules/availability.d.ts +2 -2
- package/modules/cart-session.d.ts +2 -2
- package/modules/cart.d.ts +3 -3
- package/modules/catalog.d.ts +15 -3
- package/modules/content.d.ts +2 -2
- package/modules/marketing.d.ts +5 -0
- package/modules/orders.d.ts +2 -2
- package/modules/profile.d.ts +1 -1
- package/modules/static-catalog.d.ts +13 -0
- package/modules/website.d.ts +7 -1
- package/package.json +14 -3
- package/react/context-registry.d.ts +9 -0
- package/react/context.d.ts +1 -1
- package/react/hooks.d.ts +23 -2
- package/react/marketing-context.d.ts +3 -0
- package/react/marketing-provider.d.ts +10 -0
- package/react/preview/cache.d.ts +23 -0
- package/react/preview/context.d.ts +5 -0
- package/react/preview/navigation.d.ts +9 -0
- package/react/preview/provider.d.ts +3 -0
- package/react/preview/shell.d.ts +3 -0
- package/react/preview/toolbar.d.ts +3 -0
- package/react/preview/types.d.ts +60 -0
- package/react/preview.cjs +742 -0
- package/react/preview.d.ts +14 -0
- package/react/preview.js +730 -0
- package/react/website-gate.d.ts +22 -0
- package/react.cjs +3432 -1780
- package/react.d.ts +8 -4
- package/react.js +3428 -1772
- package/skills/bacano-web/SKILL.md +109 -0
- package/skills/bacano-web/agents/openai.yaml +4 -0
- package/skills/bacano-web/references/common.md +91 -0
- package/skills/bacano-web/references/informational.md +39 -0
- package/skills/bacano-web/references/report.md +52 -0
- package/skills/bacano-web/references/storefront.md +116 -0
- package/skills/bacano-web/scripts/audit-http.mjs +247 -0
- package/skills/bacano-web/scripts/cli.mjs +222 -0
- package/skills/bacano-web/scripts/postinstall.mjs +8 -0
- package/transport/documents.d.ts +8 -6
- package/transport/graphql.d.ts +13 -1
- package/types.d.ts +202 -0
package/index.js
CHANGED
|
@@ -1,10 +1,23 @@
|
|
|
1
1
|
// libs/sdk/src/errors.ts
|
|
2
2
|
var WebsiteError = class extends Error {
|
|
3
3
|
code;
|
|
4
|
-
|
|
4
|
+
status;
|
|
5
|
+
retryAfterSeconds;
|
|
6
|
+
constructor(message, code = "WEBSITE_ERROR", options) {
|
|
5
7
|
super(message);
|
|
6
8
|
this.name = "WebsiteError";
|
|
7
9
|
this.code = code;
|
|
10
|
+
this.status = options?.status ?? null;
|
|
11
|
+
this.retryAfterSeconds = options?.retryAfterSeconds ?? null;
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
var RateLimitError = class extends WebsiteError {
|
|
15
|
+
constructor(message, retryAfterSeconds) {
|
|
16
|
+
super(message, "RATE_LIMITED", {
|
|
17
|
+
status: 429,
|
|
18
|
+
retryAfterSeconds
|
|
19
|
+
});
|
|
20
|
+
this.name = "RateLimitError";
|
|
8
21
|
}
|
|
9
22
|
};
|
|
10
23
|
var NotInitializedError = class extends WebsiteError {
|
|
@@ -40,7 +53,109 @@ var GraphQLRequestError = class extends WebsiteError {
|
|
|
40
53
|
}
|
|
41
54
|
};
|
|
42
55
|
|
|
56
|
+
// libs/sdk/src/media.ts
|
|
57
|
+
var DEFAULT_RESPONSIVE_WIDTHS = [240, 384, 640, 960, 1400];
|
|
58
|
+
var CONTENT_HERO_WIDTHS = [640, 960, 1400, 1920, 2560];
|
|
59
|
+
var CONTENT_HERO_MAX_WIDTH = 1920;
|
|
60
|
+
var MEDIA_VARIANTS = {
|
|
61
|
+
thumbnail: { width: 240, quality: 75, format: "webp" },
|
|
62
|
+
card: { width: 640, quality: 80, format: "webp" },
|
|
63
|
+
detail: { width: 1400, quality: 85, format: "webp" }
|
|
64
|
+
};
|
|
65
|
+
function toSafeInteger(value) {
|
|
66
|
+
if (!Number.isFinite(value)) return void 0;
|
|
67
|
+
return Math.max(1, Math.floor(value));
|
|
68
|
+
}
|
|
69
|
+
function buildQuery(options) {
|
|
70
|
+
const params = new URLSearchParams();
|
|
71
|
+
const width = toSafeInteger(options.width);
|
|
72
|
+
const height = toSafeInteger(options.height);
|
|
73
|
+
const quality = toSafeInteger(options.quality);
|
|
74
|
+
if (width) params.set("w", String(width));
|
|
75
|
+
if (height) params.set("h", String(height));
|
|
76
|
+
if (quality) params.set("q", String(Math.min(100, quality)));
|
|
77
|
+
if (options.format) params.set("f", options.format);
|
|
78
|
+
const query = params.toString();
|
|
79
|
+
return query ? `?${query}` : "";
|
|
80
|
+
}
|
|
81
|
+
function buildStorageFileUrl(storageUrl, fileId, variant = "original") {
|
|
82
|
+
if (!storageUrl || !fileId) return "";
|
|
83
|
+
const baseUrl = `${storageUrl.replace(/\/+$/, "")}/files/${fileId}`;
|
|
84
|
+
if (variant === "original") return baseUrl;
|
|
85
|
+
return `${baseUrl}${buildQuery(MEDIA_VARIANTS[variant])}`;
|
|
86
|
+
}
|
|
87
|
+
function buildStorageTransformedFileUrl(storageUrl, fileId, options) {
|
|
88
|
+
if (!storageUrl || !fileId) return "";
|
|
89
|
+
const baseUrl = `${storageUrl.replace(/\/+$/, "")}/files/${fileId}`;
|
|
90
|
+
return `${baseUrl}${buildQuery(options)}`;
|
|
91
|
+
}
|
|
92
|
+
function buildStorageResponsiveSources(storageUrl, fileId, options = {}) {
|
|
93
|
+
const widths = options.widths ?? [...DEFAULT_RESPONSIVE_WIDTHS];
|
|
94
|
+
const uniqueWidths = [...new Set(widths.map(toSafeInteger))].filter((width) => Boolean(width)).sort((left, right) => left - right);
|
|
95
|
+
return uniqueWidths.map((width) => ({
|
|
96
|
+
width,
|
|
97
|
+
url: buildStorageTransformedFileUrl(storageUrl, fileId, {
|
|
98
|
+
width,
|
|
99
|
+
quality: options.quality ?? 80,
|
|
100
|
+
format: options.format ?? "webp"
|
|
101
|
+
})
|
|
102
|
+
}));
|
|
103
|
+
}
|
|
104
|
+
function formatMediaSrcSet(sources) {
|
|
105
|
+
return sources.filter((source) => source.url && source.width > 0).map((source) => `${source.url} ${source.width}w`).join(", ");
|
|
106
|
+
}
|
|
107
|
+
function buildStorageMediaUrls(storageUrl, fileId) {
|
|
108
|
+
return {
|
|
109
|
+
originalUrl: buildStorageFileUrl(storageUrl, fileId, "original"),
|
|
110
|
+
thumbnailUrl: buildStorageFileUrl(storageUrl, fileId, "thumbnail"),
|
|
111
|
+
cardUrl: buildStorageFileUrl(storageUrl, fileId, "card"),
|
|
112
|
+
detailUrl: buildStorageFileUrl(storageUrl, fileId, "detail"),
|
|
113
|
+
responsiveSources: buildStorageResponsiveSources(storageUrl, fileId)
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
function buildStorageContentMediaUrls(storageUrl, fileId, originalWidth) {
|
|
117
|
+
const legacyUrls = buildStorageMediaUrls(storageUrl, fileId);
|
|
118
|
+
const safeOriginalWidth = toSafeInteger(originalWidth ?? void 0);
|
|
119
|
+
if (!safeOriginalWidth) {
|
|
120
|
+
return {
|
|
121
|
+
...legacyUrls,
|
|
122
|
+
heroUrl: legacyUrls.originalUrl,
|
|
123
|
+
responsiveSources: []
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
const responsiveWidths = [
|
|
127
|
+
...CONTENT_HERO_WIDTHS.filter((width) => width <= safeOriginalWidth),
|
|
128
|
+
safeOriginalWidth
|
|
129
|
+
];
|
|
130
|
+
const heroWidth = Math.min(safeOriginalWidth, CONTENT_HERO_MAX_WIDTH);
|
|
131
|
+
return {
|
|
132
|
+
...legacyUrls,
|
|
133
|
+
heroUrl: buildStorageTransformedFileUrl(storageUrl, fileId, {
|
|
134
|
+
width: heroWidth,
|
|
135
|
+
quality: 88,
|
|
136
|
+
format: "webp"
|
|
137
|
+
}),
|
|
138
|
+
responsiveSources: buildStorageResponsiveSources(storageUrl, fileId, {
|
|
139
|
+
widths: responsiveWidths,
|
|
140
|
+
quality: 85,
|
|
141
|
+
format: "webp"
|
|
142
|
+
})
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
|
|
43
146
|
// libs/sdk/src/modules/auth.ts
|
|
147
|
+
function getWebsiteContextFromPublicConfig(config) {
|
|
148
|
+
if (config.contextSchemaVersion !== 1) return void 0;
|
|
149
|
+
return {
|
|
150
|
+
websiteId: config.websiteId,
|
|
151
|
+
companyId: config.companyId,
|
|
152
|
+
chainId: config.chainId ?? null,
|
|
153
|
+
name: config.name,
|
|
154
|
+
slug: config.slug,
|
|
155
|
+
domain: config.domain,
|
|
156
|
+
config: config.config ?? null
|
|
157
|
+
};
|
|
158
|
+
}
|
|
44
159
|
function createAuthHeaders(accessToken) {
|
|
45
160
|
return accessToken ? { Authorization: `Bearer ${accessToken}` } : {};
|
|
46
161
|
}
|
|
@@ -62,14 +177,18 @@ function createRequestHeadersProvider(websiteSlug, getAccessToken) {
|
|
|
62
177
|
}
|
|
63
178
|
async function getWebsitePublicConfig(options) {
|
|
64
179
|
const apiUrl = options.apiUrl.replace(/\/+$/, "");
|
|
65
|
-
const headers =
|
|
180
|
+
const headers = {};
|
|
181
|
+
if (options.origin) headers.Origin = options.origin;
|
|
182
|
+
if (options.previewToken) {
|
|
183
|
+
headers["X-Bacano-Preview-Token"] = options.previewToken;
|
|
184
|
+
}
|
|
66
185
|
const params = new URLSearchParams({ websiteSlug: options.websiteSlug });
|
|
67
186
|
let response;
|
|
68
187
|
try {
|
|
69
188
|
response = await fetch(
|
|
70
189
|
`${apiUrl}/api/v1/website/public-config?${params.toString()}`,
|
|
71
190
|
{
|
|
72
|
-
headers,
|
|
191
|
+
headers: Object.keys(headers).length > 0 ? headers : void 0,
|
|
73
192
|
cache: "no-store"
|
|
74
193
|
}
|
|
75
194
|
);
|
|
@@ -93,12 +212,71 @@ async function getWebsitePublicConfig(options) {
|
|
|
93
212
|
require_document: false,
|
|
94
213
|
document_types: []
|
|
95
214
|
};
|
|
215
|
+
const marketing = raw.marketing ?? {
|
|
216
|
+
schema_version: 1,
|
|
217
|
+
enabled: false,
|
|
218
|
+
currency_code: "COP",
|
|
219
|
+
time_zone: "America/Bogota",
|
|
220
|
+
attribution_window_days: 90,
|
|
221
|
+
consent_policy: null,
|
|
222
|
+
destinations: { ga4: null, meta: null, clarity: null }
|
|
223
|
+
};
|
|
224
|
+
const legal = raw.legal ?? {
|
|
225
|
+
schema_version: 1,
|
|
226
|
+
version: 0,
|
|
227
|
+
content_hash: null,
|
|
228
|
+
documents: {
|
|
229
|
+
terms: null,
|
|
230
|
+
shipping: null,
|
|
231
|
+
returns: null,
|
|
232
|
+
privacy: null,
|
|
233
|
+
cookies: null
|
|
234
|
+
},
|
|
235
|
+
inquiries_channel: null,
|
|
236
|
+
direct_marketing_consent: null,
|
|
237
|
+
cookie_notice: {
|
|
238
|
+
enabled: false,
|
|
239
|
+
message: "Usamos cookies propias y de terceros para mejorar tu experiencia y medir el funcionamiento del sitio.",
|
|
240
|
+
button_label: "Entendido"
|
|
241
|
+
}
|
|
242
|
+
};
|
|
243
|
+
const mapConstructionImage = (image) => {
|
|
244
|
+
if (!image) return null;
|
|
245
|
+
const urls = buildStorageContentMediaUrls(
|
|
246
|
+
`${apiUrl}/storage/v1`,
|
|
247
|
+
image.file_id,
|
|
248
|
+
image.width
|
|
249
|
+
);
|
|
250
|
+
return {
|
|
251
|
+
fileId: image.file_id,
|
|
252
|
+
name: image.name,
|
|
253
|
+
altText: image.alt_text,
|
|
254
|
+
width: image.width,
|
|
255
|
+
height: image.height,
|
|
256
|
+
originalUrl: urls.originalUrl,
|
|
257
|
+
heroUrl: urls.heroUrl,
|
|
258
|
+
responsiveSources: urls.responsiveSources
|
|
259
|
+
};
|
|
260
|
+
};
|
|
261
|
+
const construction = raw.construction ?? {
|
|
262
|
+
schema_version: 1,
|
|
263
|
+
enabled: false,
|
|
264
|
+
enforced: false,
|
|
265
|
+
preview_access_granted: false,
|
|
266
|
+
title: null,
|
|
267
|
+
message: null,
|
|
268
|
+
desktop_image: null,
|
|
269
|
+
mobile_image: null
|
|
270
|
+
};
|
|
96
271
|
return {
|
|
97
272
|
websiteId: raw.website_id,
|
|
98
273
|
companyId: raw.company_id,
|
|
274
|
+
contextSchemaVersion: raw.context_schema_version === 1 ? 1 : null,
|
|
275
|
+
chainId: raw.chain_id ?? null,
|
|
99
276
|
slug: raw.slug,
|
|
100
277
|
name: raw.name,
|
|
101
278
|
domain: raw.domain,
|
|
279
|
+
config: raw.config ?? null,
|
|
102
280
|
buyerAccountsEnabled: raw.buyer_accounts_enabled,
|
|
103
281
|
clerkPublishableKey: raw.clerk_publishable_key,
|
|
104
282
|
customerIdentification: {
|
|
@@ -114,56 +292,67 @@ async function getWebsitePublicConfig(options) {
|
|
|
114
292
|
isDefault: documentType.is_default
|
|
115
293
|
})
|
|
116
294
|
)
|
|
295
|
+
},
|
|
296
|
+
marketing: {
|
|
297
|
+
schemaVersion: marketing.schema_version,
|
|
298
|
+
enabled: marketing.enabled,
|
|
299
|
+
currencyCode: marketing.currency_code,
|
|
300
|
+
timeZone: marketing.time_zone,
|
|
301
|
+
attributionWindowDays: marketing.attribution_window_days,
|
|
302
|
+
consentPolicy: marketing.consent_policy ? {
|
|
303
|
+
id: marketing.consent_policy.id,
|
|
304
|
+
version: marketing.consent_policy.version,
|
|
305
|
+
statement: marketing.consent_policy.statement,
|
|
306
|
+
statementHash: marketing.consent_policy.statement_hash
|
|
307
|
+
} : null,
|
|
308
|
+
destinations: {
|
|
309
|
+
ga4: marketing.destinations.ga4 ? {
|
|
310
|
+
measurementId: marketing.destinations.ga4.measurement_id,
|
|
311
|
+
diagnosticMode: marketing.destinations.ga4.diagnostic_mode ?? false
|
|
312
|
+
} : null,
|
|
313
|
+
meta: marketing.destinations.meta ? {
|
|
314
|
+
pixelId: marketing.destinations.meta.pixel_id,
|
|
315
|
+
diagnosticMode: marketing.destinations.meta.diagnostic_mode ?? false
|
|
316
|
+
} : null,
|
|
317
|
+
clarity: marketing.destinations.clarity ? { projectId: marketing.destinations.clarity.project_id } : null
|
|
318
|
+
}
|
|
319
|
+
},
|
|
320
|
+
legal: {
|
|
321
|
+
schemaVersion: legal.schema_version,
|
|
322
|
+
version: legal.version,
|
|
323
|
+
contentHash: legal.content_hash,
|
|
324
|
+
documents: {
|
|
325
|
+
terms: legal.documents.terms,
|
|
326
|
+
shipping: legal.documents.shipping ?? null,
|
|
327
|
+
returns: legal.documents.returns ?? null,
|
|
328
|
+
privacy: legal.documents.privacy,
|
|
329
|
+
cookies: legal.documents.cookies
|
|
330
|
+
},
|
|
331
|
+
inquiriesChannel: legal.inquiries_channel,
|
|
332
|
+
directMarketingConsent: legal.direct_marketing_consent ? {
|
|
333
|
+
version: legal.direct_marketing_consent.version,
|
|
334
|
+
statement: legal.direct_marketing_consent.statement,
|
|
335
|
+
statementHash: legal.direct_marketing_consent.statement_hash
|
|
336
|
+
} : null,
|
|
337
|
+
cookieNotice: {
|
|
338
|
+
enabled: legal.cookie_notice.enabled,
|
|
339
|
+
message: legal.cookie_notice.message,
|
|
340
|
+
buttonLabel: legal.cookie_notice.button_label
|
|
341
|
+
}
|
|
342
|
+
},
|
|
343
|
+
construction: {
|
|
344
|
+
schemaVersion: construction.schema_version,
|
|
345
|
+
enabled: construction.enabled,
|
|
346
|
+
enforced: construction.enforced,
|
|
347
|
+
previewAccessGranted: construction.preview_access_granted,
|
|
348
|
+
title: construction.title,
|
|
349
|
+
message: construction.message,
|
|
350
|
+
desktopImage: mapConstructionImage(construction.desktop_image),
|
|
351
|
+
mobileImage: mapConstructionImage(construction.mobile_image)
|
|
117
352
|
}
|
|
118
353
|
};
|
|
119
354
|
}
|
|
120
355
|
|
|
121
|
-
// libs/sdk/src/transport/graphql.ts
|
|
122
|
-
async function executeGraphQL(url, query, variables, headers) {
|
|
123
|
-
const resolvedHeaders = typeof headers === "function" ? await headers() : headers ?? {};
|
|
124
|
-
let res;
|
|
125
|
-
try {
|
|
126
|
-
res = await fetch(url, {
|
|
127
|
-
method: "POST",
|
|
128
|
-
headers: {
|
|
129
|
-
"Content-Type": "application/json",
|
|
130
|
-
...resolvedHeaders
|
|
131
|
-
},
|
|
132
|
-
body: JSON.stringify({ query, variables })
|
|
133
|
-
});
|
|
134
|
-
} catch (err) {
|
|
135
|
-
throw new WebsiteError(
|
|
136
|
-
`Network error connecting to ${url}: ${err instanceof Error ? err.message : String(err)}`,
|
|
137
|
-
"NETWORK_ERROR"
|
|
138
|
-
);
|
|
139
|
-
}
|
|
140
|
-
if (res.status === 429) {
|
|
141
|
-
throw new WebsiteError(
|
|
142
|
-
"Rate limit exceeded. Please try again later.",
|
|
143
|
-
"RATE_LIMITED"
|
|
144
|
-
);
|
|
145
|
-
}
|
|
146
|
-
let body;
|
|
147
|
-
try {
|
|
148
|
-
body = await res.json();
|
|
149
|
-
} catch {
|
|
150
|
-
throw new WebsiteError(
|
|
151
|
-
`Non-JSON response from ${url} (status ${res.status})`,
|
|
152
|
-
"INVALID_RESPONSE"
|
|
153
|
-
);
|
|
154
|
-
}
|
|
155
|
-
if (body.errors && body.errors.length > 0) {
|
|
156
|
-
throw new GraphQLRequestError(body.errors);
|
|
157
|
-
}
|
|
158
|
-
if (!body.data) {
|
|
159
|
-
throw new WebsiteError(
|
|
160
|
-
"GraphQL response contains no data",
|
|
161
|
-
"EMPTY_RESPONSE"
|
|
162
|
-
);
|
|
163
|
-
}
|
|
164
|
-
return body.data;
|
|
165
|
-
}
|
|
166
|
-
|
|
167
356
|
// libs/sdk/src/transport/documents.ts
|
|
168
357
|
var RESOLVE_WEBSITE_BY_SLUG = (
|
|
169
358
|
/* GraphQL */
|
|
@@ -989,6 +1178,62 @@ var CHECK_AVAILABILITY = (
|
|
|
989
1178
|
product_variant_id
|
|
990
1179
|
in_stock
|
|
991
1180
|
label
|
|
1181
|
+
max_purchasable_quantity
|
|
1182
|
+
}
|
|
1183
|
+
}
|
|
1184
|
+
`
|
|
1185
|
+
);
|
|
1186
|
+
var GET_PRODUCT_LIVE_STATE = (
|
|
1187
|
+
/* GraphQL */
|
|
1188
|
+
`
|
|
1189
|
+
query GetProductLiveState(
|
|
1190
|
+
$variantIds: [uuid!]!
|
|
1191
|
+
$companyId: uuid!
|
|
1192
|
+
$chainId: uuid!
|
|
1193
|
+
$websiteId: uuid!
|
|
1194
|
+
) {
|
|
1195
|
+
product_variants(
|
|
1196
|
+
where: {
|
|
1197
|
+
id: { _in: $variantIds }
|
|
1198
|
+
is_disabled: { _eq: false }
|
|
1199
|
+
product: {
|
|
1200
|
+
company_id: { _eq: $companyId }
|
|
1201
|
+
product_website_publications: {
|
|
1202
|
+
website_id: { _eq: $websiteId }
|
|
1203
|
+
is_visible: { _eq: true }
|
|
1204
|
+
}
|
|
1205
|
+
chain_products: {
|
|
1206
|
+
chain_id: { _eq: $chainId }
|
|
1207
|
+
is_enabled: { _eq: true }
|
|
1208
|
+
status: { _eq: active }
|
|
1209
|
+
}
|
|
1210
|
+
}
|
|
1211
|
+
}
|
|
1212
|
+
) {
|
|
1213
|
+
id
|
|
1214
|
+
product_variant_prices(
|
|
1215
|
+
where: {
|
|
1216
|
+
price_type: { _in: [CLIENT, SALE, WHOLESALE, WHOLESALE_2] }
|
|
1217
|
+
_or: [
|
|
1218
|
+
{ chain_id: { _eq: $chainId } }
|
|
1219
|
+
{ chain_id: { _is_null: true } }
|
|
1220
|
+
]
|
|
1221
|
+
}
|
|
1222
|
+
) {
|
|
1223
|
+
id
|
|
1224
|
+
price
|
|
1225
|
+
price_type
|
|
1226
|
+
chain_id
|
|
1227
|
+
}
|
|
1228
|
+
}
|
|
1229
|
+
websiteProductAvailability(
|
|
1230
|
+
product_variant_ids: $variantIds
|
|
1231
|
+
chain_id: $chainId
|
|
1232
|
+
) {
|
|
1233
|
+
product_variant_id
|
|
1234
|
+
in_stock
|
|
1235
|
+
label
|
|
1236
|
+
max_purchasable_quantity
|
|
992
1237
|
}
|
|
993
1238
|
}
|
|
994
1239
|
`
|
|
@@ -1098,6 +1343,7 @@ var START_GUEST_CHECKOUT = (
|
|
|
1098
1343
|
$contact: WebsiteGuestContactInput!
|
|
1099
1344
|
$items: [WebsiteCheckoutItemInput!]!
|
|
1100
1345
|
$fulfillmentFamily: String
|
|
1346
|
+
$marketingAttribution: WebsiteMarketingAttributionInput
|
|
1101
1347
|
) {
|
|
1102
1348
|
websiteStartGuestCheckout(
|
|
1103
1349
|
website_id: $websiteId
|
|
@@ -1105,6 +1351,7 @@ var START_GUEST_CHECKOUT = (
|
|
|
1105
1351
|
contact: $contact
|
|
1106
1352
|
items: $items
|
|
1107
1353
|
fulfillment_family: $fulfillmentFamily
|
|
1354
|
+
marketing_attribution: $marketingAttribution
|
|
1108
1355
|
) {
|
|
1109
1356
|
success
|
|
1110
1357
|
order_id
|
|
@@ -1262,6 +1509,8 @@ var CHECKOUT = (
|
|
|
1262
1509
|
$paymentMethodId: uuid
|
|
1263
1510
|
$buyerAccountOptIn: Boolean
|
|
1264
1511
|
$initialPayment: WebsiteInitialPaymentInput
|
|
1512
|
+
$marketingAttribution: WebsiteMarketingAttributionInput
|
|
1513
|
+
$advertisingConsent: WebsiteAdvertisingConsentInput
|
|
1265
1514
|
) {
|
|
1266
1515
|
websiteCheckout(
|
|
1267
1516
|
order_id: $orderId
|
|
@@ -1274,6 +1523,8 @@ var CHECKOUT = (
|
|
|
1274
1523
|
payment_method_id: $paymentMethodId
|
|
1275
1524
|
buyer_account_opt_in: $buyerAccountOptIn
|
|
1276
1525
|
initial_payment: $initialPayment
|
|
1526
|
+
marketing_attribution: $marketingAttribution
|
|
1527
|
+
advertising_consent: $advertisingConsent
|
|
1277
1528
|
) {
|
|
1278
1529
|
success
|
|
1279
1530
|
order_id
|
|
@@ -2200,13 +2451,121 @@ var GET_SEARCH_SUGGESTIONS = (
|
|
|
2200
2451
|
`
|
|
2201
2452
|
);
|
|
2202
2453
|
|
|
2454
|
+
// libs/sdk/src/transport/graphql.ts
|
|
2455
|
+
var DEFAULT_MAX_ATTEMPTS = 4;
|
|
2456
|
+
var DEFAULT_MAX_TOTAL_WAIT_MS = 3 * 60 * 1e3;
|
|
2457
|
+
var DEFAULT_JITTER_RATIO = 0.1;
|
|
2458
|
+
function parseRetryAfterSeconds(res, body) {
|
|
2459
|
+
const header = res.headers?.get?.("Retry-After")?.trim();
|
|
2460
|
+
if (header) {
|
|
2461
|
+
const seconds = Number(header);
|
|
2462
|
+
if (Number.isFinite(seconds) && seconds >= 0) return Math.ceil(seconds);
|
|
2463
|
+
const retryAt = Date.parse(header);
|
|
2464
|
+
if (Number.isFinite(retryAt)) {
|
|
2465
|
+
return Math.max(Math.ceil((retryAt - Date.now()) / 1e3), 0);
|
|
2466
|
+
}
|
|
2467
|
+
}
|
|
2468
|
+
if (body && typeof body === "object") {
|
|
2469
|
+
const seconds = Number(
|
|
2470
|
+
body.retry_after_seconds
|
|
2471
|
+
);
|
|
2472
|
+
if (Number.isFinite(seconds) && seconds >= 0) return Math.ceil(seconds);
|
|
2473
|
+
}
|
|
2474
|
+
return 1;
|
|
2475
|
+
}
|
|
2476
|
+
async function createRateLimitError(res) {
|
|
2477
|
+
let body;
|
|
2478
|
+
try {
|
|
2479
|
+
body = await res.json();
|
|
2480
|
+
} catch {
|
|
2481
|
+
body = null;
|
|
2482
|
+
}
|
|
2483
|
+
const message = body && typeof body === "object" && typeof body.message === "string" ? body.message : "Rate limit exceeded. Please try again later.";
|
|
2484
|
+
return new RateLimitError(message, parseRetryAfterSeconds(res, body));
|
|
2485
|
+
}
|
|
2486
|
+
function getRetryDelayMs(error, options) {
|
|
2487
|
+
const retryAfterMs = (error.retryAfterSeconds ?? 1) * 1e3;
|
|
2488
|
+
const jitterRatio = Math.max(
|
|
2489
|
+
0,
|
|
2490
|
+
Math.min(options.jitterRatio ?? DEFAULT_JITTER_RATIO, 1)
|
|
2491
|
+
);
|
|
2492
|
+
return Math.ceil(retryAfterMs * (1 + Math.random() * jitterRatio));
|
|
2493
|
+
}
|
|
2494
|
+
function wait(delayMs) {
|
|
2495
|
+
return new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
2496
|
+
}
|
|
2497
|
+
async function executeGraphQL(url, query, variables, headers, options) {
|
|
2498
|
+
const retry = options?.retry;
|
|
2499
|
+
const maxAttempts = Math.max(
|
|
2500
|
+
1,
|
|
2501
|
+
Math.floor(retry?.maxAttempts ?? DEFAULT_MAX_ATTEMPTS)
|
|
2502
|
+
);
|
|
2503
|
+
const maxTotalWaitMs = Math.max(
|
|
2504
|
+
0,
|
|
2505
|
+
retry?.maxTotalWaitMs ?? DEFAULT_MAX_TOTAL_WAIT_MS
|
|
2506
|
+
);
|
|
2507
|
+
let totalWaitMs = 0;
|
|
2508
|
+
let res = null;
|
|
2509
|
+
for (let attempt = 1; attempt <= (retry ? maxAttempts : 1); attempt += 1) {
|
|
2510
|
+
const resolvedHeaders = typeof headers === "function" ? await headers() : headers ?? {};
|
|
2511
|
+
try {
|
|
2512
|
+
res = await fetch(url, {
|
|
2513
|
+
method: "POST",
|
|
2514
|
+
headers: {
|
|
2515
|
+
"Content-Type": "application/json",
|
|
2516
|
+
...resolvedHeaders
|
|
2517
|
+
},
|
|
2518
|
+
body: JSON.stringify({ query, variables })
|
|
2519
|
+
});
|
|
2520
|
+
} catch (err) {
|
|
2521
|
+
throw new WebsiteError(
|
|
2522
|
+
`Network error connecting to ${url}: ${err instanceof Error ? err.message : String(err)}`,
|
|
2523
|
+
"NETWORK_ERROR"
|
|
2524
|
+
);
|
|
2525
|
+
}
|
|
2526
|
+
if (res.status !== 429) break;
|
|
2527
|
+
const rateLimitError = await createRateLimitError(res);
|
|
2528
|
+
const delayMs = getRetryDelayMs(rateLimitError, retry ?? {});
|
|
2529
|
+
const canRetry = Boolean(retry) && attempt < maxAttempts && totalWaitMs + delayMs <= maxTotalWaitMs;
|
|
2530
|
+
if (!canRetry) throw rateLimitError;
|
|
2531
|
+
totalWaitMs += delayMs;
|
|
2532
|
+
await wait(delayMs);
|
|
2533
|
+
}
|
|
2534
|
+
if (!res) {
|
|
2535
|
+
throw new WebsiteError(
|
|
2536
|
+
"GraphQL request did not produce a response",
|
|
2537
|
+
"EMPTY_RESPONSE"
|
|
2538
|
+
);
|
|
2539
|
+
}
|
|
2540
|
+
let body;
|
|
2541
|
+
try {
|
|
2542
|
+
body = await res.json();
|
|
2543
|
+
} catch {
|
|
2544
|
+
throw new WebsiteError(
|
|
2545
|
+
`Non-JSON response from ${url} (status ${res.status})`,
|
|
2546
|
+
"INVALID_RESPONSE"
|
|
2547
|
+
);
|
|
2548
|
+
}
|
|
2549
|
+
if (body.errors && body.errors.length > 0) {
|
|
2550
|
+
throw new GraphQLRequestError(body.errors);
|
|
2551
|
+
}
|
|
2552
|
+
if (!body.data) {
|
|
2553
|
+
throw new WebsiteError(
|
|
2554
|
+
"GraphQL response contains no data",
|
|
2555
|
+
"EMPTY_RESPONSE"
|
|
2556
|
+
);
|
|
2557
|
+
}
|
|
2558
|
+
return body.data;
|
|
2559
|
+
}
|
|
2560
|
+
|
|
2203
2561
|
// libs/sdk/src/modules/availability.ts
|
|
2204
2562
|
var MAX_AVAILABILITY_VARIANT_IDS = 100;
|
|
2205
2563
|
function mapAvailability(raw) {
|
|
2206
2564
|
return {
|
|
2207
2565
|
productVariantId: raw.product_variant_id,
|
|
2208
2566
|
inStock: raw.in_stock,
|
|
2209
|
-
label: raw.label
|
|
2567
|
+
label: raw.label,
|
|
2568
|
+
maxPurchasableQuantity: raw.max_purchasable_quantity
|
|
2210
2569
|
};
|
|
2211
2570
|
}
|
|
2212
2571
|
function chunkArray(items, chunkSize) {
|
|
@@ -2232,7 +2591,10 @@ function createAvailabilityModule(graphqlUrl, getHeaders, getContext) {
|
|
|
2232
2591
|
if (productVariantIds.length === 0) return [];
|
|
2233
2592
|
const variantIds = uniqueValues(productVariantIds);
|
|
2234
2593
|
const results = [];
|
|
2235
|
-
for (const chunk of chunkArray(
|
|
2594
|
+
for (const chunk of chunkArray(
|
|
2595
|
+
variantIds,
|
|
2596
|
+
MAX_AVAILABILITY_VARIANT_IDS
|
|
2597
|
+
)) {
|
|
2236
2598
|
const data = await executeGraphQL(
|
|
2237
2599
|
graphqlUrl,
|
|
2238
2600
|
CHECK_AVAILABILITY,
|
|
@@ -2251,6 +2613,255 @@ function createAvailabilityModule(graphqlUrl, getHeaders, getContext) {
|
|
|
2251
2613
|
};
|
|
2252
2614
|
}
|
|
2253
2615
|
|
|
2616
|
+
// libs/sdk/src/marketing/sanitize.ts
|
|
2617
|
+
var SENSITIVE_PARAMETER_PATTERNS = [
|
|
2618
|
+
/token/i,
|
|
2619
|
+
/secret/i,
|
|
2620
|
+
/password/i,
|
|
2621
|
+
/authorization/i,
|
|
2622
|
+
/session/i,
|
|
2623
|
+
/ticket/i,
|
|
2624
|
+
/code_verifier/i,
|
|
2625
|
+
/client_secret/i,
|
|
2626
|
+
/payment/i,
|
|
2627
|
+
/order[_-]?(?:id|token)/i,
|
|
2628
|
+
/public[_-]?order/i,
|
|
2629
|
+
/clerk/i,
|
|
2630
|
+
/oauth/i,
|
|
2631
|
+
/email/i,
|
|
2632
|
+
/phone/i,
|
|
2633
|
+
/document/i
|
|
2634
|
+
];
|
|
2635
|
+
var ALLOWED_MARKETING_PARAMETERS = /* @__PURE__ */ new Set([
|
|
2636
|
+
"utm_source",
|
|
2637
|
+
"utm_medium",
|
|
2638
|
+
"utm_campaign",
|
|
2639
|
+
"utm_term",
|
|
2640
|
+
"utm_content",
|
|
2641
|
+
"utm_source_platform",
|
|
2642
|
+
"utm_id",
|
|
2643
|
+
"gclid",
|
|
2644
|
+
"gbraid",
|
|
2645
|
+
"wbraid",
|
|
2646
|
+
"fbclid",
|
|
2647
|
+
"ttclid",
|
|
2648
|
+
"msclkid"
|
|
2649
|
+
]);
|
|
2650
|
+
function sanitizeMarketingUrl(input, baseUrl) {
|
|
2651
|
+
try {
|
|
2652
|
+
const url = new URL(input, baseUrl);
|
|
2653
|
+
const safeParameters = new URLSearchParams();
|
|
2654
|
+
for (const [key, value] of url.searchParams.entries()) {
|
|
2655
|
+
if (ALLOWED_MARKETING_PARAMETERS.has(key.toLowerCase()) && !SENSITIVE_PARAMETER_PATTERNS.some((pattern) => pattern.test(key))) {
|
|
2656
|
+
safeParameters.set(key.toLowerCase(), value.slice(0, 500));
|
|
2657
|
+
}
|
|
2658
|
+
}
|
|
2659
|
+
url.search = safeParameters.toString();
|
|
2660
|
+
url.hash = "";
|
|
2661
|
+
return url.toString();
|
|
2662
|
+
} catch {
|
|
2663
|
+
return "";
|
|
2664
|
+
}
|
|
2665
|
+
}
|
|
2666
|
+
function getAllowedMarketingParameters(input, baseUrl) {
|
|
2667
|
+
const sanitized = sanitizeMarketingUrl(input, baseUrl);
|
|
2668
|
+
if (!sanitized) return {};
|
|
2669
|
+
const url = new URL(sanitized);
|
|
2670
|
+
return Object.fromEntries(url.searchParams.entries());
|
|
2671
|
+
}
|
|
2672
|
+
|
|
2673
|
+
// libs/sdk/src/marketing/attribution.ts
|
|
2674
|
+
var STORAGE_PREFIX = "bacano:marketing:attribution:v1";
|
|
2675
|
+
var GA4_CLIENT_ID_PATTERN = /^\d+\.\d+$/;
|
|
2676
|
+
var META_BROWSER_ID_PATTERN = /^fb\.\d+\.\d+\.[A-Za-z0-9_-]+$/;
|
|
2677
|
+
function storageKey(websiteId) {
|
|
2678
|
+
return `${STORAGE_PREFIX}:${websiteId}`;
|
|
2679
|
+
}
|
|
2680
|
+
function readStored(websiteId) {
|
|
2681
|
+
if (typeof window === "undefined") return null;
|
|
2682
|
+
try {
|
|
2683
|
+
const value = window.localStorage.getItem(storageKey(websiteId));
|
|
2684
|
+
if (!value) return null;
|
|
2685
|
+
const parsed = JSON.parse(value);
|
|
2686
|
+
if (new Date(parsed.expiresAt).getTime() <= Date.now()) {
|
|
2687
|
+
window.localStorage.removeItem(storageKey(websiteId));
|
|
2688
|
+
return null;
|
|
2689
|
+
}
|
|
2690
|
+
return parsed;
|
|
2691
|
+
} catch {
|
|
2692
|
+
return null;
|
|
2693
|
+
}
|
|
2694
|
+
}
|
|
2695
|
+
function writeStored(websiteId, value) {
|
|
2696
|
+
if (typeof window === "undefined") return;
|
|
2697
|
+
try {
|
|
2698
|
+
window.localStorage.setItem(storageKey(websiteId), JSON.stringify(value));
|
|
2699
|
+
} catch {
|
|
2700
|
+
}
|
|
2701
|
+
}
|
|
2702
|
+
function readCookie(name) {
|
|
2703
|
+
if (typeof document === "undefined") return void 0;
|
|
2704
|
+
try {
|
|
2705
|
+
const prefix = `${name}=`;
|
|
2706
|
+
return document.cookie.split(";").map((part) => part.trim()).find((part) => part.startsWith(prefix))?.slice(prefix.length);
|
|
2707
|
+
} catch {
|
|
2708
|
+
return void 0;
|
|
2709
|
+
}
|
|
2710
|
+
}
|
|
2711
|
+
function validMetaBrowserId(value) {
|
|
2712
|
+
const normalized = value?.trim();
|
|
2713
|
+
return normalized && normalized.length <= 512 && META_BROWSER_ID_PATTERN.test(normalized) ? normalized : void 0;
|
|
2714
|
+
}
|
|
2715
|
+
function optional(value) {
|
|
2716
|
+
const normalized = value?.trim();
|
|
2717
|
+
return normalized ? normalized.slice(0, 500) : void 0;
|
|
2718
|
+
}
|
|
2719
|
+
function buildTouchpoint() {
|
|
2720
|
+
if (typeof window === "undefined") return null;
|
|
2721
|
+
const params = getAllowedMarketingParameters(window.location.href);
|
|
2722
|
+
const referrerHost = (() => {
|
|
2723
|
+
if (!document.referrer) return void 0;
|
|
2724
|
+
try {
|
|
2725
|
+
const hostname = new URL(document.referrer).hostname;
|
|
2726
|
+
return hostname === window.location.hostname ? void 0 : hostname.slice(0, 255);
|
|
2727
|
+
} catch {
|
|
2728
|
+
return void 0;
|
|
2729
|
+
}
|
|
2730
|
+
})();
|
|
2731
|
+
return {
|
|
2732
|
+
capturedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2733
|
+
landingUrl: sanitizeMarketingUrl(window.location.href),
|
|
2734
|
+
referrerHost,
|
|
2735
|
+
source: optional(params.utm_source),
|
|
2736
|
+
medium: optional(params.utm_medium),
|
|
2737
|
+
campaign: optional(params.utm_campaign),
|
|
2738
|
+
term: optional(params.utm_term),
|
|
2739
|
+
content: optional(params.utm_content),
|
|
2740
|
+
sourcePlatform: optional(params.utm_source_platform),
|
|
2741
|
+
campaignId: optional(params.utm_id),
|
|
2742
|
+
gclid: optional(params.gclid),
|
|
2743
|
+
gbraid: optional(params.gbraid),
|
|
2744
|
+
wbraid: optional(params.wbraid),
|
|
2745
|
+
fbclid: optional(params.fbclid),
|
|
2746
|
+
ttclid: optional(params.ttclid),
|
|
2747
|
+
msclkid: optional(params.msclkid)
|
|
2748
|
+
};
|
|
2749
|
+
}
|
|
2750
|
+
function isNonDirect(touchpoint) {
|
|
2751
|
+
return Boolean(
|
|
2752
|
+
touchpoint.source || touchpoint.medium || touchpoint.campaign || touchpoint.gclid || touchpoint.gbraid || touchpoint.wbraid || touchpoint.fbclid || touchpoint.ttclid || touchpoint.msclkid || touchpoint.referrerHost
|
|
2753
|
+
);
|
|
2754
|
+
}
|
|
2755
|
+
function toPublic(stored) {
|
|
2756
|
+
return {
|
|
2757
|
+
schemaVersion: 1,
|
|
2758
|
+
firstTouch: stored.firstTouch,
|
|
2759
|
+
lastNonDirectTouch: stored.lastNonDirectTouch,
|
|
2760
|
+
ga4ClientId: stored.ga4ClientId,
|
|
2761
|
+
ga4SessionId: stored.ga4SessionId,
|
|
2762
|
+
metaFbp: stored.metaFbp,
|
|
2763
|
+
metaFbc: stored.metaFbc,
|
|
2764
|
+
clientUserAgent: stored.clientUserAgent,
|
|
2765
|
+
capturedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2766
|
+
expiresAt: stored.expiresAt
|
|
2767
|
+
};
|
|
2768
|
+
}
|
|
2769
|
+
function refreshWebsiteMarketingMetaContext(websiteId) {
|
|
2770
|
+
try {
|
|
2771
|
+
const stored = readStored(websiteId);
|
|
2772
|
+
if (!stored || typeof window === "undefined") return false;
|
|
2773
|
+
const fbp = validMetaBrowserId(readCookie("_fbp"));
|
|
2774
|
+
const clickTouchpoint = stored.lastNonDirectTouch ?? stored.firstTouch;
|
|
2775
|
+
const derivedFbc = clickTouchpoint.fbclid ? `fb.1.${new Date(clickTouchpoint.capturedAt).getTime()}.${clickTouchpoint.fbclid}` : void 0;
|
|
2776
|
+
const fbc = validMetaBrowserId(readCookie("_fbc") ?? derivedFbc);
|
|
2777
|
+
const clientUserAgent = window.navigator?.userAgent.trim().slice(0, 1024);
|
|
2778
|
+
writeStored(websiteId, {
|
|
2779
|
+
...stored,
|
|
2780
|
+
metaFbp: fbp ?? stored.metaFbp,
|
|
2781
|
+
metaFbc: fbc ?? stored.metaFbc,
|
|
2782
|
+
clientUserAgent: clientUserAgent || stored.clientUserAgent
|
|
2783
|
+
});
|
|
2784
|
+
return Boolean(fbp || fbc || clientUserAgent);
|
|
2785
|
+
} catch {
|
|
2786
|
+
return false;
|
|
2787
|
+
}
|
|
2788
|
+
}
|
|
2789
|
+
function storeWebsiteMarketingGa4ClientId(websiteId, clientId) {
|
|
2790
|
+
if (typeof clientId !== "string") return false;
|
|
2791
|
+
const normalized = clientId.trim();
|
|
2792
|
+
if (!GA4_CLIENT_ID_PATTERN.test(normalized)) return false;
|
|
2793
|
+
const stored = readStored(websiteId);
|
|
2794
|
+
if (!stored) return false;
|
|
2795
|
+
writeStored(websiteId, { ...stored, ga4ClientId: normalized });
|
|
2796
|
+
return true;
|
|
2797
|
+
}
|
|
2798
|
+
function storeWebsiteMarketingGa4SessionId(websiteId, sessionId) {
|
|
2799
|
+
const normalized = typeof sessionId === "number" && Number.isSafeInteger(sessionId) ? String(sessionId) : typeof sessionId === "string" ? sessionId.trim() : "";
|
|
2800
|
+
const numericValue = Number(normalized);
|
|
2801
|
+
if (!/^\d{1,16}$/.test(normalized) || !Number.isSafeInteger(numericValue) || numericValue <= 0) {
|
|
2802
|
+
return false;
|
|
2803
|
+
}
|
|
2804
|
+
const stored = readStored(websiteId);
|
|
2805
|
+
if (!stored) return false;
|
|
2806
|
+
writeStored(websiteId, { ...stored, ga4SessionId: normalized });
|
|
2807
|
+
return true;
|
|
2808
|
+
}
|
|
2809
|
+
function captureWebsiteMarketingAttribution(websiteId, windowDays) {
|
|
2810
|
+
const current = buildTouchpoint();
|
|
2811
|
+
if (!current) return null;
|
|
2812
|
+
const stored = readStored(websiteId);
|
|
2813
|
+
const expiresAt = stored?.expiresAt ?? new Date(Date.now() + Math.max(1, windowDays) * 864e5).toISOString();
|
|
2814
|
+
const next = {
|
|
2815
|
+
firstTouch: stored?.firstTouch ?? current,
|
|
2816
|
+
lastNonDirectTouch: isNonDirect(current) ? current : stored?.lastNonDirectTouch ?? null,
|
|
2817
|
+
ga4ClientId: stored?.ga4ClientId,
|
|
2818
|
+
ga4SessionId: stored?.ga4SessionId,
|
|
2819
|
+
metaFbp: stored?.metaFbp,
|
|
2820
|
+
metaFbc: stored?.metaFbc,
|
|
2821
|
+
clientUserAgent: stored?.clientUserAgent,
|
|
2822
|
+
expiresAt
|
|
2823
|
+
};
|
|
2824
|
+
writeStored(websiteId, next);
|
|
2825
|
+
return toPublic(next);
|
|
2826
|
+
}
|
|
2827
|
+
function getWebsiteMarketingAttribution(websiteId) {
|
|
2828
|
+
const stored = readStored(websiteId);
|
|
2829
|
+
return stored ? toPublic(stored) : null;
|
|
2830
|
+
}
|
|
2831
|
+
function serializeTouchpoint(touchpoint) {
|
|
2832
|
+
return {
|
|
2833
|
+
captured_at: touchpoint.capturedAt,
|
|
2834
|
+
landing_url: touchpoint.landingUrl,
|
|
2835
|
+
referrer_host: touchpoint.referrerHost,
|
|
2836
|
+
source: touchpoint.source,
|
|
2837
|
+
medium: touchpoint.medium,
|
|
2838
|
+
campaign: touchpoint.campaign,
|
|
2839
|
+
term: touchpoint.term,
|
|
2840
|
+
content: touchpoint.content,
|
|
2841
|
+
source_platform: touchpoint.sourcePlatform,
|
|
2842
|
+
campaign_id: touchpoint.campaignId,
|
|
2843
|
+
gclid: touchpoint.gclid,
|
|
2844
|
+
gbraid: touchpoint.gbraid,
|
|
2845
|
+
wbraid: touchpoint.wbraid,
|
|
2846
|
+
fbclid: touchpoint.fbclid,
|
|
2847
|
+
ttclid: touchpoint.ttclid,
|
|
2848
|
+
msclkid: touchpoint.msclkid
|
|
2849
|
+
};
|
|
2850
|
+
}
|
|
2851
|
+
function serializeWebsiteMarketingAttribution(attribution) {
|
|
2852
|
+
return {
|
|
2853
|
+
schema_version: attribution.schemaVersion,
|
|
2854
|
+
first_touch: serializeTouchpoint(attribution.firstTouch),
|
|
2855
|
+
last_non_direct_touch: attribution.lastNonDirectTouch ? serializeTouchpoint(attribution.lastNonDirectTouch) : null,
|
|
2856
|
+
ga4_client_id: attribution.ga4ClientId,
|
|
2857
|
+
ga4_session_id: attribution.ga4SessionId,
|
|
2858
|
+
meta_fbp: attribution.metaFbp,
|
|
2859
|
+
meta_fbc: attribution.metaFbc,
|
|
2860
|
+
client_user_agent: attribution.clientUserAgent,
|
|
2861
|
+
captured_at: attribution.capturedAt
|
|
2862
|
+
};
|
|
2863
|
+
}
|
|
2864
|
+
|
|
2254
2865
|
// libs/sdk/src/modules/cart.ts
|
|
2255
2866
|
function mapCartItem(raw) {
|
|
2256
2867
|
return {
|
|
@@ -2371,7 +2982,8 @@ function createCartModule(graphqlUrl, getHeaders, getContext, cartSessionStorage
|
|
|
2371
2982
|
items: input.items.map((item) => ({
|
|
2372
2983
|
product_variant_id: item.productVariantId,
|
|
2373
2984
|
quantity: item.quantity
|
|
2374
|
-
}))
|
|
2985
|
+
})),
|
|
2986
|
+
marketingAttribution: input.marketingAttribution ? serializeWebsiteMarketingAttribution(input.marketingAttribution) : void 0
|
|
2375
2987
|
},
|
|
2376
2988
|
getHeaders
|
|
2377
2989
|
);
|
|
@@ -2493,7 +3105,9 @@ function createCartSessionStorage(kind, getSlug) {
|
|
|
2493
3105
|
const key = readKey();
|
|
2494
3106
|
const match = document.cookie.split("; ").find((row) => row.startsWith(`${key}=`));
|
|
2495
3107
|
if (!match) return null;
|
|
2496
|
-
return JSON.parse(
|
|
3108
|
+
return JSON.parse(
|
|
3109
|
+
decodeURIComponent(match.split("=").slice(1).join("="))
|
|
3110
|
+
);
|
|
2497
3111
|
} catch {
|
|
2498
3112
|
return null;
|
|
2499
3113
|
}
|
|
@@ -2533,46 +3147,129 @@ function createCartSessionStorage(kind, getSlug) {
|
|
|
2533
3147
|
};
|
|
2534
3148
|
}
|
|
2535
3149
|
|
|
2536
|
-
// libs/sdk/src/
|
|
2537
|
-
var
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
}
|
|
2546
|
-
|
|
2547
|
-
const
|
|
2548
|
-
const
|
|
2549
|
-
const
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
|
|
2555
|
-
|
|
2556
|
-
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
|
|
2561
|
-
|
|
2562
|
-
|
|
2563
|
-
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
|
|
3150
|
+
// libs/sdk/src/modules/static-catalog.ts
|
|
3151
|
+
var STATIC_CATALOG_PAGE_SIZE = 100;
|
|
3152
|
+
var STATIC_CATALOG_MAX_PRODUCTS = 1e4;
|
|
3153
|
+
var STATIC_CATALOG_INITIAL_PAGE_SIZE = 24;
|
|
3154
|
+
async function collectStaticCatalog({
|
|
3155
|
+
websiteId,
|
|
3156
|
+
snapshot: options,
|
|
3157
|
+
bootstrap,
|
|
3158
|
+
loadPage
|
|
3159
|
+
}) {
|
|
3160
|
+
const pageSize = options.pageSize ?? STATIC_CATALOG_PAGE_SIZE;
|
|
3161
|
+
const maxProducts = options.maxProducts ?? STATIC_CATALOG_MAX_PRODUCTS;
|
|
3162
|
+
const initialPageSize = bootstrap?.initialPageSize ?? STATIC_CATALOG_INITIAL_PAGE_SIZE;
|
|
3163
|
+
const sortBy = bootstrap?.sortBy ?? "name_asc";
|
|
3164
|
+
if (!Number.isInteger(pageSize) || pageSize < 1 || pageSize > 100) {
|
|
3165
|
+
throw new WebsiteError(
|
|
3166
|
+
"Static catalog pageSize must be an integer between 1 and 100.",
|
|
3167
|
+
"INVALID_STATIC_CATALOG_PAGE_SIZE"
|
|
3168
|
+
);
|
|
3169
|
+
}
|
|
3170
|
+
if (!Number.isInteger(maxProducts) || maxProducts < 1 || maxProducts > STATIC_CATALOG_MAX_PRODUCTS) {
|
|
3171
|
+
throw new WebsiteError(
|
|
3172
|
+
`Static catalog maxProducts must be an integer between 1 and ${STATIC_CATALOG_MAX_PRODUCTS}.`,
|
|
3173
|
+
"INVALID_STATIC_CATALOG_MAX_PRODUCTS"
|
|
3174
|
+
);
|
|
3175
|
+
}
|
|
3176
|
+
if (bootstrap && (!Number.isInteger(initialPageSize) || initialPageSize < 1 || initialPageSize > 100)) {
|
|
3177
|
+
throw new WebsiteError(
|
|
3178
|
+
"Static catalog initialPageSize must be an integer between 1 and 100.",
|
|
3179
|
+
"INVALID_STATIC_CATALOG_INITIAL_PAGE_SIZE"
|
|
3180
|
+
);
|
|
3181
|
+
}
|
|
3182
|
+
const generatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
3183
|
+
const products = [];
|
|
3184
|
+
let pageCount = 0;
|
|
3185
|
+
let expectedTotal = null;
|
|
3186
|
+
let firstPage = null;
|
|
3187
|
+
for (let offset = 0; ; ) {
|
|
3188
|
+
const includeBootstrapData = Boolean(bootstrap && offset === 0);
|
|
3189
|
+
const requestLimit = includeBootstrapData ? Math.max(pageSize, initialPageSize) : pageSize;
|
|
3190
|
+
const page = await loadPage({
|
|
3191
|
+
categoryListKey: options.categoryListKey,
|
|
3192
|
+
attributeListKey: options.attributeListKey,
|
|
3193
|
+
sortBy,
|
|
3194
|
+
limit: requestLimit,
|
|
3195
|
+
offset,
|
|
3196
|
+
include: {
|
|
3197
|
+
products: true,
|
|
3198
|
+
detailVariants: true,
|
|
3199
|
+
categories: includeBootstrapData,
|
|
3200
|
+
attributes: true,
|
|
3201
|
+
filters: includeBootstrapData,
|
|
3202
|
+
brands: includeBootstrapData,
|
|
3203
|
+
debug: false
|
|
3204
|
+
}
|
|
3205
|
+
});
|
|
3206
|
+
if (offset === 0) firstPage = page;
|
|
3207
|
+
pageCount += 1;
|
|
3208
|
+
const pageTotal = page.pagination.total;
|
|
3209
|
+
if (expectedTotal === null) {
|
|
3210
|
+
expectedTotal = pageTotal;
|
|
3211
|
+
} else if (pageTotal !== expectedTotal) {
|
|
3212
|
+
throwCatalogChanged();
|
|
3213
|
+
}
|
|
3214
|
+
if (pageTotal > maxProducts) {
|
|
3215
|
+
throw new WebsiteError(
|
|
3216
|
+
`Static catalog contains ${pageTotal} products and exceeds maxProducts ${maxProducts}.`,
|
|
3217
|
+
"STATIC_CATALOG_LIMIT_EXCEEDED"
|
|
3218
|
+
);
|
|
3219
|
+
}
|
|
3220
|
+
products.push(...page.products);
|
|
3221
|
+
if (!page.pagination.hasMore) break;
|
|
3222
|
+
if (page.products.length === 0) {
|
|
3223
|
+
throw new WebsiteError(
|
|
3224
|
+
"Static catalog pagination did not advance.",
|
|
3225
|
+
"STATIC_CATALOG_PAGINATION_STALLED"
|
|
3226
|
+
);
|
|
3227
|
+
}
|
|
3228
|
+
offset += page.products.length;
|
|
3229
|
+
}
|
|
3230
|
+
const total = expectedTotal ?? 0;
|
|
3231
|
+
const uniqueProductIds = new Set(products.map((product) => product.id));
|
|
3232
|
+
if (products.length !== total || uniqueProductIds.size !== total) {
|
|
3233
|
+
throwCatalogChanged();
|
|
3234
|
+
}
|
|
3235
|
+
const snapshot = {
|
|
3236
|
+
schemaVersion: 1,
|
|
3237
|
+
snapshotId: `${websiteId}:${generatedAt}`,
|
|
3238
|
+
generatedAt,
|
|
3239
|
+
websiteId,
|
|
3240
|
+
products,
|
|
3241
|
+
total,
|
|
3242
|
+
pageSize,
|
|
3243
|
+
pageCount
|
|
2570
3244
|
};
|
|
3245
|
+
const initialPage = bootstrap && firstPage ? {
|
|
3246
|
+
...firstPage,
|
|
3247
|
+
products: firstPage.products.slice(0, initialPageSize),
|
|
3248
|
+
pagination: {
|
|
3249
|
+
...firstPage.pagination,
|
|
3250
|
+
limit: initialPageSize,
|
|
3251
|
+
offset: 0,
|
|
3252
|
+
hasMore: total > initialPageSize
|
|
3253
|
+
}
|
|
3254
|
+
} : null;
|
|
3255
|
+
return { snapshot, initialPage };
|
|
3256
|
+
}
|
|
3257
|
+
function throwCatalogChanged() {
|
|
3258
|
+
throw new WebsiteError(
|
|
3259
|
+
"Static catalog changed while it was being collected. Retry the build to get a consistent snapshot.",
|
|
3260
|
+
"STATIC_CATALOG_CHANGED_DURING_BUILD"
|
|
3261
|
+
);
|
|
2571
3262
|
}
|
|
2572
3263
|
|
|
2573
3264
|
// libs/sdk/src/modules/catalog.ts
|
|
2574
3265
|
var MAX_AVAILABILITY_VARIANT_IDS2 = 100;
|
|
2575
3266
|
var DEFAULT_FILTER_ATTRIBUTE_LIST_KEY = "atributos";
|
|
3267
|
+
var MAX_LIVE_STATE_VARIANT_IDS = 100;
|
|
3268
|
+
var STATIC_CATALOG_RETRY = {
|
|
3269
|
+
maxAttempts: 4,
|
|
3270
|
+
maxTotalWaitMs: 3 * 60 * 1e3,
|
|
3271
|
+
jitterRatio: 0.1
|
|
3272
|
+
};
|
|
2576
3273
|
function mapPrice(raw) {
|
|
2577
3274
|
return {
|
|
2578
3275
|
id: raw.id,
|
|
@@ -2605,6 +3302,9 @@ function getProductImages(media) {
|
|
|
2605
3302
|
function getProductVideos(media) {
|
|
2606
3303
|
return getProductMediaByKind(media, "video");
|
|
2607
3304
|
}
|
|
3305
|
+
function getProductDetailVariants(product) {
|
|
3306
|
+
return product.detailVariants ?? product.variants;
|
|
3307
|
+
}
|
|
2608
3308
|
function getPrimaryProductImage(media) {
|
|
2609
3309
|
return getProductImages(media)[0] ?? null;
|
|
2610
3310
|
}
|
|
@@ -2762,6 +3462,9 @@ function mapRelatedProduct(raw, storageUrl) {
|
|
|
2762
3462
|
};
|
|
2763
3463
|
}
|
|
2764
3464
|
function mapProduct(raw, storageUrl, labels, chainId) {
|
|
3465
|
+
const detailVariants = raw.product_variants.map((variant) => mapVariant(variant, labels, chainId)).filter((variant) => variant.pricing.currentPrice != null);
|
|
3466
|
+
const listingVariantIds = raw.listing_variant_ids ? new Set(raw.listing_variant_ids) : null;
|
|
3467
|
+
const variants = listingVariantIds ? detailVariants.filter((variant) => listingVariantIds.has(variant.id)) : detailVariants;
|
|
2765
3468
|
return {
|
|
2766
3469
|
id: raw.id,
|
|
2767
3470
|
name: raw.name,
|
|
@@ -2778,9 +3481,8 @@ function mapProduct(raw, storageUrl, labels, chainId) {
|
|
|
2778
3481
|
measurementUnit: raw.measurement_unit,
|
|
2779
3482
|
createdAt: raw.created_at,
|
|
2780
3483
|
updatedAt: raw.updated_at ?? raw.created_at,
|
|
2781
|
-
variants
|
|
2782
|
-
|
|
2783
|
-
),
|
|
3484
|
+
variants,
|
|
3485
|
+
detailVariants,
|
|
2784
3486
|
media: raw.product_media.map((m) => mapMedia(m, storageUrl)),
|
|
2785
3487
|
attributes: raw.product_attributes.map(
|
|
2786
3488
|
(attribute) => mapProductAttribute(attribute, labels)
|
|
@@ -3223,15 +3925,171 @@ function createCatalogModule(graphqlUrl, getHeaders, getContext, storageUrl) {
|
|
|
3223
3925
|
const publicAttributeLabels = buildPublicAttributeLabels(
|
|
3224
3926
|
data.catalog_attribute_lists
|
|
3225
3927
|
);
|
|
3928
|
+
const mappedProduct = mapProduct(
|
|
3929
|
+
product,
|
|
3930
|
+
storageUrl,
|
|
3931
|
+
publicAttributeLabels,
|
|
3932
|
+
ctx.chainId
|
|
3933
|
+
);
|
|
3226
3934
|
const filteredProducts = await filterProductsForWebsiteStock(
|
|
3227
3935
|
graphqlUrl,
|
|
3228
3936
|
getHeaders,
|
|
3229
|
-
[
|
|
3937
|
+
[mappedProduct],
|
|
3230
3938
|
ctx.chainId
|
|
3231
3939
|
);
|
|
3232
|
-
|
|
3940
|
+
if (!filteredProducts[0]) return null;
|
|
3941
|
+
return {
|
|
3942
|
+
...mappedProduct,
|
|
3943
|
+
variants: getProductDetailVariants(mappedProduct)
|
|
3944
|
+
};
|
|
3945
|
+
};
|
|
3946
|
+
const loadCatalogPage = async (opts, retryRateLimits = false) => {
|
|
3947
|
+
const ctx = getContext();
|
|
3948
|
+
if (!ctx.chainId) {
|
|
3949
|
+
throw new WebsiteError(
|
|
3950
|
+
"This website has no chain configured. Catalog requires a chain_id.",
|
|
3951
|
+
"NO_CHAIN"
|
|
3952
|
+
);
|
|
3953
|
+
}
|
|
3954
|
+
const chainId = ctx.chainId;
|
|
3955
|
+
const filters = {
|
|
3956
|
+
category_slug: opts.categorySlug,
|
|
3957
|
+
subcategory_slug: opts.subcategorySlug,
|
|
3958
|
+
tags: opts.tags,
|
|
3959
|
+
search: opts.search,
|
|
3960
|
+
brand_id: opts.brandId,
|
|
3961
|
+
min_price: opts.minPrice,
|
|
3962
|
+
max_price: opts.maxPrice,
|
|
3963
|
+
attributes_by_slug: opts.attributesBySlug,
|
|
3964
|
+
sort_by: opts.sortBy,
|
|
3965
|
+
limit: opts.limit,
|
|
3966
|
+
offset: opts.offset
|
|
3967
|
+
};
|
|
3968
|
+
const data = await executeGraphQL(
|
|
3969
|
+
graphqlUrl,
|
|
3970
|
+
WEBSITE_GET_CATALOG_PAGE,
|
|
3971
|
+
{
|
|
3972
|
+
websiteId: ctx.websiteId,
|
|
3973
|
+
categoryListKey: opts.categoryListKey,
|
|
3974
|
+
attributeListKey: opts.attributeListKey,
|
|
3975
|
+
filters,
|
|
3976
|
+
include: opts.include
|
|
3977
|
+
},
|
|
3978
|
+
getHeaders,
|
|
3979
|
+
retryRateLimits ? { retry: STATIC_CATALOG_RETRY } : void 0
|
|
3980
|
+
);
|
|
3981
|
+
if (!data.result.success) {
|
|
3982
|
+
throw new WebsiteError(
|
|
3983
|
+
data.result.errors.join("; ") || "Unable to resolve catalog page",
|
|
3984
|
+
"CATALOG_PAGE_ERROR"
|
|
3985
|
+
);
|
|
3986
|
+
}
|
|
3987
|
+
const products = parseActionJson(data.result.products, []);
|
|
3988
|
+
const categoryList = parseActionJson(
|
|
3989
|
+
data.result.category_list,
|
|
3990
|
+
null
|
|
3991
|
+
);
|
|
3992
|
+
const attributeList = parseActionJson(
|
|
3993
|
+
data.result.attribute_list,
|
|
3994
|
+
null
|
|
3995
|
+
);
|
|
3996
|
+
const filtersResult = parseActionJson(
|
|
3997
|
+
data.result.filters,
|
|
3998
|
+
null
|
|
3999
|
+
);
|
|
4000
|
+
const brands = parseActionJson(data.result.brands, []);
|
|
4001
|
+
const pagination = parseActionJson(
|
|
4002
|
+
data.result.pagination,
|
|
4003
|
+
{
|
|
4004
|
+
limit: opts.limit ?? 24,
|
|
4005
|
+
offset: opts.offset ?? 0,
|
|
4006
|
+
total: 0,
|
|
4007
|
+
hasMore: false
|
|
4008
|
+
}
|
|
4009
|
+
);
|
|
4010
|
+
const appliedFilters = parseActionJson(
|
|
4011
|
+
data.result.applied_filters,
|
|
4012
|
+
{}
|
|
4013
|
+
);
|
|
4014
|
+
const debug = parseActionJson(
|
|
4015
|
+
data.result.debug,
|
|
4016
|
+
null
|
|
4017
|
+
);
|
|
4018
|
+
const labels = buildPublicAttributeLabels(
|
|
4019
|
+
attributeList ? [attributeList] : []
|
|
4020
|
+
);
|
|
4021
|
+
return {
|
|
4022
|
+
products: products.map(
|
|
4023
|
+
(product) => mapProduct(product, storageUrl, labels, chainId)
|
|
4024
|
+
),
|
|
4025
|
+
categories: categoryList ? mapCategoryList(categoryList) : null,
|
|
4026
|
+
attributes: attributeList ? mapAttributeList(attributeList) : null,
|
|
4027
|
+
filters: filtersResult,
|
|
4028
|
+
brands,
|
|
4029
|
+
pagination,
|
|
4030
|
+
appliedFilters,
|
|
4031
|
+
debug
|
|
4032
|
+
};
|
|
3233
4033
|
};
|
|
3234
4034
|
return {
|
|
4035
|
+
async getProductLiveState(productVariantIds) {
|
|
4036
|
+
const ctx = getContext();
|
|
4037
|
+
if (!ctx.chainId) {
|
|
4038
|
+
throw new WebsiteError(
|
|
4039
|
+
"This website has no chain configured. Product live state requires a chain_id.",
|
|
4040
|
+
"NO_CHAIN"
|
|
4041
|
+
);
|
|
4042
|
+
}
|
|
4043
|
+
const chainId = ctx.chainId;
|
|
4044
|
+
const variantIds = [...new Set(productVariantIds)];
|
|
4045
|
+
if (variantIds.length === 0) return [];
|
|
4046
|
+
if (variantIds.length > MAX_LIVE_STATE_VARIANT_IDS) {
|
|
4047
|
+
throw new WebsiteError(
|
|
4048
|
+
`Product live state accepts at most ${MAX_LIVE_STATE_VARIANT_IDS} variants per request.`,
|
|
4049
|
+
"PRODUCT_LIVE_STATE_LIMIT_EXCEEDED"
|
|
4050
|
+
);
|
|
4051
|
+
}
|
|
4052
|
+
const data = await executeGraphQL(
|
|
4053
|
+
graphqlUrl,
|
|
4054
|
+
GET_PRODUCT_LIVE_STATE,
|
|
4055
|
+
{
|
|
4056
|
+
variantIds,
|
|
4057
|
+
companyId: ctx.companyId,
|
|
4058
|
+
chainId,
|
|
4059
|
+
websiteId: ctx.websiteId
|
|
4060
|
+
},
|
|
4061
|
+
getHeaders
|
|
4062
|
+
);
|
|
4063
|
+
const variantsById = new Map(
|
|
4064
|
+
(data.product_variants ?? []).map((variant) => [variant.id, variant])
|
|
4065
|
+
);
|
|
4066
|
+
const availabilityById = new Map(
|
|
4067
|
+
(data.websiteProductAvailability ?? []).map((availability) => [
|
|
4068
|
+
availability.product_variant_id,
|
|
4069
|
+
availability
|
|
4070
|
+
])
|
|
4071
|
+
);
|
|
4072
|
+
return variantIds.flatMap((variantId) => {
|
|
4073
|
+
const variant = variantsById.get(variantId);
|
|
4074
|
+
if (!variant) return [];
|
|
4075
|
+
const availability = availabilityById.get(variantId);
|
|
4076
|
+
const pricing = resolveProductPricing(
|
|
4077
|
+
selectPublicPrices(
|
|
4078
|
+
variant.product_variant_prices.map(mapPrice),
|
|
4079
|
+
chainId
|
|
4080
|
+
)
|
|
4081
|
+
);
|
|
4082
|
+
return [
|
|
4083
|
+
{
|
|
4084
|
+
productVariantId: variantId,
|
|
4085
|
+
inStock: availability?.in_stock ?? false,
|
|
4086
|
+
availabilityLabel: availability?.label ?? "Agotado",
|
|
4087
|
+
maxPurchasableQuantity: availability?.max_purchasable_quantity ?? 0,
|
|
4088
|
+
pricing
|
|
4089
|
+
}
|
|
4090
|
+
];
|
|
4091
|
+
});
|
|
4092
|
+
},
|
|
3235
4093
|
async getProductRecommendations(opts) {
|
|
3236
4094
|
const ctx = getContext();
|
|
3237
4095
|
const limit = opts.limit ?? 16;
|
|
@@ -3306,86 +4164,36 @@ function createCatalogModule(graphqlUrl, getHeaders, getContext, storageUrl) {
|
|
|
3306
4164
|
}
|
|
3307
4165
|
};
|
|
3308
4166
|
},
|
|
3309
|
-
|
|
4167
|
+
getCatalogPage: loadCatalogPage,
|
|
4168
|
+
async getStaticCatalogSnapshot(opts) {
|
|
3310
4169
|
const ctx = getContext();
|
|
3311
|
-
|
|
3312
|
-
|
|
3313
|
-
|
|
3314
|
-
|
|
3315
|
-
|
|
3316
|
-
|
|
3317
|
-
|
|
3318
|
-
const
|
|
3319
|
-
|
|
3320
|
-
|
|
3321
|
-
|
|
3322
|
-
|
|
3323
|
-
|
|
3324
|
-
|
|
3325
|
-
max_price: opts.maxPrice,
|
|
3326
|
-
attributes_by_slug: opts.attributesBySlug,
|
|
3327
|
-
sort_by: opts.sortBy,
|
|
3328
|
-
limit: opts.limit,
|
|
3329
|
-
offset: opts.offset
|
|
3330
|
-
};
|
|
3331
|
-
const data = await executeGraphQL(
|
|
3332
|
-
graphqlUrl,
|
|
3333
|
-
WEBSITE_GET_CATALOG_PAGE,
|
|
3334
|
-
{
|
|
3335
|
-
websiteId: ctx.websiteId,
|
|
3336
|
-
categoryListKey: opts.categoryListKey,
|
|
3337
|
-
attributeListKey: opts.attributeListKey,
|
|
3338
|
-
filters,
|
|
3339
|
-
include: opts.include
|
|
4170
|
+
return (await collectStaticCatalog({
|
|
4171
|
+
websiteId: ctx.websiteId,
|
|
4172
|
+
snapshot: opts,
|
|
4173
|
+
loadPage: (options) => loadCatalogPage(options, true)
|
|
4174
|
+
})).snapshot;
|
|
4175
|
+
},
|
|
4176
|
+
async getStaticCatalogBootstrap(opts) {
|
|
4177
|
+
const ctx = getContext();
|
|
4178
|
+
const { snapshot, initialPage } = await collectStaticCatalog({
|
|
4179
|
+
websiteId: ctx.websiteId,
|
|
4180
|
+
snapshot: opts,
|
|
4181
|
+
bootstrap: {
|
|
4182
|
+
initialPageSize: opts.initialPageSize,
|
|
4183
|
+
sortBy: opts.sortBy
|
|
3340
4184
|
},
|
|
3341
|
-
|
|
3342
|
-
);
|
|
3343
|
-
if (!
|
|
4185
|
+
loadPage: (options) => loadCatalogPage(options, true)
|
|
4186
|
+
});
|
|
4187
|
+
if (!initialPage) {
|
|
3344
4188
|
throw new WebsiteError(
|
|
3345
|
-
|
|
3346
|
-
"
|
|
4189
|
+
"Static catalog bootstrap could not resolve its initial page.",
|
|
4190
|
+
"STATIC_CATALOG_BOOTSTRAP_MISSING_INITIAL_PAGE"
|
|
3347
4191
|
);
|
|
3348
4192
|
}
|
|
3349
|
-
const products = parseActionJson(data.result.products, []);
|
|
3350
|
-
const categoryList = parseActionJson(
|
|
3351
|
-
data.result.category_list,
|
|
3352
|
-
null
|
|
3353
|
-
);
|
|
3354
|
-
const attributeList = parseActionJson(
|
|
3355
|
-
data.result.attribute_list,
|
|
3356
|
-
null
|
|
3357
|
-
);
|
|
3358
|
-
const filtersResult = parseActionJson(
|
|
3359
|
-
data.result.filters,
|
|
3360
|
-
null
|
|
3361
|
-
);
|
|
3362
|
-
const brands = parseActionJson(data.result.brands, []);
|
|
3363
|
-
const pagination = parseActionJson(
|
|
3364
|
-
data.result.pagination,
|
|
3365
|
-
{ limit: opts.limit ?? 24, offset: opts.offset ?? 0, total: 0, hasMore: false }
|
|
3366
|
-
);
|
|
3367
|
-
const appliedFilters = parseActionJson(
|
|
3368
|
-
data.result.applied_filters,
|
|
3369
|
-
{}
|
|
3370
|
-
);
|
|
3371
|
-
const debug = parseActionJson(
|
|
3372
|
-
data.result.debug,
|
|
3373
|
-
null
|
|
3374
|
-
);
|
|
3375
|
-
const labels = buildPublicAttributeLabels(
|
|
3376
|
-
attributeList ? [attributeList] : []
|
|
3377
|
-
);
|
|
3378
4193
|
return {
|
|
3379
|
-
|
|
3380
|
-
|
|
3381
|
-
|
|
3382
|
-
categories: categoryList ? mapCategoryList(categoryList) : null,
|
|
3383
|
-
attributes: attributeList ? mapAttributeList(attributeList) : null,
|
|
3384
|
-
filters: filtersResult,
|
|
3385
|
-
brands,
|
|
3386
|
-
pagination,
|
|
3387
|
-
appliedFilters,
|
|
3388
|
-
debug
|
|
4194
|
+
...snapshot,
|
|
4195
|
+
bootstrapSchemaVersion: 1,
|
|
4196
|
+
initialPage
|
|
3389
4197
|
};
|
|
3390
4198
|
},
|
|
3391
4199
|
async getProducts(opts) {
|
|
@@ -3852,6 +4660,7 @@ function mapCouponResult(raw) {
|
|
|
3852
4660
|
};
|
|
3853
4661
|
}
|
|
3854
4662
|
function createCheckoutModule(graphqlUrl, getHeaders, getContext, cartSessionStorage) {
|
|
4663
|
+
let shippingPolicyCache = null;
|
|
3855
4664
|
return {
|
|
3856
4665
|
async getLocationOptions() {
|
|
3857
4666
|
const context = getContext();
|
|
@@ -3949,22 +4758,39 @@ function createCheckoutModule(graphqlUrl, getHeaders, getContext, cartSessionSto
|
|
|
3949
4758
|
}));
|
|
3950
4759
|
},
|
|
3951
4760
|
async getShippingPolicy() {
|
|
4761
|
+
const now = Date.now();
|
|
4762
|
+
if (shippingPolicyCache && shippingPolicyCache.expiresAt > now) {
|
|
4763
|
+
return shippingPolicyCache.request;
|
|
4764
|
+
}
|
|
3952
4765
|
const context = getContext();
|
|
3953
|
-
const
|
|
4766
|
+
const request = executeGraphQL(
|
|
3954
4767
|
graphqlUrl,
|
|
3955
4768
|
GET_SHIPPING_POLICY,
|
|
3956
4769
|
{ websiteId: context.websiteId },
|
|
3957
4770
|
getHeaders
|
|
3958
|
-
)
|
|
3959
|
-
|
|
3960
|
-
|
|
3961
|
-
|
|
3962
|
-
|
|
3963
|
-
|
|
3964
|
-
|
|
3965
|
-
|
|
3966
|
-
|
|
4771
|
+
).then((data) => {
|
|
4772
|
+
const result = data.websiteGetShippingPolicy;
|
|
4773
|
+
if (!result.success || !result.policy) {
|
|
4774
|
+
throw new CheckoutError(result.errors ?? ["SHIPPING_POLICY_FAILED"]);
|
|
4775
|
+
}
|
|
4776
|
+
return {
|
|
4777
|
+
freeShippingEnabled: result.policy.free_shipping_enabled,
|
|
4778
|
+
freeShippingThreshold: result.policy.free_shipping_threshold,
|
|
4779
|
+
currency: result.policy.currency
|
|
4780
|
+
};
|
|
4781
|
+
});
|
|
4782
|
+
shippingPolicyCache = {
|
|
4783
|
+
expiresAt: now + 5 * 60 * 1e3,
|
|
4784
|
+
request
|
|
3967
4785
|
};
|
|
4786
|
+
try {
|
|
4787
|
+
return await request;
|
|
4788
|
+
} catch (error) {
|
|
4789
|
+
if (shippingPolicyCache?.request === request) {
|
|
4790
|
+
shippingPolicyCache = null;
|
|
4791
|
+
}
|
|
4792
|
+
throw error;
|
|
4793
|
+
}
|
|
3968
4794
|
},
|
|
3969
4795
|
async getFreeShippingProgress(input) {
|
|
3970
4796
|
if (input.items.length === 0) {
|
|
@@ -4130,6 +4956,13 @@ function createCheckoutModule(graphqlUrl, getHeaders, getContext, cartSessionSto
|
|
|
4130
4956
|
initialPayment: input.initialPayment ? {
|
|
4131
4957
|
amount: input.initialPayment.amount,
|
|
4132
4958
|
payment_method_id: input.initialPayment.paymentMethodId
|
|
4959
|
+
} : void 0,
|
|
4960
|
+
marketingAttribution: input.marketingAttribution ? serializeWebsiteMarketingAttribution(input.marketingAttribution) : void 0,
|
|
4961
|
+
advertisingConsent: input.advertisingConsent ? {
|
|
4962
|
+
policy_id: input.advertisingConsent.policyId,
|
|
4963
|
+
policy_version: input.advertisingConsent.policyVersion,
|
|
4964
|
+
statement_hash: input.advertisingConsent.statementHash,
|
|
4965
|
+
granted: input.advertisingConsent.granted
|
|
4133
4966
|
} : void 0
|
|
4134
4967
|
},
|
|
4135
4968
|
getHeaders
|
|
@@ -4265,18 +5098,23 @@ function readOptionalText(value) {
|
|
|
4265
5098
|
function enrichMediaValue(value, storageUrl) {
|
|
4266
5099
|
const fileId = typeof value.fileId === "string" && value.fileId.trim() ? value.fileId.trim() : "";
|
|
4267
5100
|
if (!fileId) return value;
|
|
4268
|
-
const
|
|
5101
|
+
const isProductMedia = typeof value.id === "string" && typeof value.order === "number";
|
|
5102
|
+
const isEditorialImage = value.mediaKind === "image" && !isProductMedia;
|
|
5103
|
+
const originalWidth = typeof value.originalWidth === "number" ? value.originalWidth : null;
|
|
5104
|
+
const urls = isEditorialImage ? buildStorageContentMediaUrls(storageUrl, fileId, originalWidth) : buildStorageMediaUrls(storageUrl, fileId);
|
|
4269
5105
|
return {
|
|
4270
5106
|
...value,
|
|
4271
5107
|
...urls,
|
|
4272
|
-
url: value.url ?? urls.originalUrl
|
|
5108
|
+
url: value.url ?? urls.originalUrl,
|
|
5109
|
+
src: value.src ?? value.url ?? urls.originalUrl
|
|
4273
5110
|
};
|
|
4274
5111
|
}
|
|
4275
5112
|
function normalizeRichTextNode(value, storageUrl) {
|
|
4276
5113
|
const type = value.type;
|
|
4277
5114
|
const attrs = isRecord(value.attrs) ? value.attrs : null;
|
|
4278
5115
|
if (attrs && (type === "bacanoImage" || type === "bacanoVideo") && typeof attrs.fileId === "string") {
|
|
4279
|
-
const
|
|
5116
|
+
const originalWidth = typeof attrs.originalWidth === "number" ? attrs.originalWidth : null;
|
|
5117
|
+
const urls = type === "bacanoImage" ? buildStorageContentMediaUrls(storageUrl, attrs.fileId, originalWidth) : buildStorageMediaUrls(storageUrl, attrs.fileId);
|
|
4280
5118
|
const altText = readOptionalText(attrs.altText) ?? readOptionalText(attrs.alt);
|
|
4281
5119
|
const caption = readOptionalText(attrs.caption);
|
|
4282
5120
|
const showCaption = Boolean(caption) && attrs.showCaption !== false;
|
|
@@ -4408,6 +5246,452 @@ function createContentModule(graphqlUrl, getHeaders, getContext, storageUrl) {
|
|
|
4408
5246
|
};
|
|
4409
5247
|
}
|
|
4410
5248
|
|
|
5249
|
+
// libs/sdk/src/marketing/clarity.ts
|
|
5250
|
+
var initializedDestinations = /* @__PURE__ */ new Set();
|
|
5251
|
+
function clarityWindow() {
|
|
5252
|
+
return typeof window === "undefined" ? null : window;
|
|
5253
|
+
}
|
|
5254
|
+
function prepareClarity(projectId) {
|
|
5255
|
+
const target = clarityWindow();
|
|
5256
|
+
if (!target || initializedDestinations.has(projectId)) return;
|
|
5257
|
+
if (!target.clarity) {
|
|
5258
|
+
const clarity = function() {
|
|
5259
|
+
clarity.q ??= [];
|
|
5260
|
+
clarity.q.push(arguments);
|
|
5261
|
+
};
|
|
5262
|
+
target.clarity = clarity;
|
|
5263
|
+
}
|
|
5264
|
+
initializedDestinations.add(projectId);
|
|
5265
|
+
}
|
|
5266
|
+
function loadClarityScript(projectId) {
|
|
5267
|
+
if (typeof document === "undefined") return;
|
|
5268
|
+
const id = `bacano-clarity-${projectId}`;
|
|
5269
|
+
if (document.getElementById(id)) return;
|
|
5270
|
+
const script = document.createElement("script");
|
|
5271
|
+
script.id = id;
|
|
5272
|
+
script.async = true;
|
|
5273
|
+
script.src = `https://www.clarity.ms/tag/${encodeURIComponent(projectId)}`;
|
|
5274
|
+
script.referrerPolicy = "strict-origin-when-cross-origin";
|
|
5275
|
+
document.head.appendChild(script);
|
|
5276
|
+
}
|
|
5277
|
+
function sendClarityEvent(eventName) {
|
|
5278
|
+
clarityWindow()?.clarity?.("event", eventName);
|
|
5279
|
+
}
|
|
5280
|
+
|
|
5281
|
+
// libs/sdk/src/marketing/domain.ts
|
|
5282
|
+
function matchesCanonicalMarketingHost(canonicalDomain, currentHostname) {
|
|
5283
|
+
if (canonicalDomain === void 0) return true;
|
|
5284
|
+
if (!canonicalDomain?.trim()) return false;
|
|
5285
|
+
try {
|
|
5286
|
+
const configuredHost = new URL(
|
|
5287
|
+
canonicalDomain.includes("://") ? canonicalDomain : `https://${canonicalDomain}`
|
|
5288
|
+
).hostname.toLowerCase();
|
|
5289
|
+
return currentHostname.toLowerCase() === configuredHost;
|
|
5290
|
+
} catch {
|
|
5291
|
+
return false;
|
|
5292
|
+
}
|
|
5293
|
+
}
|
|
5294
|
+
|
|
5295
|
+
// libs/sdk/src/marketing/ga4.ts
|
|
5296
|
+
function mapGa4Items(items) {
|
|
5297
|
+
return items?.map((item) => ({
|
|
5298
|
+
item_id: item.itemId,
|
|
5299
|
+
item_name: item.itemName,
|
|
5300
|
+
item_variant: item.itemVariant,
|
|
5301
|
+
item_brand: item.itemBrand,
|
|
5302
|
+
item_category: item.itemCategory,
|
|
5303
|
+
price: item.price,
|
|
5304
|
+
quantity: item.quantity,
|
|
5305
|
+
index: item.index,
|
|
5306
|
+
sku: item.sku
|
|
5307
|
+
}));
|
|
5308
|
+
}
|
|
5309
|
+
|
|
5310
|
+
// libs/sdk/src/marketing/types.ts
|
|
5311
|
+
var MARKETING_SCHEMA_VERSION = 1;
|
|
5312
|
+
var WEBSITE_MARKETING_CONTACT_PLACEMENTS = [
|
|
5313
|
+
"product_detail",
|
|
5314
|
+
"floating",
|
|
5315
|
+
"header",
|
|
5316
|
+
"footer",
|
|
5317
|
+
"contact_page"
|
|
5318
|
+
];
|
|
5319
|
+
|
|
5320
|
+
// libs/sdk/src/marketing/runtime.ts
|
|
5321
|
+
var initializedGa4Destinations = /* @__PURE__ */ new Set();
|
|
5322
|
+
var initializedMetaDestinations = /* @__PURE__ */ new Set();
|
|
5323
|
+
function marketingWindow() {
|
|
5324
|
+
return typeof window === "undefined" ? null : window;
|
|
5325
|
+
}
|
|
5326
|
+
function loadScriptOnce(id, source) {
|
|
5327
|
+
if (typeof document === "undefined" || document.getElementById(id)) return;
|
|
5328
|
+
const script = document.createElement("script");
|
|
5329
|
+
script.id = id;
|
|
5330
|
+
script.async = true;
|
|
5331
|
+
script.src = source;
|
|
5332
|
+
script.referrerPolicy = "strict-origin-when-cross-origin";
|
|
5333
|
+
document.head.appendChild(script);
|
|
5334
|
+
}
|
|
5335
|
+
function prepareGa4(measurementId) {
|
|
5336
|
+
const target = marketingWindow();
|
|
5337
|
+
if (!target) return;
|
|
5338
|
+
if (initializedGa4Destinations.has(measurementId)) return;
|
|
5339
|
+
target.dataLayer ??= [];
|
|
5340
|
+
target.gtag ??= function() {
|
|
5341
|
+
target.dataLayer?.push(arguments);
|
|
5342
|
+
};
|
|
5343
|
+
target.gtag("js", /* @__PURE__ */ new Date());
|
|
5344
|
+
target.gtag("config", measurementId, { send_page_view: false });
|
|
5345
|
+
initializedGa4Destinations.add(measurementId);
|
|
5346
|
+
}
|
|
5347
|
+
function loadGa4Script(measurementId) {
|
|
5348
|
+
loadScriptOnce(
|
|
5349
|
+
`bacano-ga4-${measurementId}`,
|
|
5350
|
+
`https://www.googletagmanager.com/gtag/js?id=${encodeURIComponent(measurementId)}`
|
|
5351
|
+
);
|
|
5352
|
+
}
|
|
5353
|
+
function captureGa4ClientId(measurementId, websiteId, diagnostic) {
|
|
5354
|
+
const target = marketingWindow();
|
|
5355
|
+
target?.gtag?.("get", measurementId, "client_id", (clientId) => {
|
|
5356
|
+
if (storeWebsiteMarketingGa4ClientId(websiteId, clientId)) return;
|
|
5357
|
+
diagnostic?.({
|
|
5358
|
+
level: "warning",
|
|
5359
|
+
code: "GA4_CLIENT_ID_UNAVAILABLE",
|
|
5360
|
+
message: "GA4 did not provide a valid browser client identifier"
|
|
5361
|
+
});
|
|
5362
|
+
});
|
|
5363
|
+
}
|
|
5364
|
+
function captureGa4SessionId(measurementId, websiteId, diagnostic) {
|
|
5365
|
+
const target = marketingWindow();
|
|
5366
|
+
target?.gtag?.("get", measurementId, "session_id", (sessionId) => {
|
|
5367
|
+
if (storeWebsiteMarketingGa4SessionId(websiteId, sessionId)) return;
|
|
5368
|
+
diagnostic?.({
|
|
5369
|
+
level: "warning",
|
|
5370
|
+
code: "GA4_SESSION_ID_UNAVAILABLE",
|
|
5371
|
+
message: "GA4 did not provide a valid browser session identifier"
|
|
5372
|
+
});
|
|
5373
|
+
});
|
|
5374
|
+
}
|
|
5375
|
+
function prepareMeta(pixelId) {
|
|
5376
|
+
const target = marketingWindow();
|
|
5377
|
+
if (!target) return;
|
|
5378
|
+
if (initializedMetaDestinations.has(pixelId)) return;
|
|
5379
|
+
if (!target.fbq) {
|
|
5380
|
+
const queue = [];
|
|
5381
|
+
const fbq = ((...args) => {
|
|
5382
|
+
if (fbq.callMethod) {
|
|
5383
|
+
fbq.callMethod(...args);
|
|
5384
|
+
return;
|
|
5385
|
+
}
|
|
5386
|
+
queue.push(args);
|
|
5387
|
+
});
|
|
5388
|
+
fbq.push = fbq;
|
|
5389
|
+
fbq.loaded = true;
|
|
5390
|
+
fbq.version = "2.0";
|
|
5391
|
+
fbq.queue = queue;
|
|
5392
|
+
target.fbq = fbq;
|
|
5393
|
+
target._fbq = fbq;
|
|
5394
|
+
}
|
|
5395
|
+
target.fbq("init", pixelId);
|
|
5396
|
+
initializedMetaDestinations.add(pixelId);
|
|
5397
|
+
}
|
|
5398
|
+
function loadMetaScript() {
|
|
5399
|
+
loadScriptOnce(
|
|
5400
|
+
"bacano-meta-pixel",
|
|
5401
|
+
"https://connect.facebook.net/en_US/fbevents.js"
|
|
5402
|
+
);
|
|
5403
|
+
}
|
|
5404
|
+
function scheduleScriptLoad(load, strategy) {
|
|
5405
|
+
const target = marketingWindow();
|
|
5406
|
+
if (!target || strategy !== "idle") {
|
|
5407
|
+
load();
|
|
5408
|
+
return () => void 0;
|
|
5409
|
+
}
|
|
5410
|
+
let completed = false;
|
|
5411
|
+
let idleHandle = null;
|
|
5412
|
+
let timeoutHandle = null;
|
|
5413
|
+
const interactionEvents = ["pointerdown", "keydown", "touchstart"];
|
|
5414
|
+
const cleanup = () => {
|
|
5415
|
+
for (const eventName of interactionEvents) {
|
|
5416
|
+
target.removeEventListener(eventName, finish, true);
|
|
5417
|
+
}
|
|
5418
|
+
if (idleHandle !== null) target.cancelIdleCallback?.(idleHandle);
|
|
5419
|
+
if (timeoutHandle !== null) target.clearTimeout(timeoutHandle);
|
|
5420
|
+
};
|
|
5421
|
+
const finish = () => {
|
|
5422
|
+
if (completed) return;
|
|
5423
|
+
completed = true;
|
|
5424
|
+
cleanup();
|
|
5425
|
+
load();
|
|
5426
|
+
};
|
|
5427
|
+
for (const eventName of interactionEvents) {
|
|
5428
|
+
target.addEventListener(eventName, finish, {
|
|
5429
|
+
capture: true,
|
|
5430
|
+
once: true,
|
|
5431
|
+
passive: true
|
|
5432
|
+
});
|
|
5433
|
+
}
|
|
5434
|
+
if (target.requestIdleCallback) {
|
|
5435
|
+
idleHandle = target.requestIdleCallback(finish, { timeout: 2e3 });
|
|
5436
|
+
} else {
|
|
5437
|
+
timeoutHandle = target.setTimeout(finish, 1500);
|
|
5438
|
+
}
|
|
5439
|
+
return cleanup;
|
|
5440
|
+
}
|
|
5441
|
+
function metaEventName(eventName) {
|
|
5442
|
+
const standard = {
|
|
5443
|
+
page_view: "PageView",
|
|
5444
|
+
search: "Search",
|
|
5445
|
+
view_item: "ViewContent",
|
|
5446
|
+
add_to_cart: "AddToCart",
|
|
5447
|
+
begin_checkout: "InitiateCheckout",
|
|
5448
|
+
add_payment_info: "AddPaymentInfo",
|
|
5449
|
+
contact: "Contact"
|
|
5450
|
+
};
|
|
5451
|
+
return standard[eventName] ?? eventName;
|
|
5452
|
+
}
|
|
5453
|
+
function isWebsiteMarketingContactPlacement(value) {
|
|
5454
|
+
return WEBSITE_MARKETING_CONTACT_PLACEMENTS.some(
|
|
5455
|
+
(candidate) => candidate === value
|
|
5456
|
+
);
|
|
5457
|
+
}
|
|
5458
|
+
function contactPlacementFor(event) {
|
|
5459
|
+
const placement = event.contactPlacement;
|
|
5460
|
+
if (event.eventName !== "contact" || !isWebsiteMarketingContactPlacement(placement)) {
|
|
5461
|
+
return void 0;
|
|
5462
|
+
}
|
|
5463
|
+
return placement;
|
|
5464
|
+
}
|
|
5465
|
+
function sendGa4(event, diagnosticMode) {
|
|
5466
|
+
const target = marketingWindow();
|
|
5467
|
+
const contactPlacement = contactPlacementFor(event);
|
|
5468
|
+
target?.gtag?.("event", event.eventName, {
|
|
5469
|
+
currency: event.currency,
|
|
5470
|
+
value: event.value,
|
|
5471
|
+
search_term: event.searchTerm,
|
|
5472
|
+
item_list_id: event.itemListId,
|
|
5473
|
+
item_list_name: event.itemListName,
|
|
5474
|
+
shipping_tier: event.shippingTier,
|
|
5475
|
+
payment_type: event.paymentType,
|
|
5476
|
+
method: event.contactMethod,
|
|
5477
|
+
...contactPlacement ? { contact_placement: contactPlacement } : void 0,
|
|
5478
|
+
page_location: event.pageLocation,
|
|
5479
|
+
page_title: event.pageTitle,
|
|
5480
|
+
items: mapGa4Items(event.items),
|
|
5481
|
+
event_id: event.eventId,
|
|
5482
|
+
debug_mode: diagnosticMode ? true : void 0
|
|
5483
|
+
});
|
|
5484
|
+
}
|
|
5485
|
+
function sendMeta(event) {
|
|
5486
|
+
const target = marketingWindow();
|
|
5487
|
+
const eventName = metaEventName(event.eventName);
|
|
5488
|
+
const isStandard = eventName !== event.eventName;
|
|
5489
|
+
const contactPlacement = contactPlacementFor(event);
|
|
5490
|
+
target?.fbq?.(
|
|
5491
|
+
isStandard ? "track" : "trackCustom",
|
|
5492
|
+
eventName,
|
|
5493
|
+
{
|
|
5494
|
+
currency: event.currency,
|
|
5495
|
+
value: event.value,
|
|
5496
|
+
search_string: event.searchTerm,
|
|
5497
|
+
content_ids: event.items?.map((item) => item.itemId),
|
|
5498
|
+
contents: event.items?.map((item) => ({
|
|
5499
|
+
id: item.itemId,
|
|
5500
|
+
quantity: item.quantity ?? 1,
|
|
5501
|
+
item_price: item.price
|
|
5502
|
+
})),
|
|
5503
|
+
content_type: event.items?.length ? "product" : void 0,
|
|
5504
|
+
contact_method: event.contactMethod,
|
|
5505
|
+
...contactPlacement ? { contact_placement: contactPlacement } : void 0
|
|
5506
|
+
},
|
|
5507
|
+
{ eventID: event.eventId }
|
|
5508
|
+
);
|
|
5509
|
+
}
|
|
5510
|
+
function newEventId() {
|
|
5511
|
+
return typeof crypto !== "undefined" && "randomUUID" in crypto ? crypto.randomUUID() : `evt_${Date.now()}_${Math.random().toString(16).slice(2)}`;
|
|
5512
|
+
}
|
|
5513
|
+
function createWebsiteMarketingRuntime(options) {
|
|
5514
|
+
let destroyed = false;
|
|
5515
|
+
let cancelScheduledLoad = () => void 0;
|
|
5516
|
+
const target = marketingWindow();
|
|
5517
|
+
const enabled = options.config.enabled && Boolean(
|
|
5518
|
+
target && matchesCanonicalMarketingHost(
|
|
5519
|
+
options.canonicalDomain,
|
|
5520
|
+
target.location.hostname
|
|
5521
|
+
)
|
|
5522
|
+
) && Boolean(
|
|
5523
|
+
options.config.destinations.ga4 || options.config.destinations.meta || options.config.destinations.clarity
|
|
5524
|
+
);
|
|
5525
|
+
if (enabled) {
|
|
5526
|
+
try {
|
|
5527
|
+
if (options.config.destinations.ga4) {
|
|
5528
|
+
prepareGa4(options.config.destinations.ga4.measurementId);
|
|
5529
|
+
}
|
|
5530
|
+
if (options.config.destinations.meta) {
|
|
5531
|
+
prepareMeta(options.config.destinations.meta.pixelId);
|
|
5532
|
+
}
|
|
5533
|
+
if (options.config.destinations.clarity) {
|
|
5534
|
+
prepareClarity(options.config.destinations.clarity.projectId);
|
|
5535
|
+
}
|
|
5536
|
+
cancelScheduledLoad = scheduleScriptLoad(() => {
|
|
5537
|
+
if (destroyed) return;
|
|
5538
|
+
if (options.config.destinations.ga4) {
|
|
5539
|
+
loadGa4Script(options.config.destinations.ga4.measurementId);
|
|
5540
|
+
}
|
|
5541
|
+
if (options.config.destinations.meta) loadMetaScript();
|
|
5542
|
+
if (options.config.destinations.clarity) {
|
|
5543
|
+
loadClarityScript(options.config.destinations.clarity.projectId);
|
|
5544
|
+
}
|
|
5545
|
+
}, options.scriptLoadStrategy);
|
|
5546
|
+
captureWebsiteMarketingAttribution(
|
|
5547
|
+
options.websiteId,
|
|
5548
|
+
options.config.attributionWindowDays
|
|
5549
|
+
);
|
|
5550
|
+
if (options.config.destinations.ga4) {
|
|
5551
|
+
captureGa4ClientId(
|
|
5552
|
+
options.config.destinations.ga4.measurementId,
|
|
5553
|
+
options.websiteId,
|
|
5554
|
+
options.diagnostic
|
|
5555
|
+
);
|
|
5556
|
+
captureGa4SessionId(
|
|
5557
|
+
options.config.destinations.ga4.measurementId,
|
|
5558
|
+
options.websiteId,
|
|
5559
|
+
options.diagnostic
|
|
5560
|
+
);
|
|
5561
|
+
}
|
|
5562
|
+
if (options.config.destinations.meta) {
|
|
5563
|
+
refreshWebsiteMarketingMetaContext(options.websiteId);
|
|
5564
|
+
}
|
|
5565
|
+
} catch (error) {
|
|
5566
|
+
options.diagnostic?.({
|
|
5567
|
+
level: "warning",
|
|
5568
|
+
code: "RUNTIME_INITIALIZATION_FAILED",
|
|
5569
|
+
message: error instanceof Error ? error.message : "Initialization failed"
|
|
5570
|
+
});
|
|
5571
|
+
}
|
|
5572
|
+
}
|
|
5573
|
+
return {
|
|
5574
|
+
enabled,
|
|
5575
|
+
track(input) {
|
|
5576
|
+
if (!enabled || destroyed) return;
|
|
5577
|
+
const event = {
|
|
5578
|
+
...input,
|
|
5579
|
+
schemaVersion: 1,
|
|
5580
|
+
eventId: input.eventId ?? newEventId(),
|
|
5581
|
+
occurredAt: input.occurredAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
5582
|
+
currency: input.currency ?? options.config.currencyCode,
|
|
5583
|
+
pageLocation: sanitizeMarketingUrl(
|
|
5584
|
+
input.pageLocation ?? (typeof window === "undefined" ? "" : window.location.href)
|
|
5585
|
+
)
|
|
5586
|
+
};
|
|
5587
|
+
try {
|
|
5588
|
+
if (options.config.destinations.ga4) {
|
|
5589
|
+
sendGa4(event, options.config.destinations.ga4.diagnosticMode);
|
|
5590
|
+
}
|
|
5591
|
+
if (options.config.destinations.meta) sendMeta(event);
|
|
5592
|
+
if (options.config.destinations.clarity) {
|
|
5593
|
+
sendClarityEvent(event.eventName);
|
|
5594
|
+
}
|
|
5595
|
+
options.diagnostic?.({
|
|
5596
|
+
level: "debug",
|
|
5597
|
+
code: "EVENT_DISPATCHED",
|
|
5598
|
+
message: "Marketing event dispatched",
|
|
5599
|
+
eventName: event.eventName
|
|
5600
|
+
});
|
|
5601
|
+
} catch (error) {
|
|
5602
|
+
options.diagnostic?.({
|
|
5603
|
+
level: "warning",
|
|
5604
|
+
code: "EVENT_DISPATCH_FAILED",
|
|
5605
|
+
message: error instanceof Error ? error.message : "Dispatch failed",
|
|
5606
|
+
eventName: event.eventName
|
|
5607
|
+
});
|
|
5608
|
+
}
|
|
5609
|
+
},
|
|
5610
|
+
captureAttribution() {
|
|
5611
|
+
if (!enabled) return null;
|
|
5612
|
+
captureWebsiteMarketingAttribution(
|
|
5613
|
+
options.websiteId,
|
|
5614
|
+
options.config.attributionWindowDays
|
|
5615
|
+
);
|
|
5616
|
+
if (options.config.destinations.meta) {
|
|
5617
|
+
refreshWebsiteMarketingMetaContext(options.websiteId);
|
|
5618
|
+
}
|
|
5619
|
+
return getWebsiteMarketingAttribution(options.websiteId);
|
|
5620
|
+
},
|
|
5621
|
+
getAttribution() {
|
|
5622
|
+
if (!enabled) return null;
|
|
5623
|
+
if (options.config.destinations.meta) {
|
|
5624
|
+
refreshWebsiteMarketingMetaContext(options.websiteId);
|
|
5625
|
+
}
|
|
5626
|
+
return getWebsiteMarketingAttribution(options.websiteId);
|
|
5627
|
+
},
|
|
5628
|
+
destroy() {
|
|
5629
|
+
destroyed = true;
|
|
5630
|
+
cancelScheduledLoad();
|
|
5631
|
+
}
|
|
5632
|
+
};
|
|
5633
|
+
}
|
|
5634
|
+
|
|
5635
|
+
// libs/sdk/src/modules/marketing.ts
|
|
5636
|
+
function readRetryAfter(response) {
|
|
5637
|
+
const value = Number(response.headers.get("Retry-After") ?? "1");
|
|
5638
|
+
return Number.isFinite(value) && value >= 0 ? Math.ceil(value) : 1;
|
|
5639
|
+
}
|
|
5640
|
+
function createMarketingModule(apiUrl, websiteSlug, getContext) {
|
|
5641
|
+
return {
|
|
5642
|
+
async captureLead(input) {
|
|
5643
|
+
const context = getContext();
|
|
5644
|
+
const attribution = getWebsiteMarketingAttribution(context.websiteId);
|
|
5645
|
+
let response;
|
|
5646
|
+
try {
|
|
5647
|
+
response = await fetch(
|
|
5648
|
+
`${apiUrl.replace(/\/+$/, "")}/api/v1/website/campaign-captures`,
|
|
5649
|
+
{
|
|
5650
|
+
method: "POST",
|
|
5651
|
+
headers: { "Content-Type": "application/json" },
|
|
5652
|
+
body: JSON.stringify({
|
|
5653
|
+
websiteSlug,
|
|
5654
|
+
campaignKey: input.campaignKey,
|
|
5655
|
+
idempotencyKey: input.idempotencyKey,
|
|
5656
|
+
source: input.source ?? "WEBSITE",
|
|
5657
|
+
commercialConsent: input.commercialConsent,
|
|
5658
|
+
contact: input.contact,
|
|
5659
|
+
answers: input.answers ?? {},
|
|
5660
|
+
attribution: attribution ? serializeWebsiteMarketingAttribution(attribution) : null
|
|
5661
|
+
})
|
|
5662
|
+
}
|
|
5663
|
+
);
|
|
5664
|
+
} catch (error) {
|
|
5665
|
+
throw new WebsiteError(
|
|
5666
|
+
`Could not capture the campaign lead: ${error instanceof Error ? error.message : String(error)}`,
|
|
5667
|
+
"NETWORK_ERROR"
|
|
5668
|
+
);
|
|
5669
|
+
}
|
|
5670
|
+
if (response.status === 429) {
|
|
5671
|
+
throw new RateLimitError(
|
|
5672
|
+
"Rate limit exceeded. Please try again later.",
|
|
5673
|
+
readRetryAfter(response)
|
|
5674
|
+
);
|
|
5675
|
+
}
|
|
5676
|
+
if (!response.ok) {
|
|
5677
|
+
throw new WebsiteError(
|
|
5678
|
+
`Could not capture the campaign lead (status ${response.status})`,
|
|
5679
|
+
response.status === 403 ? "ORIGIN_NOT_ALLOWED" : "INVALID_RESPONSE",
|
|
5680
|
+
{ status: response.status }
|
|
5681
|
+
);
|
|
5682
|
+
}
|
|
5683
|
+
const result = await response.json();
|
|
5684
|
+
if (typeof result.accepted !== "boolean" || typeof result.message !== "string") {
|
|
5685
|
+
throw new WebsiteError(
|
|
5686
|
+
"Campaign capture returned an invalid response",
|
|
5687
|
+
"INVALID_RESPONSE"
|
|
5688
|
+
);
|
|
5689
|
+
}
|
|
5690
|
+
return { accepted: result.accepted, message: result.message };
|
|
5691
|
+
}
|
|
5692
|
+
};
|
|
5693
|
+
}
|
|
5694
|
+
|
|
4411
5695
|
// libs/sdk/src/modules/orders.ts
|
|
4412
5696
|
function mapOrderItem(raw) {
|
|
4413
5697
|
return {
|
|
@@ -4582,13 +5866,13 @@ function createOrdersModule(graphqlUrl, getHeaders, getContext) {
|
|
|
4582
5866
|
)) {
|
|
4583
5867
|
return order;
|
|
4584
5868
|
}
|
|
4585
|
-
await
|
|
5869
|
+
await wait2(intervalMs);
|
|
4586
5870
|
}
|
|
4587
5871
|
throw new CheckoutError(["PAYMENT_STATUS_TIMEOUT"]);
|
|
4588
5872
|
}
|
|
4589
5873
|
};
|
|
4590
5874
|
}
|
|
4591
|
-
function
|
|
5875
|
+
function wait2(ms) {
|
|
4592
5876
|
return new Promise((resolve) => {
|
|
4593
5877
|
setTimeout(resolve, ms);
|
|
4594
5878
|
});
|
|
@@ -4747,6 +6031,9 @@ function createProfileModule(graphqlUrl, getHeaders) {
|
|
|
4747
6031
|
}
|
|
4748
6032
|
|
|
4749
6033
|
// libs/sdk/src/modules/website.ts
|
|
6034
|
+
var WEBSITE_CONTEXT_CACHE_TTL_MS = 5 * 60 * 1e3;
|
|
6035
|
+
var WEBSITE_CONTEXT_CACHE_MAX_ENTRIES = 100;
|
|
6036
|
+
var websiteContextCache = /* @__PURE__ */ new Map();
|
|
4750
6037
|
function mapWebsite(raw) {
|
|
4751
6038
|
return {
|
|
4752
6039
|
websiteId: raw.id,
|
|
@@ -4760,10 +6047,7 @@ function mapWebsite(raw) {
|
|
|
4760
6047
|
}
|
|
4761
6048
|
async function resolveWebsite(graphqlUrl, slug, headers) {
|
|
4762
6049
|
if (!slug.trim()) {
|
|
4763
|
-
throw new WebsiteError(
|
|
4764
|
-
"websiteSlug is required",
|
|
4765
|
-
"INVALID_CONFIG"
|
|
4766
|
-
);
|
|
6050
|
+
throw new WebsiteError("websiteSlug is required", "INVALID_CONFIG");
|
|
4767
6051
|
}
|
|
4768
6052
|
const data = await executeGraphQL(
|
|
4769
6053
|
graphqlUrl,
|
|
@@ -4779,6 +6063,31 @@ async function resolveWebsite(graphqlUrl, slug, headers) {
|
|
|
4779
6063
|
}
|
|
4780
6064
|
return mapWebsite(data.websites[0]);
|
|
4781
6065
|
}
|
|
6066
|
+
function resolveWebsiteCached(graphqlUrl, slug, headers) {
|
|
6067
|
+
const normalizedSlug = slug.trim();
|
|
6068
|
+
const cacheKey = `${graphqlUrl}::${normalizedSlug}`;
|
|
6069
|
+
const now = Date.now();
|
|
6070
|
+
const cached = websiteContextCache.get(cacheKey);
|
|
6071
|
+
if (cached && cached.expiresAt > now) return cached.request;
|
|
6072
|
+
if (cached) websiteContextCache.delete(cacheKey);
|
|
6073
|
+
const request = resolveWebsite(graphqlUrl, normalizedSlug, headers).catch(
|
|
6074
|
+
(error) => {
|
|
6075
|
+
if (websiteContextCache.get(cacheKey)?.request === request) {
|
|
6076
|
+
websiteContextCache.delete(cacheKey);
|
|
6077
|
+
}
|
|
6078
|
+
throw error;
|
|
6079
|
+
}
|
|
6080
|
+
);
|
|
6081
|
+
if (websiteContextCache.size >= WEBSITE_CONTEXT_CACHE_MAX_ENTRIES) {
|
|
6082
|
+
const oldestKey = websiteContextCache.keys().next().value;
|
|
6083
|
+
if (oldestKey) websiteContextCache.delete(oldestKey);
|
|
6084
|
+
}
|
|
6085
|
+
websiteContextCache.set(cacheKey, {
|
|
6086
|
+
expiresAt: now + WEBSITE_CONTEXT_CACHE_TTL_MS,
|
|
6087
|
+
request
|
|
6088
|
+
});
|
|
6089
|
+
return request;
|
|
6090
|
+
}
|
|
4782
6091
|
|
|
4783
6092
|
// libs/sdk/src/client.ts
|
|
4784
6093
|
function normalizeApiUrl(apiUrl) {
|
|
@@ -4789,10 +6098,17 @@ function createBacanoClient(config) {
|
|
|
4789
6098
|
throw new WebsiteError("apiUrl is required", "INVALID_CONFIG");
|
|
4790
6099
|
if (!config.websiteSlug?.trim())
|
|
4791
6100
|
throw new WebsiteError("websiteSlug is required", "INVALID_CONFIG");
|
|
6101
|
+
if (config.initialContext && config.initialContext.slug.trim() !== config.websiteSlug.trim()) {
|
|
6102
|
+
throw new WebsiteError(
|
|
6103
|
+
"initialContext slug must match websiteSlug",
|
|
6104
|
+
"INVALID_CONFIG"
|
|
6105
|
+
);
|
|
6106
|
+
}
|
|
4792
6107
|
const apiUrl = normalizeApiUrl(config.apiUrl);
|
|
4793
6108
|
const graphqlUrl = `${apiUrl}/api/v1/website/graphql`;
|
|
4794
6109
|
const storageUrl = `${apiUrl}/storage/v1`;
|
|
4795
|
-
let context = null;
|
|
6110
|
+
let context = config.initialContext ? { ...config.initialContext } : null;
|
|
6111
|
+
let initialization = null;
|
|
4796
6112
|
function getContext() {
|
|
4797
6113
|
if (!context) throw new NotInitializedError();
|
|
4798
6114
|
return context;
|
|
@@ -4837,13 +6153,27 @@ function createBacanoClient(config) {
|
|
|
4837
6153
|
getContext,
|
|
4838
6154
|
storageUrl
|
|
4839
6155
|
);
|
|
6156
|
+
const marketingModule = createMarketingModule(
|
|
6157
|
+
apiUrl,
|
|
6158
|
+
config.websiteSlug,
|
|
6159
|
+
getContext
|
|
6160
|
+
);
|
|
4840
6161
|
return {
|
|
4841
6162
|
async init() {
|
|
4842
|
-
context
|
|
4843
|
-
|
|
4844
|
-
|
|
4845
|
-
|
|
4846
|
-
|
|
6163
|
+
if (context) return;
|
|
6164
|
+
if (!initialization) {
|
|
6165
|
+
initialization = resolveWebsiteCached(
|
|
6166
|
+
graphqlUrl,
|
|
6167
|
+
config.websiteSlug,
|
|
6168
|
+
getWebsiteHeaders
|
|
6169
|
+
).then((resolvedContext) => {
|
|
6170
|
+
context = resolvedContext;
|
|
6171
|
+
}).catch((error) => {
|
|
6172
|
+
initialization = null;
|
|
6173
|
+
throw error;
|
|
6174
|
+
});
|
|
6175
|
+
}
|
|
6176
|
+
await initialization;
|
|
4847
6177
|
},
|
|
4848
6178
|
getContext,
|
|
4849
6179
|
auth: {
|
|
@@ -4858,8 +6188,11 @@ function createBacanoClient(config) {
|
|
|
4858
6188
|
},
|
|
4859
6189
|
catalog: {
|
|
4860
6190
|
getCatalogPage: catalogModule.getCatalogPage.bind(catalogModule),
|
|
6191
|
+
getStaticCatalogSnapshot: catalogModule.getStaticCatalogSnapshot.bind(catalogModule),
|
|
6192
|
+
getStaticCatalogBootstrap: catalogModule.getStaticCatalogBootstrap.bind(catalogModule),
|
|
4861
6193
|
getProductSeoEntries: catalogModule.getProductSeoEntries.bind(catalogModule),
|
|
4862
6194
|
getProductRecommendations: catalogModule.getProductRecommendations.bind(catalogModule),
|
|
6195
|
+
getProductLiveState: catalogModule.getProductLiveState.bind(catalogModule),
|
|
4863
6196
|
getProducts: catalogModule.getProducts.bind(catalogModule),
|
|
4864
6197
|
getProduct: catalogModule.getProduct.bind(catalogModule),
|
|
4865
6198
|
getProductBySlug: catalogModule.getProductBySlug.bind(catalogModule),
|
|
@@ -4911,6 +6244,9 @@ function createBacanoClient(config) {
|
|
|
4911
6244
|
get: profileModule.get.bind(profileModule),
|
|
4912
6245
|
getAddresses: profileModule.getAddresses.bind(profileModule),
|
|
4913
6246
|
update: profileModule.update.bind(profileModule)
|
|
6247
|
+
},
|
|
6248
|
+
marketing: {
|
|
6249
|
+
captureLead: marketingModule.captureLead.bind(marketingModule)
|
|
4914
6250
|
}
|
|
4915
6251
|
};
|
|
4916
6252
|
}
|
|
@@ -4918,16 +6254,30 @@ export {
|
|
|
4918
6254
|
AuthError,
|
|
4919
6255
|
CheckoutError,
|
|
4920
6256
|
GraphQLRequestError,
|
|
6257
|
+
MARKETING_SCHEMA_VERSION,
|
|
4921
6258
|
NotInitializedError,
|
|
6259
|
+
RateLimitError,
|
|
6260
|
+
WEBSITE_MARKETING_CONTACT_PLACEMENTS,
|
|
4922
6261
|
WebsiteError,
|
|
6262
|
+
buildStorageContentMediaUrls,
|
|
4923
6263
|
buildStorageFileUrl,
|
|
4924
6264
|
buildStorageMediaUrls,
|
|
6265
|
+
buildStorageResponsiveSources,
|
|
6266
|
+
buildStorageTransformedFileUrl,
|
|
6267
|
+
captureWebsiteMarketingAttribution,
|
|
4925
6268
|
createBacanoClient,
|
|
6269
|
+
createWebsiteMarketingRuntime,
|
|
6270
|
+
formatMediaSrcSet,
|
|
6271
|
+
getAllowedMarketingParameters,
|
|
4926
6272
|
getPrimaryProductImage,
|
|
6273
|
+
getProductDetailVariants,
|
|
4927
6274
|
getProductImages,
|
|
4928
6275
|
getProductMediaByKind,
|
|
4929
6276
|
getProductMediaKind,
|
|
4930
6277
|
getProductVideos,
|
|
6278
|
+
getWebsiteContextFromPublicConfig,
|
|
6279
|
+
getWebsiteMarketingAttribution,
|
|
4931
6280
|
getWebsitePublicConfig,
|
|
4932
|
-
resolveProductPricing
|
|
6281
|
+
resolveProductPricing,
|
|
6282
|
+
sanitizeMarketingUrl
|
|
4933
6283
|
};
|