@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.cjs
CHANGED
|
@@ -23,28 +23,55 @@ __export(index_exports, {
|
|
|
23
23
|
AuthError: () => AuthError,
|
|
24
24
|
CheckoutError: () => CheckoutError,
|
|
25
25
|
GraphQLRequestError: () => GraphQLRequestError,
|
|
26
|
+
MARKETING_SCHEMA_VERSION: () => MARKETING_SCHEMA_VERSION,
|
|
26
27
|
NotInitializedError: () => NotInitializedError,
|
|
28
|
+
RateLimitError: () => RateLimitError,
|
|
29
|
+
WEBSITE_MARKETING_CONTACT_PLACEMENTS: () => WEBSITE_MARKETING_CONTACT_PLACEMENTS,
|
|
27
30
|
WebsiteError: () => WebsiteError,
|
|
31
|
+
buildStorageContentMediaUrls: () => buildStorageContentMediaUrls,
|
|
28
32
|
buildStorageFileUrl: () => buildStorageFileUrl,
|
|
29
33
|
buildStorageMediaUrls: () => buildStorageMediaUrls,
|
|
34
|
+
buildStorageResponsiveSources: () => buildStorageResponsiveSources,
|
|
35
|
+
buildStorageTransformedFileUrl: () => buildStorageTransformedFileUrl,
|
|
36
|
+
captureWebsiteMarketingAttribution: () => captureWebsiteMarketingAttribution,
|
|
30
37
|
createBacanoClient: () => createBacanoClient,
|
|
38
|
+
createWebsiteMarketingRuntime: () => createWebsiteMarketingRuntime,
|
|
39
|
+
formatMediaSrcSet: () => formatMediaSrcSet,
|
|
40
|
+
getAllowedMarketingParameters: () => getAllowedMarketingParameters,
|
|
31
41
|
getPrimaryProductImage: () => getPrimaryProductImage,
|
|
42
|
+
getProductDetailVariants: () => getProductDetailVariants,
|
|
32
43
|
getProductImages: () => getProductImages,
|
|
33
44
|
getProductMediaByKind: () => getProductMediaByKind,
|
|
34
45
|
getProductMediaKind: () => getProductMediaKind,
|
|
35
46
|
getProductVideos: () => getProductVideos,
|
|
47
|
+
getWebsiteContextFromPublicConfig: () => getWebsiteContextFromPublicConfig,
|
|
48
|
+
getWebsiteMarketingAttribution: () => getWebsiteMarketingAttribution,
|
|
36
49
|
getWebsitePublicConfig: () => getWebsitePublicConfig,
|
|
37
|
-
resolveProductPricing: () => resolveProductPricing
|
|
50
|
+
resolveProductPricing: () => resolveProductPricing,
|
|
51
|
+
sanitizeMarketingUrl: () => sanitizeMarketingUrl
|
|
38
52
|
});
|
|
39
53
|
module.exports = __toCommonJS(index_exports);
|
|
40
54
|
|
|
41
55
|
// libs/sdk/src/errors.ts
|
|
42
56
|
var WebsiteError = class extends Error {
|
|
43
57
|
code;
|
|
44
|
-
|
|
58
|
+
status;
|
|
59
|
+
retryAfterSeconds;
|
|
60
|
+
constructor(message, code = "WEBSITE_ERROR", options) {
|
|
45
61
|
super(message);
|
|
46
62
|
this.name = "WebsiteError";
|
|
47
63
|
this.code = code;
|
|
64
|
+
this.status = options?.status ?? null;
|
|
65
|
+
this.retryAfterSeconds = options?.retryAfterSeconds ?? null;
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
var RateLimitError = class extends WebsiteError {
|
|
69
|
+
constructor(message, retryAfterSeconds) {
|
|
70
|
+
super(message, "RATE_LIMITED", {
|
|
71
|
+
status: 429,
|
|
72
|
+
retryAfterSeconds
|
|
73
|
+
});
|
|
74
|
+
this.name = "RateLimitError";
|
|
48
75
|
}
|
|
49
76
|
};
|
|
50
77
|
var NotInitializedError = class extends WebsiteError {
|
|
@@ -80,7 +107,109 @@ var GraphQLRequestError = class extends WebsiteError {
|
|
|
80
107
|
}
|
|
81
108
|
};
|
|
82
109
|
|
|
110
|
+
// libs/sdk/src/media.ts
|
|
111
|
+
var DEFAULT_RESPONSIVE_WIDTHS = [240, 384, 640, 960, 1400];
|
|
112
|
+
var CONTENT_HERO_WIDTHS = [640, 960, 1400, 1920, 2560];
|
|
113
|
+
var CONTENT_HERO_MAX_WIDTH = 1920;
|
|
114
|
+
var MEDIA_VARIANTS = {
|
|
115
|
+
thumbnail: { width: 240, quality: 75, format: "webp" },
|
|
116
|
+
card: { width: 640, quality: 80, format: "webp" },
|
|
117
|
+
detail: { width: 1400, quality: 85, format: "webp" }
|
|
118
|
+
};
|
|
119
|
+
function toSafeInteger(value) {
|
|
120
|
+
if (!Number.isFinite(value)) return void 0;
|
|
121
|
+
return Math.max(1, Math.floor(value));
|
|
122
|
+
}
|
|
123
|
+
function buildQuery(options) {
|
|
124
|
+
const params = new URLSearchParams();
|
|
125
|
+
const width = toSafeInteger(options.width);
|
|
126
|
+
const height = toSafeInteger(options.height);
|
|
127
|
+
const quality = toSafeInteger(options.quality);
|
|
128
|
+
if (width) params.set("w", String(width));
|
|
129
|
+
if (height) params.set("h", String(height));
|
|
130
|
+
if (quality) params.set("q", String(Math.min(100, quality)));
|
|
131
|
+
if (options.format) params.set("f", options.format);
|
|
132
|
+
const query = params.toString();
|
|
133
|
+
return query ? `?${query}` : "";
|
|
134
|
+
}
|
|
135
|
+
function buildStorageFileUrl(storageUrl, fileId, variant = "original") {
|
|
136
|
+
if (!storageUrl || !fileId) return "";
|
|
137
|
+
const baseUrl = `${storageUrl.replace(/\/+$/, "")}/files/${fileId}`;
|
|
138
|
+
if (variant === "original") return baseUrl;
|
|
139
|
+
return `${baseUrl}${buildQuery(MEDIA_VARIANTS[variant])}`;
|
|
140
|
+
}
|
|
141
|
+
function buildStorageTransformedFileUrl(storageUrl, fileId, options) {
|
|
142
|
+
if (!storageUrl || !fileId) return "";
|
|
143
|
+
const baseUrl = `${storageUrl.replace(/\/+$/, "")}/files/${fileId}`;
|
|
144
|
+
return `${baseUrl}${buildQuery(options)}`;
|
|
145
|
+
}
|
|
146
|
+
function buildStorageResponsiveSources(storageUrl, fileId, options = {}) {
|
|
147
|
+
const widths = options.widths ?? [...DEFAULT_RESPONSIVE_WIDTHS];
|
|
148
|
+
const uniqueWidths = [...new Set(widths.map(toSafeInteger))].filter((width) => Boolean(width)).sort((left, right) => left - right);
|
|
149
|
+
return uniqueWidths.map((width) => ({
|
|
150
|
+
width,
|
|
151
|
+
url: buildStorageTransformedFileUrl(storageUrl, fileId, {
|
|
152
|
+
width,
|
|
153
|
+
quality: options.quality ?? 80,
|
|
154
|
+
format: options.format ?? "webp"
|
|
155
|
+
})
|
|
156
|
+
}));
|
|
157
|
+
}
|
|
158
|
+
function formatMediaSrcSet(sources) {
|
|
159
|
+
return sources.filter((source) => source.url && source.width > 0).map((source) => `${source.url} ${source.width}w`).join(", ");
|
|
160
|
+
}
|
|
161
|
+
function buildStorageMediaUrls(storageUrl, fileId) {
|
|
162
|
+
return {
|
|
163
|
+
originalUrl: buildStorageFileUrl(storageUrl, fileId, "original"),
|
|
164
|
+
thumbnailUrl: buildStorageFileUrl(storageUrl, fileId, "thumbnail"),
|
|
165
|
+
cardUrl: buildStorageFileUrl(storageUrl, fileId, "card"),
|
|
166
|
+
detailUrl: buildStorageFileUrl(storageUrl, fileId, "detail"),
|
|
167
|
+
responsiveSources: buildStorageResponsiveSources(storageUrl, fileId)
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
function buildStorageContentMediaUrls(storageUrl, fileId, originalWidth) {
|
|
171
|
+
const legacyUrls = buildStorageMediaUrls(storageUrl, fileId);
|
|
172
|
+
const safeOriginalWidth = toSafeInteger(originalWidth ?? void 0);
|
|
173
|
+
if (!safeOriginalWidth) {
|
|
174
|
+
return {
|
|
175
|
+
...legacyUrls,
|
|
176
|
+
heroUrl: legacyUrls.originalUrl,
|
|
177
|
+
responsiveSources: []
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
const responsiveWidths = [
|
|
181
|
+
...CONTENT_HERO_WIDTHS.filter((width) => width <= safeOriginalWidth),
|
|
182
|
+
safeOriginalWidth
|
|
183
|
+
];
|
|
184
|
+
const heroWidth = Math.min(safeOriginalWidth, CONTENT_HERO_MAX_WIDTH);
|
|
185
|
+
return {
|
|
186
|
+
...legacyUrls,
|
|
187
|
+
heroUrl: buildStorageTransformedFileUrl(storageUrl, fileId, {
|
|
188
|
+
width: heroWidth,
|
|
189
|
+
quality: 88,
|
|
190
|
+
format: "webp"
|
|
191
|
+
}),
|
|
192
|
+
responsiveSources: buildStorageResponsiveSources(storageUrl, fileId, {
|
|
193
|
+
widths: responsiveWidths,
|
|
194
|
+
quality: 85,
|
|
195
|
+
format: "webp"
|
|
196
|
+
})
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
|
|
83
200
|
// libs/sdk/src/modules/auth.ts
|
|
201
|
+
function getWebsiteContextFromPublicConfig(config) {
|
|
202
|
+
if (config.contextSchemaVersion !== 1) return void 0;
|
|
203
|
+
return {
|
|
204
|
+
websiteId: config.websiteId,
|
|
205
|
+
companyId: config.companyId,
|
|
206
|
+
chainId: config.chainId ?? null,
|
|
207
|
+
name: config.name,
|
|
208
|
+
slug: config.slug,
|
|
209
|
+
domain: config.domain,
|
|
210
|
+
config: config.config ?? null
|
|
211
|
+
};
|
|
212
|
+
}
|
|
84
213
|
function createAuthHeaders(accessToken) {
|
|
85
214
|
return accessToken ? { Authorization: `Bearer ${accessToken}` } : {};
|
|
86
215
|
}
|
|
@@ -102,14 +231,18 @@ function createRequestHeadersProvider(websiteSlug, getAccessToken) {
|
|
|
102
231
|
}
|
|
103
232
|
async function getWebsitePublicConfig(options) {
|
|
104
233
|
const apiUrl = options.apiUrl.replace(/\/+$/, "");
|
|
105
|
-
const headers =
|
|
234
|
+
const headers = {};
|
|
235
|
+
if (options.origin) headers.Origin = options.origin;
|
|
236
|
+
if (options.previewToken) {
|
|
237
|
+
headers["X-Bacano-Preview-Token"] = options.previewToken;
|
|
238
|
+
}
|
|
106
239
|
const params = new URLSearchParams({ websiteSlug: options.websiteSlug });
|
|
107
240
|
let response;
|
|
108
241
|
try {
|
|
109
242
|
response = await fetch(
|
|
110
243
|
`${apiUrl}/api/v1/website/public-config?${params.toString()}`,
|
|
111
244
|
{
|
|
112
|
-
headers,
|
|
245
|
+
headers: Object.keys(headers).length > 0 ? headers : void 0,
|
|
113
246
|
cache: "no-store"
|
|
114
247
|
}
|
|
115
248
|
);
|
|
@@ -133,12 +266,71 @@ async function getWebsitePublicConfig(options) {
|
|
|
133
266
|
require_document: false,
|
|
134
267
|
document_types: []
|
|
135
268
|
};
|
|
269
|
+
const marketing = raw.marketing ?? {
|
|
270
|
+
schema_version: 1,
|
|
271
|
+
enabled: false,
|
|
272
|
+
currency_code: "COP",
|
|
273
|
+
time_zone: "America/Bogota",
|
|
274
|
+
attribution_window_days: 90,
|
|
275
|
+
consent_policy: null,
|
|
276
|
+
destinations: { ga4: null, meta: null, clarity: null }
|
|
277
|
+
};
|
|
278
|
+
const legal = raw.legal ?? {
|
|
279
|
+
schema_version: 1,
|
|
280
|
+
version: 0,
|
|
281
|
+
content_hash: null,
|
|
282
|
+
documents: {
|
|
283
|
+
terms: null,
|
|
284
|
+
shipping: null,
|
|
285
|
+
returns: null,
|
|
286
|
+
privacy: null,
|
|
287
|
+
cookies: null
|
|
288
|
+
},
|
|
289
|
+
inquiries_channel: null,
|
|
290
|
+
direct_marketing_consent: null,
|
|
291
|
+
cookie_notice: {
|
|
292
|
+
enabled: false,
|
|
293
|
+
message: "Usamos cookies propias y de terceros para mejorar tu experiencia y medir el funcionamiento del sitio.",
|
|
294
|
+
button_label: "Entendido"
|
|
295
|
+
}
|
|
296
|
+
};
|
|
297
|
+
const mapConstructionImage = (image) => {
|
|
298
|
+
if (!image) return null;
|
|
299
|
+
const urls = buildStorageContentMediaUrls(
|
|
300
|
+
`${apiUrl}/storage/v1`,
|
|
301
|
+
image.file_id,
|
|
302
|
+
image.width
|
|
303
|
+
);
|
|
304
|
+
return {
|
|
305
|
+
fileId: image.file_id,
|
|
306
|
+
name: image.name,
|
|
307
|
+
altText: image.alt_text,
|
|
308
|
+
width: image.width,
|
|
309
|
+
height: image.height,
|
|
310
|
+
originalUrl: urls.originalUrl,
|
|
311
|
+
heroUrl: urls.heroUrl,
|
|
312
|
+
responsiveSources: urls.responsiveSources
|
|
313
|
+
};
|
|
314
|
+
};
|
|
315
|
+
const construction = raw.construction ?? {
|
|
316
|
+
schema_version: 1,
|
|
317
|
+
enabled: false,
|
|
318
|
+
enforced: false,
|
|
319
|
+
preview_access_granted: false,
|
|
320
|
+
title: null,
|
|
321
|
+
message: null,
|
|
322
|
+
desktop_image: null,
|
|
323
|
+
mobile_image: null
|
|
324
|
+
};
|
|
136
325
|
return {
|
|
137
326
|
websiteId: raw.website_id,
|
|
138
327
|
companyId: raw.company_id,
|
|
328
|
+
contextSchemaVersion: raw.context_schema_version === 1 ? 1 : null,
|
|
329
|
+
chainId: raw.chain_id ?? null,
|
|
139
330
|
slug: raw.slug,
|
|
140
331
|
name: raw.name,
|
|
141
332
|
domain: raw.domain,
|
|
333
|
+
config: raw.config ?? null,
|
|
142
334
|
buyerAccountsEnabled: raw.buyer_accounts_enabled,
|
|
143
335
|
clerkPublishableKey: raw.clerk_publishable_key,
|
|
144
336
|
customerIdentification: {
|
|
@@ -154,56 +346,67 @@ async function getWebsitePublicConfig(options) {
|
|
|
154
346
|
isDefault: documentType.is_default
|
|
155
347
|
})
|
|
156
348
|
)
|
|
349
|
+
},
|
|
350
|
+
marketing: {
|
|
351
|
+
schemaVersion: marketing.schema_version,
|
|
352
|
+
enabled: marketing.enabled,
|
|
353
|
+
currencyCode: marketing.currency_code,
|
|
354
|
+
timeZone: marketing.time_zone,
|
|
355
|
+
attributionWindowDays: marketing.attribution_window_days,
|
|
356
|
+
consentPolicy: marketing.consent_policy ? {
|
|
357
|
+
id: marketing.consent_policy.id,
|
|
358
|
+
version: marketing.consent_policy.version,
|
|
359
|
+
statement: marketing.consent_policy.statement,
|
|
360
|
+
statementHash: marketing.consent_policy.statement_hash
|
|
361
|
+
} : null,
|
|
362
|
+
destinations: {
|
|
363
|
+
ga4: marketing.destinations.ga4 ? {
|
|
364
|
+
measurementId: marketing.destinations.ga4.measurement_id,
|
|
365
|
+
diagnosticMode: marketing.destinations.ga4.diagnostic_mode ?? false
|
|
366
|
+
} : null,
|
|
367
|
+
meta: marketing.destinations.meta ? {
|
|
368
|
+
pixelId: marketing.destinations.meta.pixel_id,
|
|
369
|
+
diagnosticMode: marketing.destinations.meta.diagnostic_mode ?? false
|
|
370
|
+
} : null,
|
|
371
|
+
clarity: marketing.destinations.clarity ? { projectId: marketing.destinations.clarity.project_id } : null
|
|
372
|
+
}
|
|
373
|
+
},
|
|
374
|
+
legal: {
|
|
375
|
+
schemaVersion: legal.schema_version,
|
|
376
|
+
version: legal.version,
|
|
377
|
+
contentHash: legal.content_hash,
|
|
378
|
+
documents: {
|
|
379
|
+
terms: legal.documents.terms,
|
|
380
|
+
shipping: legal.documents.shipping ?? null,
|
|
381
|
+
returns: legal.documents.returns ?? null,
|
|
382
|
+
privacy: legal.documents.privacy,
|
|
383
|
+
cookies: legal.documents.cookies
|
|
384
|
+
},
|
|
385
|
+
inquiriesChannel: legal.inquiries_channel,
|
|
386
|
+
directMarketingConsent: legal.direct_marketing_consent ? {
|
|
387
|
+
version: legal.direct_marketing_consent.version,
|
|
388
|
+
statement: legal.direct_marketing_consent.statement,
|
|
389
|
+
statementHash: legal.direct_marketing_consent.statement_hash
|
|
390
|
+
} : null,
|
|
391
|
+
cookieNotice: {
|
|
392
|
+
enabled: legal.cookie_notice.enabled,
|
|
393
|
+
message: legal.cookie_notice.message,
|
|
394
|
+
buttonLabel: legal.cookie_notice.button_label
|
|
395
|
+
}
|
|
396
|
+
},
|
|
397
|
+
construction: {
|
|
398
|
+
schemaVersion: construction.schema_version,
|
|
399
|
+
enabled: construction.enabled,
|
|
400
|
+
enforced: construction.enforced,
|
|
401
|
+
previewAccessGranted: construction.preview_access_granted,
|
|
402
|
+
title: construction.title,
|
|
403
|
+
message: construction.message,
|
|
404
|
+
desktopImage: mapConstructionImage(construction.desktop_image),
|
|
405
|
+
mobileImage: mapConstructionImage(construction.mobile_image)
|
|
157
406
|
}
|
|
158
407
|
};
|
|
159
408
|
}
|
|
160
409
|
|
|
161
|
-
// libs/sdk/src/transport/graphql.ts
|
|
162
|
-
async function executeGraphQL(url, query, variables, headers) {
|
|
163
|
-
const resolvedHeaders = typeof headers === "function" ? await headers() : headers ?? {};
|
|
164
|
-
let res;
|
|
165
|
-
try {
|
|
166
|
-
res = await fetch(url, {
|
|
167
|
-
method: "POST",
|
|
168
|
-
headers: {
|
|
169
|
-
"Content-Type": "application/json",
|
|
170
|
-
...resolvedHeaders
|
|
171
|
-
},
|
|
172
|
-
body: JSON.stringify({ query, variables })
|
|
173
|
-
});
|
|
174
|
-
} catch (err) {
|
|
175
|
-
throw new WebsiteError(
|
|
176
|
-
`Network error connecting to ${url}: ${err instanceof Error ? err.message : String(err)}`,
|
|
177
|
-
"NETWORK_ERROR"
|
|
178
|
-
);
|
|
179
|
-
}
|
|
180
|
-
if (res.status === 429) {
|
|
181
|
-
throw new WebsiteError(
|
|
182
|
-
"Rate limit exceeded. Please try again later.",
|
|
183
|
-
"RATE_LIMITED"
|
|
184
|
-
);
|
|
185
|
-
}
|
|
186
|
-
let body;
|
|
187
|
-
try {
|
|
188
|
-
body = await res.json();
|
|
189
|
-
} catch {
|
|
190
|
-
throw new WebsiteError(
|
|
191
|
-
`Non-JSON response from ${url} (status ${res.status})`,
|
|
192
|
-
"INVALID_RESPONSE"
|
|
193
|
-
);
|
|
194
|
-
}
|
|
195
|
-
if (body.errors && body.errors.length > 0) {
|
|
196
|
-
throw new GraphQLRequestError(body.errors);
|
|
197
|
-
}
|
|
198
|
-
if (!body.data) {
|
|
199
|
-
throw new WebsiteError(
|
|
200
|
-
"GraphQL response contains no data",
|
|
201
|
-
"EMPTY_RESPONSE"
|
|
202
|
-
);
|
|
203
|
-
}
|
|
204
|
-
return body.data;
|
|
205
|
-
}
|
|
206
|
-
|
|
207
410
|
// libs/sdk/src/transport/documents.ts
|
|
208
411
|
var RESOLVE_WEBSITE_BY_SLUG = (
|
|
209
412
|
/* GraphQL */
|
|
@@ -1029,6 +1232,62 @@ var CHECK_AVAILABILITY = (
|
|
|
1029
1232
|
product_variant_id
|
|
1030
1233
|
in_stock
|
|
1031
1234
|
label
|
|
1235
|
+
max_purchasable_quantity
|
|
1236
|
+
}
|
|
1237
|
+
}
|
|
1238
|
+
`
|
|
1239
|
+
);
|
|
1240
|
+
var GET_PRODUCT_LIVE_STATE = (
|
|
1241
|
+
/* GraphQL */
|
|
1242
|
+
`
|
|
1243
|
+
query GetProductLiveState(
|
|
1244
|
+
$variantIds: [uuid!]!
|
|
1245
|
+
$companyId: uuid!
|
|
1246
|
+
$chainId: uuid!
|
|
1247
|
+
$websiteId: uuid!
|
|
1248
|
+
) {
|
|
1249
|
+
product_variants(
|
|
1250
|
+
where: {
|
|
1251
|
+
id: { _in: $variantIds }
|
|
1252
|
+
is_disabled: { _eq: false }
|
|
1253
|
+
product: {
|
|
1254
|
+
company_id: { _eq: $companyId }
|
|
1255
|
+
product_website_publications: {
|
|
1256
|
+
website_id: { _eq: $websiteId }
|
|
1257
|
+
is_visible: { _eq: true }
|
|
1258
|
+
}
|
|
1259
|
+
chain_products: {
|
|
1260
|
+
chain_id: { _eq: $chainId }
|
|
1261
|
+
is_enabled: { _eq: true }
|
|
1262
|
+
status: { _eq: active }
|
|
1263
|
+
}
|
|
1264
|
+
}
|
|
1265
|
+
}
|
|
1266
|
+
) {
|
|
1267
|
+
id
|
|
1268
|
+
product_variant_prices(
|
|
1269
|
+
where: {
|
|
1270
|
+
price_type: { _in: [CLIENT, SALE, WHOLESALE, WHOLESALE_2] }
|
|
1271
|
+
_or: [
|
|
1272
|
+
{ chain_id: { _eq: $chainId } }
|
|
1273
|
+
{ chain_id: { _is_null: true } }
|
|
1274
|
+
]
|
|
1275
|
+
}
|
|
1276
|
+
) {
|
|
1277
|
+
id
|
|
1278
|
+
price
|
|
1279
|
+
price_type
|
|
1280
|
+
chain_id
|
|
1281
|
+
}
|
|
1282
|
+
}
|
|
1283
|
+
websiteProductAvailability(
|
|
1284
|
+
product_variant_ids: $variantIds
|
|
1285
|
+
chain_id: $chainId
|
|
1286
|
+
) {
|
|
1287
|
+
product_variant_id
|
|
1288
|
+
in_stock
|
|
1289
|
+
label
|
|
1290
|
+
max_purchasable_quantity
|
|
1032
1291
|
}
|
|
1033
1292
|
}
|
|
1034
1293
|
`
|
|
@@ -1138,6 +1397,7 @@ var START_GUEST_CHECKOUT = (
|
|
|
1138
1397
|
$contact: WebsiteGuestContactInput!
|
|
1139
1398
|
$items: [WebsiteCheckoutItemInput!]!
|
|
1140
1399
|
$fulfillmentFamily: String
|
|
1400
|
+
$marketingAttribution: WebsiteMarketingAttributionInput
|
|
1141
1401
|
) {
|
|
1142
1402
|
websiteStartGuestCheckout(
|
|
1143
1403
|
website_id: $websiteId
|
|
@@ -1145,6 +1405,7 @@ var START_GUEST_CHECKOUT = (
|
|
|
1145
1405
|
contact: $contact
|
|
1146
1406
|
items: $items
|
|
1147
1407
|
fulfillment_family: $fulfillmentFamily
|
|
1408
|
+
marketing_attribution: $marketingAttribution
|
|
1148
1409
|
) {
|
|
1149
1410
|
success
|
|
1150
1411
|
order_id
|
|
@@ -1302,6 +1563,8 @@ var CHECKOUT = (
|
|
|
1302
1563
|
$paymentMethodId: uuid
|
|
1303
1564
|
$buyerAccountOptIn: Boolean
|
|
1304
1565
|
$initialPayment: WebsiteInitialPaymentInput
|
|
1566
|
+
$marketingAttribution: WebsiteMarketingAttributionInput
|
|
1567
|
+
$advertisingConsent: WebsiteAdvertisingConsentInput
|
|
1305
1568
|
) {
|
|
1306
1569
|
websiteCheckout(
|
|
1307
1570
|
order_id: $orderId
|
|
@@ -1314,6 +1577,8 @@ var CHECKOUT = (
|
|
|
1314
1577
|
payment_method_id: $paymentMethodId
|
|
1315
1578
|
buyer_account_opt_in: $buyerAccountOptIn
|
|
1316
1579
|
initial_payment: $initialPayment
|
|
1580
|
+
marketing_attribution: $marketingAttribution
|
|
1581
|
+
advertising_consent: $advertisingConsent
|
|
1317
1582
|
) {
|
|
1318
1583
|
success
|
|
1319
1584
|
order_id
|
|
@@ -2240,13 +2505,121 @@ var GET_SEARCH_SUGGESTIONS = (
|
|
|
2240
2505
|
`
|
|
2241
2506
|
);
|
|
2242
2507
|
|
|
2508
|
+
// libs/sdk/src/transport/graphql.ts
|
|
2509
|
+
var DEFAULT_MAX_ATTEMPTS = 4;
|
|
2510
|
+
var DEFAULT_MAX_TOTAL_WAIT_MS = 3 * 60 * 1e3;
|
|
2511
|
+
var DEFAULT_JITTER_RATIO = 0.1;
|
|
2512
|
+
function parseRetryAfterSeconds(res, body) {
|
|
2513
|
+
const header = res.headers?.get?.("Retry-After")?.trim();
|
|
2514
|
+
if (header) {
|
|
2515
|
+
const seconds = Number(header);
|
|
2516
|
+
if (Number.isFinite(seconds) && seconds >= 0) return Math.ceil(seconds);
|
|
2517
|
+
const retryAt = Date.parse(header);
|
|
2518
|
+
if (Number.isFinite(retryAt)) {
|
|
2519
|
+
return Math.max(Math.ceil((retryAt - Date.now()) / 1e3), 0);
|
|
2520
|
+
}
|
|
2521
|
+
}
|
|
2522
|
+
if (body && typeof body === "object") {
|
|
2523
|
+
const seconds = Number(
|
|
2524
|
+
body.retry_after_seconds
|
|
2525
|
+
);
|
|
2526
|
+
if (Number.isFinite(seconds) && seconds >= 0) return Math.ceil(seconds);
|
|
2527
|
+
}
|
|
2528
|
+
return 1;
|
|
2529
|
+
}
|
|
2530
|
+
async function createRateLimitError(res) {
|
|
2531
|
+
let body;
|
|
2532
|
+
try {
|
|
2533
|
+
body = await res.json();
|
|
2534
|
+
} catch {
|
|
2535
|
+
body = null;
|
|
2536
|
+
}
|
|
2537
|
+
const message = body && typeof body === "object" && typeof body.message === "string" ? body.message : "Rate limit exceeded. Please try again later.";
|
|
2538
|
+
return new RateLimitError(message, parseRetryAfterSeconds(res, body));
|
|
2539
|
+
}
|
|
2540
|
+
function getRetryDelayMs(error, options) {
|
|
2541
|
+
const retryAfterMs = (error.retryAfterSeconds ?? 1) * 1e3;
|
|
2542
|
+
const jitterRatio = Math.max(
|
|
2543
|
+
0,
|
|
2544
|
+
Math.min(options.jitterRatio ?? DEFAULT_JITTER_RATIO, 1)
|
|
2545
|
+
);
|
|
2546
|
+
return Math.ceil(retryAfterMs * (1 + Math.random() * jitterRatio));
|
|
2547
|
+
}
|
|
2548
|
+
function wait(delayMs) {
|
|
2549
|
+
return new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
2550
|
+
}
|
|
2551
|
+
async function executeGraphQL(url, query, variables, headers, options) {
|
|
2552
|
+
const retry = options?.retry;
|
|
2553
|
+
const maxAttempts = Math.max(
|
|
2554
|
+
1,
|
|
2555
|
+
Math.floor(retry?.maxAttempts ?? DEFAULT_MAX_ATTEMPTS)
|
|
2556
|
+
);
|
|
2557
|
+
const maxTotalWaitMs = Math.max(
|
|
2558
|
+
0,
|
|
2559
|
+
retry?.maxTotalWaitMs ?? DEFAULT_MAX_TOTAL_WAIT_MS
|
|
2560
|
+
);
|
|
2561
|
+
let totalWaitMs = 0;
|
|
2562
|
+
let res = null;
|
|
2563
|
+
for (let attempt = 1; attempt <= (retry ? maxAttempts : 1); attempt += 1) {
|
|
2564
|
+
const resolvedHeaders = typeof headers === "function" ? await headers() : headers ?? {};
|
|
2565
|
+
try {
|
|
2566
|
+
res = await fetch(url, {
|
|
2567
|
+
method: "POST",
|
|
2568
|
+
headers: {
|
|
2569
|
+
"Content-Type": "application/json",
|
|
2570
|
+
...resolvedHeaders
|
|
2571
|
+
},
|
|
2572
|
+
body: JSON.stringify({ query, variables })
|
|
2573
|
+
});
|
|
2574
|
+
} catch (err) {
|
|
2575
|
+
throw new WebsiteError(
|
|
2576
|
+
`Network error connecting to ${url}: ${err instanceof Error ? err.message : String(err)}`,
|
|
2577
|
+
"NETWORK_ERROR"
|
|
2578
|
+
);
|
|
2579
|
+
}
|
|
2580
|
+
if (res.status !== 429) break;
|
|
2581
|
+
const rateLimitError = await createRateLimitError(res);
|
|
2582
|
+
const delayMs = getRetryDelayMs(rateLimitError, retry ?? {});
|
|
2583
|
+
const canRetry = Boolean(retry) && attempt < maxAttempts && totalWaitMs + delayMs <= maxTotalWaitMs;
|
|
2584
|
+
if (!canRetry) throw rateLimitError;
|
|
2585
|
+
totalWaitMs += delayMs;
|
|
2586
|
+
await wait(delayMs);
|
|
2587
|
+
}
|
|
2588
|
+
if (!res) {
|
|
2589
|
+
throw new WebsiteError(
|
|
2590
|
+
"GraphQL request did not produce a response",
|
|
2591
|
+
"EMPTY_RESPONSE"
|
|
2592
|
+
);
|
|
2593
|
+
}
|
|
2594
|
+
let body;
|
|
2595
|
+
try {
|
|
2596
|
+
body = await res.json();
|
|
2597
|
+
} catch {
|
|
2598
|
+
throw new WebsiteError(
|
|
2599
|
+
`Non-JSON response from ${url} (status ${res.status})`,
|
|
2600
|
+
"INVALID_RESPONSE"
|
|
2601
|
+
);
|
|
2602
|
+
}
|
|
2603
|
+
if (body.errors && body.errors.length > 0) {
|
|
2604
|
+
throw new GraphQLRequestError(body.errors);
|
|
2605
|
+
}
|
|
2606
|
+
if (!body.data) {
|
|
2607
|
+
throw new WebsiteError(
|
|
2608
|
+
"GraphQL response contains no data",
|
|
2609
|
+
"EMPTY_RESPONSE"
|
|
2610
|
+
);
|
|
2611
|
+
}
|
|
2612
|
+
return body.data;
|
|
2613
|
+
}
|
|
2614
|
+
|
|
2243
2615
|
// libs/sdk/src/modules/availability.ts
|
|
2244
2616
|
var MAX_AVAILABILITY_VARIANT_IDS = 100;
|
|
2245
2617
|
function mapAvailability(raw) {
|
|
2246
2618
|
return {
|
|
2247
2619
|
productVariantId: raw.product_variant_id,
|
|
2248
2620
|
inStock: raw.in_stock,
|
|
2249
|
-
label: raw.label
|
|
2621
|
+
label: raw.label,
|
|
2622
|
+
maxPurchasableQuantity: raw.max_purchasable_quantity
|
|
2250
2623
|
};
|
|
2251
2624
|
}
|
|
2252
2625
|
function chunkArray(items, chunkSize) {
|
|
@@ -2272,7 +2645,10 @@ function createAvailabilityModule(graphqlUrl, getHeaders, getContext) {
|
|
|
2272
2645
|
if (productVariantIds.length === 0) return [];
|
|
2273
2646
|
const variantIds = uniqueValues(productVariantIds);
|
|
2274
2647
|
const results = [];
|
|
2275
|
-
for (const chunk of chunkArray(
|
|
2648
|
+
for (const chunk of chunkArray(
|
|
2649
|
+
variantIds,
|
|
2650
|
+
MAX_AVAILABILITY_VARIANT_IDS
|
|
2651
|
+
)) {
|
|
2276
2652
|
const data = await executeGraphQL(
|
|
2277
2653
|
graphqlUrl,
|
|
2278
2654
|
CHECK_AVAILABILITY,
|
|
@@ -2291,6 +2667,255 @@ function createAvailabilityModule(graphqlUrl, getHeaders, getContext) {
|
|
|
2291
2667
|
};
|
|
2292
2668
|
}
|
|
2293
2669
|
|
|
2670
|
+
// libs/sdk/src/marketing/sanitize.ts
|
|
2671
|
+
var SENSITIVE_PARAMETER_PATTERNS = [
|
|
2672
|
+
/token/i,
|
|
2673
|
+
/secret/i,
|
|
2674
|
+
/password/i,
|
|
2675
|
+
/authorization/i,
|
|
2676
|
+
/session/i,
|
|
2677
|
+
/ticket/i,
|
|
2678
|
+
/code_verifier/i,
|
|
2679
|
+
/client_secret/i,
|
|
2680
|
+
/payment/i,
|
|
2681
|
+
/order[_-]?(?:id|token)/i,
|
|
2682
|
+
/public[_-]?order/i,
|
|
2683
|
+
/clerk/i,
|
|
2684
|
+
/oauth/i,
|
|
2685
|
+
/email/i,
|
|
2686
|
+
/phone/i,
|
|
2687
|
+
/document/i
|
|
2688
|
+
];
|
|
2689
|
+
var ALLOWED_MARKETING_PARAMETERS = /* @__PURE__ */ new Set([
|
|
2690
|
+
"utm_source",
|
|
2691
|
+
"utm_medium",
|
|
2692
|
+
"utm_campaign",
|
|
2693
|
+
"utm_term",
|
|
2694
|
+
"utm_content",
|
|
2695
|
+
"utm_source_platform",
|
|
2696
|
+
"utm_id",
|
|
2697
|
+
"gclid",
|
|
2698
|
+
"gbraid",
|
|
2699
|
+
"wbraid",
|
|
2700
|
+
"fbclid",
|
|
2701
|
+
"ttclid",
|
|
2702
|
+
"msclkid"
|
|
2703
|
+
]);
|
|
2704
|
+
function sanitizeMarketingUrl(input, baseUrl) {
|
|
2705
|
+
try {
|
|
2706
|
+
const url = new URL(input, baseUrl);
|
|
2707
|
+
const safeParameters = new URLSearchParams();
|
|
2708
|
+
for (const [key, value] of url.searchParams.entries()) {
|
|
2709
|
+
if (ALLOWED_MARKETING_PARAMETERS.has(key.toLowerCase()) && !SENSITIVE_PARAMETER_PATTERNS.some((pattern) => pattern.test(key))) {
|
|
2710
|
+
safeParameters.set(key.toLowerCase(), value.slice(0, 500));
|
|
2711
|
+
}
|
|
2712
|
+
}
|
|
2713
|
+
url.search = safeParameters.toString();
|
|
2714
|
+
url.hash = "";
|
|
2715
|
+
return url.toString();
|
|
2716
|
+
} catch {
|
|
2717
|
+
return "";
|
|
2718
|
+
}
|
|
2719
|
+
}
|
|
2720
|
+
function getAllowedMarketingParameters(input, baseUrl) {
|
|
2721
|
+
const sanitized = sanitizeMarketingUrl(input, baseUrl);
|
|
2722
|
+
if (!sanitized) return {};
|
|
2723
|
+
const url = new URL(sanitized);
|
|
2724
|
+
return Object.fromEntries(url.searchParams.entries());
|
|
2725
|
+
}
|
|
2726
|
+
|
|
2727
|
+
// libs/sdk/src/marketing/attribution.ts
|
|
2728
|
+
var STORAGE_PREFIX = "bacano:marketing:attribution:v1";
|
|
2729
|
+
var GA4_CLIENT_ID_PATTERN = /^\d+\.\d+$/;
|
|
2730
|
+
var META_BROWSER_ID_PATTERN = /^fb\.\d+\.\d+\.[A-Za-z0-9_-]+$/;
|
|
2731
|
+
function storageKey(websiteId) {
|
|
2732
|
+
return `${STORAGE_PREFIX}:${websiteId}`;
|
|
2733
|
+
}
|
|
2734
|
+
function readStored(websiteId) {
|
|
2735
|
+
if (typeof window === "undefined") return null;
|
|
2736
|
+
try {
|
|
2737
|
+
const value = window.localStorage.getItem(storageKey(websiteId));
|
|
2738
|
+
if (!value) return null;
|
|
2739
|
+
const parsed = JSON.parse(value);
|
|
2740
|
+
if (new Date(parsed.expiresAt).getTime() <= Date.now()) {
|
|
2741
|
+
window.localStorage.removeItem(storageKey(websiteId));
|
|
2742
|
+
return null;
|
|
2743
|
+
}
|
|
2744
|
+
return parsed;
|
|
2745
|
+
} catch {
|
|
2746
|
+
return null;
|
|
2747
|
+
}
|
|
2748
|
+
}
|
|
2749
|
+
function writeStored(websiteId, value) {
|
|
2750
|
+
if (typeof window === "undefined") return;
|
|
2751
|
+
try {
|
|
2752
|
+
window.localStorage.setItem(storageKey(websiteId), JSON.stringify(value));
|
|
2753
|
+
} catch {
|
|
2754
|
+
}
|
|
2755
|
+
}
|
|
2756
|
+
function readCookie(name) {
|
|
2757
|
+
if (typeof document === "undefined") return void 0;
|
|
2758
|
+
try {
|
|
2759
|
+
const prefix = `${name}=`;
|
|
2760
|
+
return document.cookie.split(";").map((part) => part.trim()).find((part) => part.startsWith(prefix))?.slice(prefix.length);
|
|
2761
|
+
} catch {
|
|
2762
|
+
return void 0;
|
|
2763
|
+
}
|
|
2764
|
+
}
|
|
2765
|
+
function validMetaBrowserId(value) {
|
|
2766
|
+
const normalized = value?.trim();
|
|
2767
|
+
return normalized && normalized.length <= 512 && META_BROWSER_ID_PATTERN.test(normalized) ? normalized : void 0;
|
|
2768
|
+
}
|
|
2769
|
+
function optional(value) {
|
|
2770
|
+
const normalized = value?.trim();
|
|
2771
|
+
return normalized ? normalized.slice(0, 500) : void 0;
|
|
2772
|
+
}
|
|
2773
|
+
function buildTouchpoint() {
|
|
2774
|
+
if (typeof window === "undefined") return null;
|
|
2775
|
+
const params = getAllowedMarketingParameters(window.location.href);
|
|
2776
|
+
const referrerHost = (() => {
|
|
2777
|
+
if (!document.referrer) return void 0;
|
|
2778
|
+
try {
|
|
2779
|
+
const hostname = new URL(document.referrer).hostname;
|
|
2780
|
+
return hostname === window.location.hostname ? void 0 : hostname.slice(0, 255);
|
|
2781
|
+
} catch {
|
|
2782
|
+
return void 0;
|
|
2783
|
+
}
|
|
2784
|
+
})();
|
|
2785
|
+
return {
|
|
2786
|
+
capturedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2787
|
+
landingUrl: sanitizeMarketingUrl(window.location.href),
|
|
2788
|
+
referrerHost,
|
|
2789
|
+
source: optional(params.utm_source),
|
|
2790
|
+
medium: optional(params.utm_medium),
|
|
2791
|
+
campaign: optional(params.utm_campaign),
|
|
2792
|
+
term: optional(params.utm_term),
|
|
2793
|
+
content: optional(params.utm_content),
|
|
2794
|
+
sourcePlatform: optional(params.utm_source_platform),
|
|
2795
|
+
campaignId: optional(params.utm_id),
|
|
2796
|
+
gclid: optional(params.gclid),
|
|
2797
|
+
gbraid: optional(params.gbraid),
|
|
2798
|
+
wbraid: optional(params.wbraid),
|
|
2799
|
+
fbclid: optional(params.fbclid),
|
|
2800
|
+
ttclid: optional(params.ttclid),
|
|
2801
|
+
msclkid: optional(params.msclkid)
|
|
2802
|
+
};
|
|
2803
|
+
}
|
|
2804
|
+
function isNonDirect(touchpoint) {
|
|
2805
|
+
return Boolean(
|
|
2806
|
+
touchpoint.source || touchpoint.medium || touchpoint.campaign || touchpoint.gclid || touchpoint.gbraid || touchpoint.wbraid || touchpoint.fbclid || touchpoint.ttclid || touchpoint.msclkid || touchpoint.referrerHost
|
|
2807
|
+
);
|
|
2808
|
+
}
|
|
2809
|
+
function toPublic(stored) {
|
|
2810
|
+
return {
|
|
2811
|
+
schemaVersion: 1,
|
|
2812
|
+
firstTouch: stored.firstTouch,
|
|
2813
|
+
lastNonDirectTouch: stored.lastNonDirectTouch,
|
|
2814
|
+
ga4ClientId: stored.ga4ClientId,
|
|
2815
|
+
ga4SessionId: stored.ga4SessionId,
|
|
2816
|
+
metaFbp: stored.metaFbp,
|
|
2817
|
+
metaFbc: stored.metaFbc,
|
|
2818
|
+
clientUserAgent: stored.clientUserAgent,
|
|
2819
|
+
capturedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2820
|
+
expiresAt: stored.expiresAt
|
|
2821
|
+
};
|
|
2822
|
+
}
|
|
2823
|
+
function refreshWebsiteMarketingMetaContext(websiteId) {
|
|
2824
|
+
try {
|
|
2825
|
+
const stored = readStored(websiteId);
|
|
2826
|
+
if (!stored || typeof window === "undefined") return false;
|
|
2827
|
+
const fbp = validMetaBrowserId(readCookie("_fbp"));
|
|
2828
|
+
const clickTouchpoint = stored.lastNonDirectTouch ?? stored.firstTouch;
|
|
2829
|
+
const derivedFbc = clickTouchpoint.fbclid ? `fb.1.${new Date(clickTouchpoint.capturedAt).getTime()}.${clickTouchpoint.fbclid}` : void 0;
|
|
2830
|
+
const fbc = validMetaBrowserId(readCookie("_fbc") ?? derivedFbc);
|
|
2831
|
+
const clientUserAgent = window.navigator?.userAgent.trim().slice(0, 1024);
|
|
2832
|
+
writeStored(websiteId, {
|
|
2833
|
+
...stored,
|
|
2834
|
+
metaFbp: fbp ?? stored.metaFbp,
|
|
2835
|
+
metaFbc: fbc ?? stored.metaFbc,
|
|
2836
|
+
clientUserAgent: clientUserAgent || stored.clientUserAgent
|
|
2837
|
+
});
|
|
2838
|
+
return Boolean(fbp || fbc || clientUserAgent);
|
|
2839
|
+
} catch {
|
|
2840
|
+
return false;
|
|
2841
|
+
}
|
|
2842
|
+
}
|
|
2843
|
+
function storeWebsiteMarketingGa4ClientId(websiteId, clientId) {
|
|
2844
|
+
if (typeof clientId !== "string") return false;
|
|
2845
|
+
const normalized = clientId.trim();
|
|
2846
|
+
if (!GA4_CLIENT_ID_PATTERN.test(normalized)) return false;
|
|
2847
|
+
const stored = readStored(websiteId);
|
|
2848
|
+
if (!stored) return false;
|
|
2849
|
+
writeStored(websiteId, { ...stored, ga4ClientId: normalized });
|
|
2850
|
+
return true;
|
|
2851
|
+
}
|
|
2852
|
+
function storeWebsiteMarketingGa4SessionId(websiteId, sessionId) {
|
|
2853
|
+
const normalized = typeof sessionId === "number" && Number.isSafeInteger(sessionId) ? String(sessionId) : typeof sessionId === "string" ? sessionId.trim() : "";
|
|
2854
|
+
const numericValue = Number(normalized);
|
|
2855
|
+
if (!/^\d{1,16}$/.test(normalized) || !Number.isSafeInteger(numericValue) || numericValue <= 0) {
|
|
2856
|
+
return false;
|
|
2857
|
+
}
|
|
2858
|
+
const stored = readStored(websiteId);
|
|
2859
|
+
if (!stored) return false;
|
|
2860
|
+
writeStored(websiteId, { ...stored, ga4SessionId: normalized });
|
|
2861
|
+
return true;
|
|
2862
|
+
}
|
|
2863
|
+
function captureWebsiteMarketingAttribution(websiteId, windowDays) {
|
|
2864
|
+
const current = buildTouchpoint();
|
|
2865
|
+
if (!current) return null;
|
|
2866
|
+
const stored = readStored(websiteId);
|
|
2867
|
+
const expiresAt = stored?.expiresAt ?? new Date(Date.now() + Math.max(1, windowDays) * 864e5).toISOString();
|
|
2868
|
+
const next = {
|
|
2869
|
+
firstTouch: stored?.firstTouch ?? current,
|
|
2870
|
+
lastNonDirectTouch: isNonDirect(current) ? current : stored?.lastNonDirectTouch ?? null,
|
|
2871
|
+
ga4ClientId: stored?.ga4ClientId,
|
|
2872
|
+
ga4SessionId: stored?.ga4SessionId,
|
|
2873
|
+
metaFbp: stored?.metaFbp,
|
|
2874
|
+
metaFbc: stored?.metaFbc,
|
|
2875
|
+
clientUserAgent: stored?.clientUserAgent,
|
|
2876
|
+
expiresAt
|
|
2877
|
+
};
|
|
2878
|
+
writeStored(websiteId, next);
|
|
2879
|
+
return toPublic(next);
|
|
2880
|
+
}
|
|
2881
|
+
function getWebsiteMarketingAttribution(websiteId) {
|
|
2882
|
+
const stored = readStored(websiteId);
|
|
2883
|
+
return stored ? toPublic(stored) : null;
|
|
2884
|
+
}
|
|
2885
|
+
function serializeTouchpoint(touchpoint) {
|
|
2886
|
+
return {
|
|
2887
|
+
captured_at: touchpoint.capturedAt,
|
|
2888
|
+
landing_url: touchpoint.landingUrl,
|
|
2889
|
+
referrer_host: touchpoint.referrerHost,
|
|
2890
|
+
source: touchpoint.source,
|
|
2891
|
+
medium: touchpoint.medium,
|
|
2892
|
+
campaign: touchpoint.campaign,
|
|
2893
|
+
term: touchpoint.term,
|
|
2894
|
+
content: touchpoint.content,
|
|
2895
|
+
source_platform: touchpoint.sourcePlatform,
|
|
2896
|
+
campaign_id: touchpoint.campaignId,
|
|
2897
|
+
gclid: touchpoint.gclid,
|
|
2898
|
+
gbraid: touchpoint.gbraid,
|
|
2899
|
+
wbraid: touchpoint.wbraid,
|
|
2900
|
+
fbclid: touchpoint.fbclid,
|
|
2901
|
+
ttclid: touchpoint.ttclid,
|
|
2902
|
+
msclkid: touchpoint.msclkid
|
|
2903
|
+
};
|
|
2904
|
+
}
|
|
2905
|
+
function serializeWebsiteMarketingAttribution(attribution) {
|
|
2906
|
+
return {
|
|
2907
|
+
schema_version: attribution.schemaVersion,
|
|
2908
|
+
first_touch: serializeTouchpoint(attribution.firstTouch),
|
|
2909
|
+
last_non_direct_touch: attribution.lastNonDirectTouch ? serializeTouchpoint(attribution.lastNonDirectTouch) : null,
|
|
2910
|
+
ga4_client_id: attribution.ga4ClientId,
|
|
2911
|
+
ga4_session_id: attribution.ga4SessionId,
|
|
2912
|
+
meta_fbp: attribution.metaFbp,
|
|
2913
|
+
meta_fbc: attribution.metaFbc,
|
|
2914
|
+
client_user_agent: attribution.clientUserAgent,
|
|
2915
|
+
captured_at: attribution.capturedAt
|
|
2916
|
+
};
|
|
2917
|
+
}
|
|
2918
|
+
|
|
2294
2919
|
// libs/sdk/src/modules/cart.ts
|
|
2295
2920
|
function mapCartItem(raw) {
|
|
2296
2921
|
return {
|
|
@@ -2411,7 +3036,8 @@ function createCartModule(graphqlUrl, getHeaders, getContext, cartSessionStorage
|
|
|
2411
3036
|
items: input.items.map((item) => ({
|
|
2412
3037
|
product_variant_id: item.productVariantId,
|
|
2413
3038
|
quantity: item.quantity
|
|
2414
|
-
}))
|
|
3039
|
+
})),
|
|
3040
|
+
marketingAttribution: input.marketingAttribution ? serializeWebsiteMarketingAttribution(input.marketingAttribution) : void 0
|
|
2415
3041
|
},
|
|
2416
3042
|
getHeaders
|
|
2417
3043
|
);
|
|
@@ -2533,7 +3159,9 @@ function createCartSessionStorage(kind, getSlug) {
|
|
|
2533
3159
|
const key = readKey();
|
|
2534
3160
|
const match = document.cookie.split("; ").find((row) => row.startsWith(`${key}=`));
|
|
2535
3161
|
if (!match) return null;
|
|
2536
|
-
return JSON.parse(
|
|
3162
|
+
return JSON.parse(
|
|
3163
|
+
decodeURIComponent(match.split("=").slice(1).join("="))
|
|
3164
|
+
);
|
|
2537
3165
|
} catch {
|
|
2538
3166
|
return null;
|
|
2539
3167
|
}
|
|
@@ -2573,46 +3201,129 @@ function createCartSessionStorage(kind, getSlug) {
|
|
|
2573
3201
|
};
|
|
2574
3202
|
}
|
|
2575
3203
|
|
|
2576
|
-
// libs/sdk/src/
|
|
2577
|
-
var
|
|
2578
|
-
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
|
|
2585
|
-
}
|
|
2586
|
-
|
|
2587
|
-
const
|
|
2588
|
-
const
|
|
2589
|
-
const
|
|
2590
|
-
|
|
2591
|
-
|
|
2592
|
-
|
|
2593
|
-
|
|
2594
|
-
|
|
2595
|
-
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
|
|
2607
|
-
|
|
2608
|
-
|
|
2609
|
-
|
|
3204
|
+
// libs/sdk/src/modules/static-catalog.ts
|
|
3205
|
+
var STATIC_CATALOG_PAGE_SIZE = 100;
|
|
3206
|
+
var STATIC_CATALOG_MAX_PRODUCTS = 1e4;
|
|
3207
|
+
var STATIC_CATALOG_INITIAL_PAGE_SIZE = 24;
|
|
3208
|
+
async function collectStaticCatalog({
|
|
3209
|
+
websiteId,
|
|
3210
|
+
snapshot: options,
|
|
3211
|
+
bootstrap,
|
|
3212
|
+
loadPage
|
|
3213
|
+
}) {
|
|
3214
|
+
const pageSize = options.pageSize ?? STATIC_CATALOG_PAGE_SIZE;
|
|
3215
|
+
const maxProducts = options.maxProducts ?? STATIC_CATALOG_MAX_PRODUCTS;
|
|
3216
|
+
const initialPageSize = bootstrap?.initialPageSize ?? STATIC_CATALOG_INITIAL_PAGE_SIZE;
|
|
3217
|
+
const sortBy = bootstrap?.sortBy ?? "name_asc";
|
|
3218
|
+
if (!Number.isInteger(pageSize) || pageSize < 1 || pageSize > 100) {
|
|
3219
|
+
throw new WebsiteError(
|
|
3220
|
+
"Static catalog pageSize must be an integer between 1 and 100.",
|
|
3221
|
+
"INVALID_STATIC_CATALOG_PAGE_SIZE"
|
|
3222
|
+
);
|
|
3223
|
+
}
|
|
3224
|
+
if (!Number.isInteger(maxProducts) || maxProducts < 1 || maxProducts > STATIC_CATALOG_MAX_PRODUCTS) {
|
|
3225
|
+
throw new WebsiteError(
|
|
3226
|
+
`Static catalog maxProducts must be an integer between 1 and ${STATIC_CATALOG_MAX_PRODUCTS}.`,
|
|
3227
|
+
"INVALID_STATIC_CATALOG_MAX_PRODUCTS"
|
|
3228
|
+
);
|
|
3229
|
+
}
|
|
3230
|
+
if (bootstrap && (!Number.isInteger(initialPageSize) || initialPageSize < 1 || initialPageSize > 100)) {
|
|
3231
|
+
throw new WebsiteError(
|
|
3232
|
+
"Static catalog initialPageSize must be an integer between 1 and 100.",
|
|
3233
|
+
"INVALID_STATIC_CATALOG_INITIAL_PAGE_SIZE"
|
|
3234
|
+
);
|
|
3235
|
+
}
|
|
3236
|
+
const generatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
3237
|
+
const products = [];
|
|
3238
|
+
let pageCount = 0;
|
|
3239
|
+
let expectedTotal = null;
|
|
3240
|
+
let firstPage = null;
|
|
3241
|
+
for (let offset = 0; ; ) {
|
|
3242
|
+
const includeBootstrapData = Boolean(bootstrap && offset === 0);
|
|
3243
|
+
const requestLimit = includeBootstrapData ? Math.max(pageSize, initialPageSize) : pageSize;
|
|
3244
|
+
const page = await loadPage({
|
|
3245
|
+
categoryListKey: options.categoryListKey,
|
|
3246
|
+
attributeListKey: options.attributeListKey,
|
|
3247
|
+
sortBy,
|
|
3248
|
+
limit: requestLimit,
|
|
3249
|
+
offset,
|
|
3250
|
+
include: {
|
|
3251
|
+
products: true,
|
|
3252
|
+
detailVariants: true,
|
|
3253
|
+
categories: includeBootstrapData,
|
|
3254
|
+
attributes: true,
|
|
3255
|
+
filters: includeBootstrapData,
|
|
3256
|
+
brands: includeBootstrapData,
|
|
3257
|
+
debug: false
|
|
3258
|
+
}
|
|
3259
|
+
});
|
|
3260
|
+
if (offset === 0) firstPage = page;
|
|
3261
|
+
pageCount += 1;
|
|
3262
|
+
const pageTotal = page.pagination.total;
|
|
3263
|
+
if (expectedTotal === null) {
|
|
3264
|
+
expectedTotal = pageTotal;
|
|
3265
|
+
} else if (pageTotal !== expectedTotal) {
|
|
3266
|
+
throwCatalogChanged();
|
|
3267
|
+
}
|
|
3268
|
+
if (pageTotal > maxProducts) {
|
|
3269
|
+
throw new WebsiteError(
|
|
3270
|
+
`Static catalog contains ${pageTotal} products and exceeds maxProducts ${maxProducts}.`,
|
|
3271
|
+
"STATIC_CATALOG_LIMIT_EXCEEDED"
|
|
3272
|
+
);
|
|
3273
|
+
}
|
|
3274
|
+
products.push(...page.products);
|
|
3275
|
+
if (!page.pagination.hasMore) break;
|
|
3276
|
+
if (page.products.length === 0) {
|
|
3277
|
+
throw new WebsiteError(
|
|
3278
|
+
"Static catalog pagination did not advance.",
|
|
3279
|
+
"STATIC_CATALOG_PAGINATION_STALLED"
|
|
3280
|
+
);
|
|
3281
|
+
}
|
|
3282
|
+
offset += page.products.length;
|
|
3283
|
+
}
|
|
3284
|
+
const total = expectedTotal ?? 0;
|
|
3285
|
+
const uniqueProductIds = new Set(products.map((product) => product.id));
|
|
3286
|
+
if (products.length !== total || uniqueProductIds.size !== total) {
|
|
3287
|
+
throwCatalogChanged();
|
|
3288
|
+
}
|
|
3289
|
+
const snapshot = {
|
|
3290
|
+
schemaVersion: 1,
|
|
3291
|
+
snapshotId: `${websiteId}:${generatedAt}`,
|
|
3292
|
+
generatedAt,
|
|
3293
|
+
websiteId,
|
|
3294
|
+
products,
|
|
3295
|
+
total,
|
|
3296
|
+
pageSize,
|
|
3297
|
+
pageCount
|
|
2610
3298
|
};
|
|
3299
|
+
const initialPage = bootstrap && firstPage ? {
|
|
3300
|
+
...firstPage,
|
|
3301
|
+
products: firstPage.products.slice(0, initialPageSize),
|
|
3302
|
+
pagination: {
|
|
3303
|
+
...firstPage.pagination,
|
|
3304
|
+
limit: initialPageSize,
|
|
3305
|
+
offset: 0,
|
|
3306
|
+
hasMore: total > initialPageSize
|
|
3307
|
+
}
|
|
3308
|
+
} : null;
|
|
3309
|
+
return { snapshot, initialPage };
|
|
3310
|
+
}
|
|
3311
|
+
function throwCatalogChanged() {
|
|
3312
|
+
throw new WebsiteError(
|
|
3313
|
+
"Static catalog changed while it was being collected. Retry the build to get a consistent snapshot.",
|
|
3314
|
+
"STATIC_CATALOG_CHANGED_DURING_BUILD"
|
|
3315
|
+
);
|
|
2611
3316
|
}
|
|
2612
3317
|
|
|
2613
3318
|
// libs/sdk/src/modules/catalog.ts
|
|
2614
3319
|
var MAX_AVAILABILITY_VARIANT_IDS2 = 100;
|
|
2615
3320
|
var DEFAULT_FILTER_ATTRIBUTE_LIST_KEY = "atributos";
|
|
3321
|
+
var MAX_LIVE_STATE_VARIANT_IDS = 100;
|
|
3322
|
+
var STATIC_CATALOG_RETRY = {
|
|
3323
|
+
maxAttempts: 4,
|
|
3324
|
+
maxTotalWaitMs: 3 * 60 * 1e3,
|
|
3325
|
+
jitterRatio: 0.1
|
|
3326
|
+
};
|
|
2616
3327
|
function mapPrice(raw) {
|
|
2617
3328
|
return {
|
|
2618
3329
|
id: raw.id,
|
|
@@ -2645,6 +3356,9 @@ function getProductImages(media) {
|
|
|
2645
3356
|
function getProductVideos(media) {
|
|
2646
3357
|
return getProductMediaByKind(media, "video");
|
|
2647
3358
|
}
|
|
3359
|
+
function getProductDetailVariants(product) {
|
|
3360
|
+
return product.detailVariants ?? product.variants;
|
|
3361
|
+
}
|
|
2648
3362
|
function getPrimaryProductImage(media) {
|
|
2649
3363
|
return getProductImages(media)[0] ?? null;
|
|
2650
3364
|
}
|
|
@@ -2802,6 +3516,9 @@ function mapRelatedProduct(raw, storageUrl) {
|
|
|
2802
3516
|
};
|
|
2803
3517
|
}
|
|
2804
3518
|
function mapProduct(raw, storageUrl, labels, chainId) {
|
|
3519
|
+
const detailVariants = raw.product_variants.map((variant) => mapVariant(variant, labels, chainId)).filter((variant) => variant.pricing.currentPrice != null);
|
|
3520
|
+
const listingVariantIds = raw.listing_variant_ids ? new Set(raw.listing_variant_ids) : null;
|
|
3521
|
+
const variants = listingVariantIds ? detailVariants.filter((variant) => listingVariantIds.has(variant.id)) : detailVariants;
|
|
2805
3522
|
return {
|
|
2806
3523
|
id: raw.id,
|
|
2807
3524
|
name: raw.name,
|
|
@@ -2818,9 +3535,8 @@ function mapProduct(raw, storageUrl, labels, chainId) {
|
|
|
2818
3535
|
measurementUnit: raw.measurement_unit,
|
|
2819
3536
|
createdAt: raw.created_at,
|
|
2820
3537
|
updatedAt: raw.updated_at ?? raw.created_at,
|
|
2821
|
-
variants
|
|
2822
|
-
|
|
2823
|
-
),
|
|
3538
|
+
variants,
|
|
3539
|
+
detailVariants,
|
|
2824
3540
|
media: raw.product_media.map((m) => mapMedia(m, storageUrl)),
|
|
2825
3541
|
attributes: raw.product_attributes.map(
|
|
2826
3542
|
(attribute) => mapProductAttribute(attribute, labels)
|
|
@@ -3263,15 +3979,171 @@ function createCatalogModule(graphqlUrl, getHeaders, getContext, storageUrl) {
|
|
|
3263
3979
|
const publicAttributeLabels = buildPublicAttributeLabels(
|
|
3264
3980
|
data.catalog_attribute_lists
|
|
3265
3981
|
);
|
|
3982
|
+
const mappedProduct = mapProduct(
|
|
3983
|
+
product,
|
|
3984
|
+
storageUrl,
|
|
3985
|
+
publicAttributeLabels,
|
|
3986
|
+
ctx.chainId
|
|
3987
|
+
);
|
|
3266
3988
|
const filteredProducts = await filterProductsForWebsiteStock(
|
|
3267
3989
|
graphqlUrl,
|
|
3268
3990
|
getHeaders,
|
|
3269
|
-
[
|
|
3991
|
+
[mappedProduct],
|
|
3270
3992
|
ctx.chainId
|
|
3271
3993
|
);
|
|
3272
|
-
|
|
3994
|
+
if (!filteredProducts[0]) return null;
|
|
3995
|
+
return {
|
|
3996
|
+
...mappedProduct,
|
|
3997
|
+
variants: getProductDetailVariants(mappedProduct)
|
|
3998
|
+
};
|
|
3999
|
+
};
|
|
4000
|
+
const loadCatalogPage = async (opts, retryRateLimits = false) => {
|
|
4001
|
+
const ctx = getContext();
|
|
4002
|
+
if (!ctx.chainId) {
|
|
4003
|
+
throw new WebsiteError(
|
|
4004
|
+
"This website has no chain configured. Catalog requires a chain_id.",
|
|
4005
|
+
"NO_CHAIN"
|
|
4006
|
+
);
|
|
4007
|
+
}
|
|
4008
|
+
const chainId = ctx.chainId;
|
|
4009
|
+
const filters = {
|
|
4010
|
+
category_slug: opts.categorySlug,
|
|
4011
|
+
subcategory_slug: opts.subcategorySlug,
|
|
4012
|
+
tags: opts.tags,
|
|
4013
|
+
search: opts.search,
|
|
4014
|
+
brand_id: opts.brandId,
|
|
4015
|
+
min_price: opts.minPrice,
|
|
4016
|
+
max_price: opts.maxPrice,
|
|
4017
|
+
attributes_by_slug: opts.attributesBySlug,
|
|
4018
|
+
sort_by: opts.sortBy,
|
|
4019
|
+
limit: opts.limit,
|
|
4020
|
+
offset: opts.offset
|
|
4021
|
+
};
|
|
4022
|
+
const data = await executeGraphQL(
|
|
4023
|
+
graphqlUrl,
|
|
4024
|
+
WEBSITE_GET_CATALOG_PAGE,
|
|
4025
|
+
{
|
|
4026
|
+
websiteId: ctx.websiteId,
|
|
4027
|
+
categoryListKey: opts.categoryListKey,
|
|
4028
|
+
attributeListKey: opts.attributeListKey,
|
|
4029
|
+
filters,
|
|
4030
|
+
include: opts.include
|
|
4031
|
+
},
|
|
4032
|
+
getHeaders,
|
|
4033
|
+
retryRateLimits ? { retry: STATIC_CATALOG_RETRY } : void 0
|
|
4034
|
+
);
|
|
4035
|
+
if (!data.result.success) {
|
|
4036
|
+
throw new WebsiteError(
|
|
4037
|
+
data.result.errors.join("; ") || "Unable to resolve catalog page",
|
|
4038
|
+
"CATALOG_PAGE_ERROR"
|
|
4039
|
+
);
|
|
4040
|
+
}
|
|
4041
|
+
const products = parseActionJson(data.result.products, []);
|
|
4042
|
+
const categoryList = parseActionJson(
|
|
4043
|
+
data.result.category_list,
|
|
4044
|
+
null
|
|
4045
|
+
);
|
|
4046
|
+
const attributeList = parseActionJson(
|
|
4047
|
+
data.result.attribute_list,
|
|
4048
|
+
null
|
|
4049
|
+
);
|
|
4050
|
+
const filtersResult = parseActionJson(
|
|
4051
|
+
data.result.filters,
|
|
4052
|
+
null
|
|
4053
|
+
);
|
|
4054
|
+
const brands = parseActionJson(data.result.brands, []);
|
|
4055
|
+
const pagination = parseActionJson(
|
|
4056
|
+
data.result.pagination,
|
|
4057
|
+
{
|
|
4058
|
+
limit: opts.limit ?? 24,
|
|
4059
|
+
offset: opts.offset ?? 0,
|
|
4060
|
+
total: 0,
|
|
4061
|
+
hasMore: false
|
|
4062
|
+
}
|
|
4063
|
+
);
|
|
4064
|
+
const appliedFilters = parseActionJson(
|
|
4065
|
+
data.result.applied_filters,
|
|
4066
|
+
{}
|
|
4067
|
+
);
|
|
4068
|
+
const debug = parseActionJson(
|
|
4069
|
+
data.result.debug,
|
|
4070
|
+
null
|
|
4071
|
+
);
|
|
4072
|
+
const labels = buildPublicAttributeLabels(
|
|
4073
|
+
attributeList ? [attributeList] : []
|
|
4074
|
+
);
|
|
4075
|
+
return {
|
|
4076
|
+
products: products.map(
|
|
4077
|
+
(product) => mapProduct(product, storageUrl, labels, chainId)
|
|
4078
|
+
),
|
|
4079
|
+
categories: categoryList ? mapCategoryList(categoryList) : null,
|
|
4080
|
+
attributes: attributeList ? mapAttributeList(attributeList) : null,
|
|
4081
|
+
filters: filtersResult,
|
|
4082
|
+
brands,
|
|
4083
|
+
pagination,
|
|
4084
|
+
appliedFilters,
|
|
4085
|
+
debug
|
|
4086
|
+
};
|
|
3273
4087
|
};
|
|
3274
4088
|
return {
|
|
4089
|
+
async getProductLiveState(productVariantIds) {
|
|
4090
|
+
const ctx = getContext();
|
|
4091
|
+
if (!ctx.chainId) {
|
|
4092
|
+
throw new WebsiteError(
|
|
4093
|
+
"This website has no chain configured. Product live state requires a chain_id.",
|
|
4094
|
+
"NO_CHAIN"
|
|
4095
|
+
);
|
|
4096
|
+
}
|
|
4097
|
+
const chainId = ctx.chainId;
|
|
4098
|
+
const variantIds = [...new Set(productVariantIds)];
|
|
4099
|
+
if (variantIds.length === 0) return [];
|
|
4100
|
+
if (variantIds.length > MAX_LIVE_STATE_VARIANT_IDS) {
|
|
4101
|
+
throw new WebsiteError(
|
|
4102
|
+
`Product live state accepts at most ${MAX_LIVE_STATE_VARIANT_IDS} variants per request.`,
|
|
4103
|
+
"PRODUCT_LIVE_STATE_LIMIT_EXCEEDED"
|
|
4104
|
+
);
|
|
4105
|
+
}
|
|
4106
|
+
const data = await executeGraphQL(
|
|
4107
|
+
graphqlUrl,
|
|
4108
|
+
GET_PRODUCT_LIVE_STATE,
|
|
4109
|
+
{
|
|
4110
|
+
variantIds,
|
|
4111
|
+
companyId: ctx.companyId,
|
|
4112
|
+
chainId,
|
|
4113
|
+
websiteId: ctx.websiteId
|
|
4114
|
+
},
|
|
4115
|
+
getHeaders
|
|
4116
|
+
);
|
|
4117
|
+
const variantsById = new Map(
|
|
4118
|
+
(data.product_variants ?? []).map((variant) => [variant.id, variant])
|
|
4119
|
+
);
|
|
4120
|
+
const availabilityById = new Map(
|
|
4121
|
+
(data.websiteProductAvailability ?? []).map((availability) => [
|
|
4122
|
+
availability.product_variant_id,
|
|
4123
|
+
availability
|
|
4124
|
+
])
|
|
4125
|
+
);
|
|
4126
|
+
return variantIds.flatMap((variantId) => {
|
|
4127
|
+
const variant = variantsById.get(variantId);
|
|
4128
|
+
if (!variant) return [];
|
|
4129
|
+
const availability = availabilityById.get(variantId);
|
|
4130
|
+
const pricing = resolveProductPricing(
|
|
4131
|
+
selectPublicPrices(
|
|
4132
|
+
variant.product_variant_prices.map(mapPrice),
|
|
4133
|
+
chainId
|
|
4134
|
+
)
|
|
4135
|
+
);
|
|
4136
|
+
return [
|
|
4137
|
+
{
|
|
4138
|
+
productVariantId: variantId,
|
|
4139
|
+
inStock: availability?.in_stock ?? false,
|
|
4140
|
+
availabilityLabel: availability?.label ?? "Agotado",
|
|
4141
|
+
maxPurchasableQuantity: availability?.max_purchasable_quantity ?? 0,
|
|
4142
|
+
pricing
|
|
4143
|
+
}
|
|
4144
|
+
];
|
|
4145
|
+
});
|
|
4146
|
+
},
|
|
3275
4147
|
async getProductRecommendations(opts) {
|
|
3276
4148
|
const ctx = getContext();
|
|
3277
4149
|
const limit = opts.limit ?? 16;
|
|
@@ -3346,86 +4218,36 @@ function createCatalogModule(graphqlUrl, getHeaders, getContext, storageUrl) {
|
|
|
3346
4218
|
}
|
|
3347
4219
|
};
|
|
3348
4220
|
},
|
|
3349
|
-
|
|
4221
|
+
getCatalogPage: loadCatalogPage,
|
|
4222
|
+
async getStaticCatalogSnapshot(opts) {
|
|
3350
4223
|
const ctx = getContext();
|
|
3351
|
-
|
|
3352
|
-
|
|
3353
|
-
|
|
3354
|
-
|
|
3355
|
-
|
|
3356
|
-
|
|
3357
|
-
|
|
3358
|
-
const
|
|
3359
|
-
|
|
3360
|
-
|
|
3361
|
-
|
|
3362
|
-
|
|
3363
|
-
|
|
3364
|
-
|
|
3365
|
-
max_price: opts.maxPrice,
|
|
3366
|
-
attributes_by_slug: opts.attributesBySlug,
|
|
3367
|
-
sort_by: opts.sortBy,
|
|
3368
|
-
limit: opts.limit,
|
|
3369
|
-
offset: opts.offset
|
|
3370
|
-
};
|
|
3371
|
-
const data = await executeGraphQL(
|
|
3372
|
-
graphqlUrl,
|
|
3373
|
-
WEBSITE_GET_CATALOG_PAGE,
|
|
3374
|
-
{
|
|
3375
|
-
websiteId: ctx.websiteId,
|
|
3376
|
-
categoryListKey: opts.categoryListKey,
|
|
3377
|
-
attributeListKey: opts.attributeListKey,
|
|
3378
|
-
filters,
|
|
3379
|
-
include: opts.include
|
|
4224
|
+
return (await collectStaticCatalog({
|
|
4225
|
+
websiteId: ctx.websiteId,
|
|
4226
|
+
snapshot: opts,
|
|
4227
|
+
loadPage: (options) => loadCatalogPage(options, true)
|
|
4228
|
+
})).snapshot;
|
|
4229
|
+
},
|
|
4230
|
+
async getStaticCatalogBootstrap(opts) {
|
|
4231
|
+
const ctx = getContext();
|
|
4232
|
+
const { snapshot, initialPage } = await collectStaticCatalog({
|
|
4233
|
+
websiteId: ctx.websiteId,
|
|
4234
|
+
snapshot: opts,
|
|
4235
|
+
bootstrap: {
|
|
4236
|
+
initialPageSize: opts.initialPageSize,
|
|
4237
|
+
sortBy: opts.sortBy
|
|
3380
4238
|
},
|
|
3381
|
-
|
|
3382
|
-
);
|
|
3383
|
-
if (!
|
|
4239
|
+
loadPage: (options) => loadCatalogPage(options, true)
|
|
4240
|
+
});
|
|
4241
|
+
if (!initialPage) {
|
|
3384
4242
|
throw new WebsiteError(
|
|
3385
|
-
|
|
3386
|
-
"
|
|
4243
|
+
"Static catalog bootstrap could not resolve its initial page.",
|
|
4244
|
+
"STATIC_CATALOG_BOOTSTRAP_MISSING_INITIAL_PAGE"
|
|
3387
4245
|
);
|
|
3388
4246
|
}
|
|
3389
|
-
const products = parseActionJson(data.result.products, []);
|
|
3390
|
-
const categoryList = parseActionJson(
|
|
3391
|
-
data.result.category_list,
|
|
3392
|
-
null
|
|
3393
|
-
);
|
|
3394
|
-
const attributeList = parseActionJson(
|
|
3395
|
-
data.result.attribute_list,
|
|
3396
|
-
null
|
|
3397
|
-
);
|
|
3398
|
-
const filtersResult = parseActionJson(
|
|
3399
|
-
data.result.filters,
|
|
3400
|
-
null
|
|
3401
|
-
);
|
|
3402
|
-
const brands = parseActionJson(data.result.brands, []);
|
|
3403
|
-
const pagination = parseActionJson(
|
|
3404
|
-
data.result.pagination,
|
|
3405
|
-
{ limit: opts.limit ?? 24, offset: opts.offset ?? 0, total: 0, hasMore: false }
|
|
3406
|
-
);
|
|
3407
|
-
const appliedFilters = parseActionJson(
|
|
3408
|
-
data.result.applied_filters,
|
|
3409
|
-
{}
|
|
3410
|
-
);
|
|
3411
|
-
const debug = parseActionJson(
|
|
3412
|
-
data.result.debug,
|
|
3413
|
-
null
|
|
3414
|
-
);
|
|
3415
|
-
const labels = buildPublicAttributeLabels(
|
|
3416
|
-
attributeList ? [attributeList] : []
|
|
3417
|
-
);
|
|
3418
4247
|
return {
|
|
3419
|
-
|
|
3420
|
-
|
|
3421
|
-
|
|
3422
|
-
categories: categoryList ? mapCategoryList(categoryList) : null,
|
|
3423
|
-
attributes: attributeList ? mapAttributeList(attributeList) : null,
|
|
3424
|
-
filters: filtersResult,
|
|
3425
|
-
brands,
|
|
3426
|
-
pagination,
|
|
3427
|
-
appliedFilters,
|
|
3428
|
-
debug
|
|
4248
|
+
...snapshot,
|
|
4249
|
+
bootstrapSchemaVersion: 1,
|
|
4250
|
+
initialPage
|
|
3429
4251
|
};
|
|
3430
4252
|
},
|
|
3431
4253
|
async getProducts(opts) {
|
|
@@ -3892,6 +4714,7 @@ function mapCouponResult(raw) {
|
|
|
3892
4714
|
};
|
|
3893
4715
|
}
|
|
3894
4716
|
function createCheckoutModule(graphqlUrl, getHeaders, getContext, cartSessionStorage) {
|
|
4717
|
+
let shippingPolicyCache = null;
|
|
3895
4718
|
return {
|
|
3896
4719
|
async getLocationOptions() {
|
|
3897
4720
|
const context = getContext();
|
|
@@ -3989,22 +4812,39 @@ function createCheckoutModule(graphqlUrl, getHeaders, getContext, cartSessionSto
|
|
|
3989
4812
|
}));
|
|
3990
4813
|
},
|
|
3991
4814
|
async getShippingPolicy() {
|
|
4815
|
+
const now = Date.now();
|
|
4816
|
+
if (shippingPolicyCache && shippingPolicyCache.expiresAt > now) {
|
|
4817
|
+
return shippingPolicyCache.request;
|
|
4818
|
+
}
|
|
3992
4819
|
const context = getContext();
|
|
3993
|
-
const
|
|
4820
|
+
const request = executeGraphQL(
|
|
3994
4821
|
graphqlUrl,
|
|
3995
4822
|
GET_SHIPPING_POLICY,
|
|
3996
4823
|
{ websiteId: context.websiteId },
|
|
3997
4824
|
getHeaders
|
|
3998
|
-
)
|
|
3999
|
-
|
|
4000
|
-
|
|
4001
|
-
|
|
4002
|
-
|
|
4003
|
-
|
|
4004
|
-
|
|
4005
|
-
|
|
4006
|
-
|
|
4825
|
+
).then((data) => {
|
|
4826
|
+
const result = data.websiteGetShippingPolicy;
|
|
4827
|
+
if (!result.success || !result.policy) {
|
|
4828
|
+
throw new CheckoutError(result.errors ?? ["SHIPPING_POLICY_FAILED"]);
|
|
4829
|
+
}
|
|
4830
|
+
return {
|
|
4831
|
+
freeShippingEnabled: result.policy.free_shipping_enabled,
|
|
4832
|
+
freeShippingThreshold: result.policy.free_shipping_threshold,
|
|
4833
|
+
currency: result.policy.currency
|
|
4834
|
+
};
|
|
4835
|
+
});
|
|
4836
|
+
shippingPolicyCache = {
|
|
4837
|
+
expiresAt: now + 5 * 60 * 1e3,
|
|
4838
|
+
request
|
|
4007
4839
|
};
|
|
4840
|
+
try {
|
|
4841
|
+
return await request;
|
|
4842
|
+
} catch (error) {
|
|
4843
|
+
if (shippingPolicyCache?.request === request) {
|
|
4844
|
+
shippingPolicyCache = null;
|
|
4845
|
+
}
|
|
4846
|
+
throw error;
|
|
4847
|
+
}
|
|
4008
4848
|
},
|
|
4009
4849
|
async getFreeShippingProgress(input) {
|
|
4010
4850
|
if (input.items.length === 0) {
|
|
@@ -4170,6 +5010,13 @@ function createCheckoutModule(graphqlUrl, getHeaders, getContext, cartSessionSto
|
|
|
4170
5010
|
initialPayment: input.initialPayment ? {
|
|
4171
5011
|
amount: input.initialPayment.amount,
|
|
4172
5012
|
payment_method_id: input.initialPayment.paymentMethodId
|
|
5013
|
+
} : void 0,
|
|
5014
|
+
marketingAttribution: input.marketingAttribution ? serializeWebsiteMarketingAttribution(input.marketingAttribution) : void 0,
|
|
5015
|
+
advertisingConsent: input.advertisingConsent ? {
|
|
5016
|
+
policy_id: input.advertisingConsent.policyId,
|
|
5017
|
+
policy_version: input.advertisingConsent.policyVersion,
|
|
5018
|
+
statement_hash: input.advertisingConsent.statementHash,
|
|
5019
|
+
granted: input.advertisingConsent.granted
|
|
4173
5020
|
} : void 0
|
|
4174
5021
|
},
|
|
4175
5022
|
getHeaders
|
|
@@ -4305,18 +5152,23 @@ function readOptionalText(value) {
|
|
|
4305
5152
|
function enrichMediaValue(value, storageUrl) {
|
|
4306
5153
|
const fileId = typeof value.fileId === "string" && value.fileId.trim() ? value.fileId.trim() : "";
|
|
4307
5154
|
if (!fileId) return value;
|
|
4308
|
-
const
|
|
5155
|
+
const isProductMedia = typeof value.id === "string" && typeof value.order === "number";
|
|
5156
|
+
const isEditorialImage = value.mediaKind === "image" && !isProductMedia;
|
|
5157
|
+
const originalWidth = typeof value.originalWidth === "number" ? value.originalWidth : null;
|
|
5158
|
+
const urls = isEditorialImage ? buildStorageContentMediaUrls(storageUrl, fileId, originalWidth) : buildStorageMediaUrls(storageUrl, fileId);
|
|
4309
5159
|
return {
|
|
4310
5160
|
...value,
|
|
4311
5161
|
...urls,
|
|
4312
|
-
url: value.url ?? urls.originalUrl
|
|
5162
|
+
url: value.url ?? urls.originalUrl,
|
|
5163
|
+
src: value.src ?? value.url ?? urls.originalUrl
|
|
4313
5164
|
};
|
|
4314
5165
|
}
|
|
4315
5166
|
function normalizeRichTextNode(value, storageUrl) {
|
|
4316
5167
|
const type = value.type;
|
|
4317
5168
|
const attrs = isRecord(value.attrs) ? value.attrs : null;
|
|
4318
5169
|
if (attrs && (type === "bacanoImage" || type === "bacanoVideo") && typeof attrs.fileId === "string") {
|
|
4319
|
-
const
|
|
5170
|
+
const originalWidth = typeof attrs.originalWidth === "number" ? attrs.originalWidth : null;
|
|
5171
|
+
const urls = type === "bacanoImage" ? buildStorageContentMediaUrls(storageUrl, attrs.fileId, originalWidth) : buildStorageMediaUrls(storageUrl, attrs.fileId);
|
|
4320
5172
|
const altText = readOptionalText(attrs.altText) ?? readOptionalText(attrs.alt);
|
|
4321
5173
|
const caption = readOptionalText(attrs.caption);
|
|
4322
5174
|
const showCaption = Boolean(caption) && attrs.showCaption !== false;
|
|
@@ -4448,6 +5300,452 @@ function createContentModule(graphqlUrl, getHeaders, getContext, storageUrl) {
|
|
|
4448
5300
|
};
|
|
4449
5301
|
}
|
|
4450
5302
|
|
|
5303
|
+
// libs/sdk/src/marketing/clarity.ts
|
|
5304
|
+
var initializedDestinations = /* @__PURE__ */ new Set();
|
|
5305
|
+
function clarityWindow() {
|
|
5306
|
+
return typeof window === "undefined" ? null : window;
|
|
5307
|
+
}
|
|
5308
|
+
function prepareClarity(projectId) {
|
|
5309
|
+
const target = clarityWindow();
|
|
5310
|
+
if (!target || initializedDestinations.has(projectId)) return;
|
|
5311
|
+
if (!target.clarity) {
|
|
5312
|
+
const clarity = function() {
|
|
5313
|
+
clarity.q ??= [];
|
|
5314
|
+
clarity.q.push(arguments);
|
|
5315
|
+
};
|
|
5316
|
+
target.clarity = clarity;
|
|
5317
|
+
}
|
|
5318
|
+
initializedDestinations.add(projectId);
|
|
5319
|
+
}
|
|
5320
|
+
function loadClarityScript(projectId) {
|
|
5321
|
+
if (typeof document === "undefined") return;
|
|
5322
|
+
const id = `bacano-clarity-${projectId}`;
|
|
5323
|
+
if (document.getElementById(id)) return;
|
|
5324
|
+
const script = document.createElement("script");
|
|
5325
|
+
script.id = id;
|
|
5326
|
+
script.async = true;
|
|
5327
|
+
script.src = `https://www.clarity.ms/tag/${encodeURIComponent(projectId)}`;
|
|
5328
|
+
script.referrerPolicy = "strict-origin-when-cross-origin";
|
|
5329
|
+
document.head.appendChild(script);
|
|
5330
|
+
}
|
|
5331
|
+
function sendClarityEvent(eventName) {
|
|
5332
|
+
clarityWindow()?.clarity?.("event", eventName);
|
|
5333
|
+
}
|
|
5334
|
+
|
|
5335
|
+
// libs/sdk/src/marketing/domain.ts
|
|
5336
|
+
function matchesCanonicalMarketingHost(canonicalDomain, currentHostname) {
|
|
5337
|
+
if (canonicalDomain === void 0) return true;
|
|
5338
|
+
if (!canonicalDomain?.trim()) return false;
|
|
5339
|
+
try {
|
|
5340
|
+
const configuredHost = new URL(
|
|
5341
|
+
canonicalDomain.includes("://") ? canonicalDomain : `https://${canonicalDomain}`
|
|
5342
|
+
).hostname.toLowerCase();
|
|
5343
|
+
return currentHostname.toLowerCase() === configuredHost;
|
|
5344
|
+
} catch {
|
|
5345
|
+
return false;
|
|
5346
|
+
}
|
|
5347
|
+
}
|
|
5348
|
+
|
|
5349
|
+
// libs/sdk/src/marketing/ga4.ts
|
|
5350
|
+
function mapGa4Items(items) {
|
|
5351
|
+
return items?.map((item) => ({
|
|
5352
|
+
item_id: item.itemId,
|
|
5353
|
+
item_name: item.itemName,
|
|
5354
|
+
item_variant: item.itemVariant,
|
|
5355
|
+
item_brand: item.itemBrand,
|
|
5356
|
+
item_category: item.itemCategory,
|
|
5357
|
+
price: item.price,
|
|
5358
|
+
quantity: item.quantity,
|
|
5359
|
+
index: item.index,
|
|
5360
|
+
sku: item.sku
|
|
5361
|
+
}));
|
|
5362
|
+
}
|
|
5363
|
+
|
|
5364
|
+
// libs/sdk/src/marketing/types.ts
|
|
5365
|
+
var MARKETING_SCHEMA_VERSION = 1;
|
|
5366
|
+
var WEBSITE_MARKETING_CONTACT_PLACEMENTS = [
|
|
5367
|
+
"product_detail",
|
|
5368
|
+
"floating",
|
|
5369
|
+
"header",
|
|
5370
|
+
"footer",
|
|
5371
|
+
"contact_page"
|
|
5372
|
+
];
|
|
5373
|
+
|
|
5374
|
+
// libs/sdk/src/marketing/runtime.ts
|
|
5375
|
+
var initializedGa4Destinations = /* @__PURE__ */ new Set();
|
|
5376
|
+
var initializedMetaDestinations = /* @__PURE__ */ new Set();
|
|
5377
|
+
function marketingWindow() {
|
|
5378
|
+
return typeof window === "undefined" ? null : window;
|
|
5379
|
+
}
|
|
5380
|
+
function loadScriptOnce(id, source) {
|
|
5381
|
+
if (typeof document === "undefined" || document.getElementById(id)) return;
|
|
5382
|
+
const script = document.createElement("script");
|
|
5383
|
+
script.id = id;
|
|
5384
|
+
script.async = true;
|
|
5385
|
+
script.src = source;
|
|
5386
|
+
script.referrerPolicy = "strict-origin-when-cross-origin";
|
|
5387
|
+
document.head.appendChild(script);
|
|
5388
|
+
}
|
|
5389
|
+
function prepareGa4(measurementId) {
|
|
5390
|
+
const target = marketingWindow();
|
|
5391
|
+
if (!target) return;
|
|
5392
|
+
if (initializedGa4Destinations.has(measurementId)) return;
|
|
5393
|
+
target.dataLayer ??= [];
|
|
5394
|
+
target.gtag ??= function() {
|
|
5395
|
+
target.dataLayer?.push(arguments);
|
|
5396
|
+
};
|
|
5397
|
+
target.gtag("js", /* @__PURE__ */ new Date());
|
|
5398
|
+
target.gtag("config", measurementId, { send_page_view: false });
|
|
5399
|
+
initializedGa4Destinations.add(measurementId);
|
|
5400
|
+
}
|
|
5401
|
+
function loadGa4Script(measurementId) {
|
|
5402
|
+
loadScriptOnce(
|
|
5403
|
+
`bacano-ga4-${measurementId}`,
|
|
5404
|
+
`https://www.googletagmanager.com/gtag/js?id=${encodeURIComponent(measurementId)}`
|
|
5405
|
+
);
|
|
5406
|
+
}
|
|
5407
|
+
function captureGa4ClientId(measurementId, websiteId, diagnostic) {
|
|
5408
|
+
const target = marketingWindow();
|
|
5409
|
+
target?.gtag?.("get", measurementId, "client_id", (clientId) => {
|
|
5410
|
+
if (storeWebsiteMarketingGa4ClientId(websiteId, clientId)) return;
|
|
5411
|
+
diagnostic?.({
|
|
5412
|
+
level: "warning",
|
|
5413
|
+
code: "GA4_CLIENT_ID_UNAVAILABLE",
|
|
5414
|
+
message: "GA4 did not provide a valid browser client identifier"
|
|
5415
|
+
});
|
|
5416
|
+
});
|
|
5417
|
+
}
|
|
5418
|
+
function captureGa4SessionId(measurementId, websiteId, diagnostic) {
|
|
5419
|
+
const target = marketingWindow();
|
|
5420
|
+
target?.gtag?.("get", measurementId, "session_id", (sessionId) => {
|
|
5421
|
+
if (storeWebsiteMarketingGa4SessionId(websiteId, sessionId)) return;
|
|
5422
|
+
diagnostic?.({
|
|
5423
|
+
level: "warning",
|
|
5424
|
+
code: "GA4_SESSION_ID_UNAVAILABLE",
|
|
5425
|
+
message: "GA4 did not provide a valid browser session identifier"
|
|
5426
|
+
});
|
|
5427
|
+
});
|
|
5428
|
+
}
|
|
5429
|
+
function prepareMeta(pixelId) {
|
|
5430
|
+
const target = marketingWindow();
|
|
5431
|
+
if (!target) return;
|
|
5432
|
+
if (initializedMetaDestinations.has(pixelId)) return;
|
|
5433
|
+
if (!target.fbq) {
|
|
5434
|
+
const queue = [];
|
|
5435
|
+
const fbq = ((...args) => {
|
|
5436
|
+
if (fbq.callMethod) {
|
|
5437
|
+
fbq.callMethod(...args);
|
|
5438
|
+
return;
|
|
5439
|
+
}
|
|
5440
|
+
queue.push(args);
|
|
5441
|
+
});
|
|
5442
|
+
fbq.push = fbq;
|
|
5443
|
+
fbq.loaded = true;
|
|
5444
|
+
fbq.version = "2.0";
|
|
5445
|
+
fbq.queue = queue;
|
|
5446
|
+
target.fbq = fbq;
|
|
5447
|
+
target._fbq = fbq;
|
|
5448
|
+
}
|
|
5449
|
+
target.fbq("init", pixelId);
|
|
5450
|
+
initializedMetaDestinations.add(pixelId);
|
|
5451
|
+
}
|
|
5452
|
+
function loadMetaScript() {
|
|
5453
|
+
loadScriptOnce(
|
|
5454
|
+
"bacano-meta-pixel",
|
|
5455
|
+
"https://connect.facebook.net/en_US/fbevents.js"
|
|
5456
|
+
);
|
|
5457
|
+
}
|
|
5458
|
+
function scheduleScriptLoad(load, strategy) {
|
|
5459
|
+
const target = marketingWindow();
|
|
5460
|
+
if (!target || strategy !== "idle") {
|
|
5461
|
+
load();
|
|
5462
|
+
return () => void 0;
|
|
5463
|
+
}
|
|
5464
|
+
let completed = false;
|
|
5465
|
+
let idleHandle = null;
|
|
5466
|
+
let timeoutHandle = null;
|
|
5467
|
+
const interactionEvents = ["pointerdown", "keydown", "touchstart"];
|
|
5468
|
+
const cleanup = () => {
|
|
5469
|
+
for (const eventName of interactionEvents) {
|
|
5470
|
+
target.removeEventListener(eventName, finish, true);
|
|
5471
|
+
}
|
|
5472
|
+
if (idleHandle !== null) target.cancelIdleCallback?.(idleHandle);
|
|
5473
|
+
if (timeoutHandle !== null) target.clearTimeout(timeoutHandle);
|
|
5474
|
+
};
|
|
5475
|
+
const finish = () => {
|
|
5476
|
+
if (completed) return;
|
|
5477
|
+
completed = true;
|
|
5478
|
+
cleanup();
|
|
5479
|
+
load();
|
|
5480
|
+
};
|
|
5481
|
+
for (const eventName of interactionEvents) {
|
|
5482
|
+
target.addEventListener(eventName, finish, {
|
|
5483
|
+
capture: true,
|
|
5484
|
+
once: true,
|
|
5485
|
+
passive: true
|
|
5486
|
+
});
|
|
5487
|
+
}
|
|
5488
|
+
if (target.requestIdleCallback) {
|
|
5489
|
+
idleHandle = target.requestIdleCallback(finish, { timeout: 2e3 });
|
|
5490
|
+
} else {
|
|
5491
|
+
timeoutHandle = target.setTimeout(finish, 1500);
|
|
5492
|
+
}
|
|
5493
|
+
return cleanup;
|
|
5494
|
+
}
|
|
5495
|
+
function metaEventName(eventName) {
|
|
5496
|
+
const standard = {
|
|
5497
|
+
page_view: "PageView",
|
|
5498
|
+
search: "Search",
|
|
5499
|
+
view_item: "ViewContent",
|
|
5500
|
+
add_to_cart: "AddToCart",
|
|
5501
|
+
begin_checkout: "InitiateCheckout",
|
|
5502
|
+
add_payment_info: "AddPaymentInfo",
|
|
5503
|
+
contact: "Contact"
|
|
5504
|
+
};
|
|
5505
|
+
return standard[eventName] ?? eventName;
|
|
5506
|
+
}
|
|
5507
|
+
function isWebsiteMarketingContactPlacement(value) {
|
|
5508
|
+
return WEBSITE_MARKETING_CONTACT_PLACEMENTS.some(
|
|
5509
|
+
(candidate) => candidate === value
|
|
5510
|
+
);
|
|
5511
|
+
}
|
|
5512
|
+
function contactPlacementFor(event) {
|
|
5513
|
+
const placement = event.contactPlacement;
|
|
5514
|
+
if (event.eventName !== "contact" || !isWebsiteMarketingContactPlacement(placement)) {
|
|
5515
|
+
return void 0;
|
|
5516
|
+
}
|
|
5517
|
+
return placement;
|
|
5518
|
+
}
|
|
5519
|
+
function sendGa4(event, diagnosticMode) {
|
|
5520
|
+
const target = marketingWindow();
|
|
5521
|
+
const contactPlacement = contactPlacementFor(event);
|
|
5522
|
+
target?.gtag?.("event", event.eventName, {
|
|
5523
|
+
currency: event.currency,
|
|
5524
|
+
value: event.value,
|
|
5525
|
+
search_term: event.searchTerm,
|
|
5526
|
+
item_list_id: event.itemListId,
|
|
5527
|
+
item_list_name: event.itemListName,
|
|
5528
|
+
shipping_tier: event.shippingTier,
|
|
5529
|
+
payment_type: event.paymentType,
|
|
5530
|
+
method: event.contactMethod,
|
|
5531
|
+
...contactPlacement ? { contact_placement: contactPlacement } : void 0,
|
|
5532
|
+
page_location: event.pageLocation,
|
|
5533
|
+
page_title: event.pageTitle,
|
|
5534
|
+
items: mapGa4Items(event.items),
|
|
5535
|
+
event_id: event.eventId,
|
|
5536
|
+
debug_mode: diagnosticMode ? true : void 0
|
|
5537
|
+
});
|
|
5538
|
+
}
|
|
5539
|
+
function sendMeta(event) {
|
|
5540
|
+
const target = marketingWindow();
|
|
5541
|
+
const eventName = metaEventName(event.eventName);
|
|
5542
|
+
const isStandard = eventName !== event.eventName;
|
|
5543
|
+
const contactPlacement = contactPlacementFor(event);
|
|
5544
|
+
target?.fbq?.(
|
|
5545
|
+
isStandard ? "track" : "trackCustom",
|
|
5546
|
+
eventName,
|
|
5547
|
+
{
|
|
5548
|
+
currency: event.currency,
|
|
5549
|
+
value: event.value,
|
|
5550
|
+
search_string: event.searchTerm,
|
|
5551
|
+
content_ids: event.items?.map((item) => item.itemId),
|
|
5552
|
+
contents: event.items?.map((item) => ({
|
|
5553
|
+
id: item.itemId,
|
|
5554
|
+
quantity: item.quantity ?? 1,
|
|
5555
|
+
item_price: item.price
|
|
5556
|
+
})),
|
|
5557
|
+
content_type: event.items?.length ? "product" : void 0,
|
|
5558
|
+
contact_method: event.contactMethod,
|
|
5559
|
+
...contactPlacement ? { contact_placement: contactPlacement } : void 0
|
|
5560
|
+
},
|
|
5561
|
+
{ eventID: event.eventId }
|
|
5562
|
+
);
|
|
5563
|
+
}
|
|
5564
|
+
function newEventId() {
|
|
5565
|
+
return typeof crypto !== "undefined" && "randomUUID" in crypto ? crypto.randomUUID() : `evt_${Date.now()}_${Math.random().toString(16).slice(2)}`;
|
|
5566
|
+
}
|
|
5567
|
+
function createWebsiteMarketingRuntime(options) {
|
|
5568
|
+
let destroyed = false;
|
|
5569
|
+
let cancelScheduledLoad = () => void 0;
|
|
5570
|
+
const target = marketingWindow();
|
|
5571
|
+
const enabled = options.config.enabled && Boolean(
|
|
5572
|
+
target && matchesCanonicalMarketingHost(
|
|
5573
|
+
options.canonicalDomain,
|
|
5574
|
+
target.location.hostname
|
|
5575
|
+
)
|
|
5576
|
+
) && Boolean(
|
|
5577
|
+
options.config.destinations.ga4 || options.config.destinations.meta || options.config.destinations.clarity
|
|
5578
|
+
);
|
|
5579
|
+
if (enabled) {
|
|
5580
|
+
try {
|
|
5581
|
+
if (options.config.destinations.ga4) {
|
|
5582
|
+
prepareGa4(options.config.destinations.ga4.measurementId);
|
|
5583
|
+
}
|
|
5584
|
+
if (options.config.destinations.meta) {
|
|
5585
|
+
prepareMeta(options.config.destinations.meta.pixelId);
|
|
5586
|
+
}
|
|
5587
|
+
if (options.config.destinations.clarity) {
|
|
5588
|
+
prepareClarity(options.config.destinations.clarity.projectId);
|
|
5589
|
+
}
|
|
5590
|
+
cancelScheduledLoad = scheduleScriptLoad(() => {
|
|
5591
|
+
if (destroyed) return;
|
|
5592
|
+
if (options.config.destinations.ga4) {
|
|
5593
|
+
loadGa4Script(options.config.destinations.ga4.measurementId);
|
|
5594
|
+
}
|
|
5595
|
+
if (options.config.destinations.meta) loadMetaScript();
|
|
5596
|
+
if (options.config.destinations.clarity) {
|
|
5597
|
+
loadClarityScript(options.config.destinations.clarity.projectId);
|
|
5598
|
+
}
|
|
5599
|
+
}, options.scriptLoadStrategy);
|
|
5600
|
+
captureWebsiteMarketingAttribution(
|
|
5601
|
+
options.websiteId,
|
|
5602
|
+
options.config.attributionWindowDays
|
|
5603
|
+
);
|
|
5604
|
+
if (options.config.destinations.ga4) {
|
|
5605
|
+
captureGa4ClientId(
|
|
5606
|
+
options.config.destinations.ga4.measurementId,
|
|
5607
|
+
options.websiteId,
|
|
5608
|
+
options.diagnostic
|
|
5609
|
+
);
|
|
5610
|
+
captureGa4SessionId(
|
|
5611
|
+
options.config.destinations.ga4.measurementId,
|
|
5612
|
+
options.websiteId,
|
|
5613
|
+
options.diagnostic
|
|
5614
|
+
);
|
|
5615
|
+
}
|
|
5616
|
+
if (options.config.destinations.meta) {
|
|
5617
|
+
refreshWebsiteMarketingMetaContext(options.websiteId);
|
|
5618
|
+
}
|
|
5619
|
+
} catch (error) {
|
|
5620
|
+
options.diagnostic?.({
|
|
5621
|
+
level: "warning",
|
|
5622
|
+
code: "RUNTIME_INITIALIZATION_FAILED",
|
|
5623
|
+
message: error instanceof Error ? error.message : "Initialization failed"
|
|
5624
|
+
});
|
|
5625
|
+
}
|
|
5626
|
+
}
|
|
5627
|
+
return {
|
|
5628
|
+
enabled,
|
|
5629
|
+
track(input) {
|
|
5630
|
+
if (!enabled || destroyed) return;
|
|
5631
|
+
const event = {
|
|
5632
|
+
...input,
|
|
5633
|
+
schemaVersion: 1,
|
|
5634
|
+
eventId: input.eventId ?? newEventId(),
|
|
5635
|
+
occurredAt: input.occurredAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
5636
|
+
currency: input.currency ?? options.config.currencyCode,
|
|
5637
|
+
pageLocation: sanitizeMarketingUrl(
|
|
5638
|
+
input.pageLocation ?? (typeof window === "undefined" ? "" : window.location.href)
|
|
5639
|
+
)
|
|
5640
|
+
};
|
|
5641
|
+
try {
|
|
5642
|
+
if (options.config.destinations.ga4) {
|
|
5643
|
+
sendGa4(event, options.config.destinations.ga4.diagnosticMode);
|
|
5644
|
+
}
|
|
5645
|
+
if (options.config.destinations.meta) sendMeta(event);
|
|
5646
|
+
if (options.config.destinations.clarity) {
|
|
5647
|
+
sendClarityEvent(event.eventName);
|
|
5648
|
+
}
|
|
5649
|
+
options.diagnostic?.({
|
|
5650
|
+
level: "debug",
|
|
5651
|
+
code: "EVENT_DISPATCHED",
|
|
5652
|
+
message: "Marketing event dispatched",
|
|
5653
|
+
eventName: event.eventName
|
|
5654
|
+
});
|
|
5655
|
+
} catch (error) {
|
|
5656
|
+
options.diagnostic?.({
|
|
5657
|
+
level: "warning",
|
|
5658
|
+
code: "EVENT_DISPATCH_FAILED",
|
|
5659
|
+
message: error instanceof Error ? error.message : "Dispatch failed",
|
|
5660
|
+
eventName: event.eventName
|
|
5661
|
+
});
|
|
5662
|
+
}
|
|
5663
|
+
},
|
|
5664
|
+
captureAttribution() {
|
|
5665
|
+
if (!enabled) return null;
|
|
5666
|
+
captureWebsiteMarketingAttribution(
|
|
5667
|
+
options.websiteId,
|
|
5668
|
+
options.config.attributionWindowDays
|
|
5669
|
+
);
|
|
5670
|
+
if (options.config.destinations.meta) {
|
|
5671
|
+
refreshWebsiteMarketingMetaContext(options.websiteId);
|
|
5672
|
+
}
|
|
5673
|
+
return getWebsiteMarketingAttribution(options.websiteId);
|
|
5674
|
+
},
|
|
5675
|
+
getAttribution() {
|
|
5676
|
+
if (!enabled) return null;
|
|
5677
|
+
if (options.config.destinations.meta) {
|
|
5678
|
+
refreshWebsiteMarketingMetaContext(options.websiteId);
|
|
5679
|
+
}
|
|
5680
|
+
return getWebsiteMarketingAttribution(options.websiteId);
|
|
5681
|
+
},
|
|
5682
|
+
destroy() {
|
|
5683
|
+
destroyed = true;
|
|
5684
|
+
cancelScheduledLoad();
|
|
5685
|
+
}
|
|
5686
|
+
};
|
|
5687
|
+
}
|
|
5688
|
+
|
|
5689
|
+
// libs/sdk/src/modules/marketing.ts
|
|
5690
|
+
function readRetryAfter(response) {
|
|
5691
|
+
const value = Number(response.headers.get("Retry-After") ?? "1");
|
|
5692
|
+
return Number.isFinite(value) && value >= 0 ? Math.ceil(value) : 1;
|
|
5693
|
+
}
|
|
5694
|
+
function createMarketingModule(apiUrl, websiteSlug, getContext) {
|
|
5695
|
+
return {
|
|
5696
|
+
async captureLead(input) {
|
|
5697
|
+
const context = getContext();
|
|
5698
|
+
const attribution = getWebsiteMarketingAttribution(context.websiteId);
|
|
5699
|
+
let response;
|
|
5700
|
+
try {
|
|
5701
|
+
response = await fetch(
|
|
5702
|
+
`${apiUrl.replace(/\/+$/, "")}/api/v1/website/campaign-captures`,
|
|
5703
|
+
{
|
|
5704
|
+
method: "POST",
|
|
5705
|
+
headers: { "Content-Type": "application/json" },
|
|
5706
|
+
body: JSON.stringify({
|
|
5707
|
+
websiteSlug,
|
|
5708
|
+
campaignKey: input.campaignKey,
|
|
5709
|
+
idempotencyKey: input.idempotencyKey,
|
|
5710
|
+
source: input.source ?? "WEBSITE",
|
|
5711
|
+
commercialConsent: input.commercialConsent,
|
|
5712
|
+
contact: input.contact,
|
|
5713
|
+
answers: input.answers ?? {},
|
|
5714
|
+
attribution: attribution ? serializeWebsiteMarketingAttribution(attribution) : null
|
|
5715
|
+
})
|
|
5716
|
+
}
|
|
5717
|
+
);
|
|
5718
|
+
} catch (error) {
|
|
5719
|
+
throw new WebsiteError(
|
|
5720
|
+
`Could not capture the campaign lead: ${error instanceof Error ? error.message : String(error)}`,
|
|
5721
|
+
"NETWORK_ERROR"
|
|
5722
|
+
);
|
|
5723
|
+
}
|
|
5724
|
+
if (response.status === 429) {
|
|
5725
|
+
throw new RateLimitError(
|
|
5726
|
+
"Rate limit exceeded. Please try again later.",
|
|
5727
|
+
readRetryAfter(response)
|
|
5728
|
+
);
|
|
5729
|
+
}
|
|
5730
|
+
if (!response.ok) {
|
|
5731
|
+
throw new WebsiteError(
|
|
5732
|
+
`Could not capture the campaign lead (status ${response.status})`,
|
|
5733
|
+
response.status === 403 ? "ORIGIN_NOT_ALLOWED" : "INVALID_RESPONSE",
|
|
5734
|
+
{ status: response.status }
|
|
5735
|
+
);
|
|
5736
|
+
}
|
|
5737
|
+
const result = await response.json();
|
|
5738
|
+
if (typeof result.accepted !== "boolean" || typeof result.message !== "string") {
|
|
5739
|
+
throw new WebsiteError(
|
|
5740
|
+
"Campaign capture returned an invalid response",
|
|
5741
|
+
"INVALID_RESPONSE"
|
|
5742
|
+
);
|
|
5743
|
+
}
|
|
5744
|
+
return { accepted: result.accepted, message: result.message };
|
|
5745
|
+
}
|
|
5746
|
+
};
|
|
5747
|
+
}
|
|
5748
|
+
|
|
4451
5749
|
// libs/sdk/src/modules/orders.ts
|
|
4452
5750
|
function mapOrderItem(raw) {
|
|
4453
5751
|
return {
|
|
@@ -4622,13 +5920,13 @@ function createOrdersModule(graphqlUrl, getHeaders, getContext) {
|
|
|
4622
5920
|
)) {
|
|
4623
5921
|
return order;
|
|
4624
5922
|
}
|
|
4625
|
-
await
|
|
5923
|
+
await wait2(intervalMs);
|
|
4626
5924
|
}
|
|
4627
5925
|
throw new CheckoutError(["PAYMENT_STATUS_TIMEOUT"]);
|
|
4628
5926
|
}
|
|
4629
5927
|
};
|
|
4630
5928
|
}
|
|
4631
|
-
function
|
|
5929
|
+
function wait2(ms) {
|
|
4632
5930
|
return new Promise((resolve) => {
|
|
4633
5931
|
setTimeout(resolve, ms);
|
|
4634
5932
|
});
|
|
@@ -4787,6 +6085,9 @@ function createProfileModule(graphqlUrl, getHeaders) {
|
|
|
4787
6085
|
}
|
|
4788
6086
|
|
|
4789
6087
|
// libs/sdk/src/modules/website.ts
|
|
6088
|
+
var WEBSITE_CONTEXT_CACHE_TTL_MS = 5 * 60 * 1e3;
|
|
6089
|
+
var WEBSITE_CONTEXT_CACHE_MAX_ENTRIES = 100;
|
|
6090
|
+
var websiteContextCache = /* @__PURE__ */ new Map();
|
|
4790
6091
|
function mapWebsite(raw) {
|
|
4791
6092
|
return {
|
|
4792
6093
|
websiteId: raw.id,
|
|
@@ -4800,10 +6101,7 @@ function mapWebsite(raw) {
|
|
|
4800
6101
|
}
|
|
4801
6102
|
async function resolveWebsite(graphqlUrl, slug, headers) {
|
|
4802
6103
|
if (!slug.trim()) {
|
|
4803
|
-
throw new WebsiteError(
|
|
4804
|
-
"websiteSlug is required",
|
|
4805
|
-
"INVALID_CONFIG"
|
|
4806
|
-
);
|
|
6104
|
+
throw new WebsiteError("websiteSlug is required", "INVALID_CONFIG");
|
|
4807
6105
|
}
|
|
4808
6106
|
const data = await executeGraphQL(
|
|
4809
6107
|
graphqlUrl,
|
|
@@ -4819,6 +6117,31 @@ async function resolveWebsite(graphqlUrl, slug, headers) {
|
|
|
4819
6117
|
}
|
|
4820
6118
|
return mapWebsite(data.websites[0]);
|
|
4821
6119
|
}
|
|
6120
|
+
function resolveWebsiteCached(graphqlUrl, slug, headers) {
|
|
6121
|
+
const normalizedSlug = slug.trim();
|
|
6122
|
+
const cacheKey = `${graphqlUrl}::${normalizedSlug}`;
|
|
6123
|
+
const now = Date.now();
|
|
6124
|
+
const cached = websiteContextCache.get(cacheKey);
|
|
6125
|
+
if (cached && cached.expiresAt > now) return cached.request;
|
|
6126
|
+
if (cached) websiteContextCache.delete(cacheKey);
|
|
6127
|
+
const request = resolveWebsite(graphqlUrl, normalizedSlug, headers).catch(
|
|
6128
|
+
(error) => {
|
|
6129
|
+
if (websiteContextCache.get(cacheKey)?.request === request) {
|
|
6130
|
+
websiteContextCache.delete(cacheKey);
|
|
6131
|
+
}
|
|
6132
|
+
throw error;
|
|
6133
|
+
}
|
|
6134
|
+
);
|
|
6135
|
+
if (websiteContextCache.size >= WEBSITE_CONTEXT_CACHE_MAX_ENTRIES) {
|
|
6136
|
+
const oldestKey = websiteContextCache.keys().next().value;
|
|
6137
|
+
if (oldestKey) websiteContextCache.delete(oldestKey);
|
|
6138
|
+
}
|
|
6139
|
+
websiteContextCache.set(cacheKey, {
|
|
6140
|
+
expiresAt: now + WEBSITE_CONTEXT_CACHE_TTL_MS,
|
|
6141
|
+
request
|
|
6142
|
+
});
|
|
6143
|
+
return request;
|
|
6144
|
+
}
|
|
4822
6145
|
|
|
4823
6146
|
// libs/sdk/src/client.ts
|
|
4824
6147
|
function normalizeApiUrl(apiUrl) {
|
|
@@ -4829,10 +6152,17 @@ function createBacanoClient(config) {
|
|
|
4829
6152
|
throw new WebsiteError("apiUrl is required", "INVALID_CONFIG");
|
|
4830
6153
|
if (!config.websiteSlug?.trim())
|
|
4831
6154
|
throw new WebsiteError("websiteSlug is required", "INVALID_CONFIG");
|
|
6155
|
+
if (config.initialContext && config.initialContext.slug.trim() !== config.websiteSlug.trim()) {
|
|
6156
|
+
throw new WebsiteError(
|
|
6157
|
+
"initialContext slug must match websiteSlug",
|
|
6158
|
+
"INVALID_CONFIG"
|
|
6159
|
+
);
|
|
6160
|
+
}
|
|
4832
6161
|
const apiUrl = normalizeApiUrl(config.apiUrl);
|
|
4833
6162
|
const graphqlUrl = `${apiUrl}/api/v1/website/graphql`;
|
|
4834
6163
|
const storageUrl = `${apiUrl}/storage/v1`;
|
|
4835
|
-
let context = null;
|
|
6164
|
+
let context = config.initialContext ? { ...config.initialContext } : null;
|
|
6165
|
+
let initialization = null;
|
|
4836
6166
|
function getContext() {
|
|
4837
6167
|
if (!context) throw new NotInitializedError();
|
|
4838
6168
|
return context;
|
|
@@ -4877,13 +6207,27 @@ function createBacanoClient(config) {
|
|
|
4877
6207
|
getContext,
|
|
4878
6208
|
storageUrl
|
|
4879
6209
|
);
|
|
6210
|
+
const marketingModule = createMarketingModule(
|
|
6211
|
+
apiUrl,
|
|
6212
|
+
config.websiteSlug,
|
|
6213
|
+
getContext
|
|
6214
|
+
);
|
|
4880
6215
|
return {
|
|
4881
6216
|
async init() {
|
|
4882
|
-
context
|
|
4883
|
-
|
|
4884
|
-
|
|
4885
|
-
|
|
4886
|
-
|
|
6217
|
+
if (context) return;
|
|
6218
|
+
if (!initialization) {
|
|
6219
|
+
initialization = resolveWebsiteCached(
|
|
6220
|
+
graphqlUrl,
|
|
6221
|
+
config.websiteSlug,
|
|
6222
|
+
getWebsiteHeaders
|
|
6223
|
+
).then((resolvedContext) => {
|
|
6224
|
+
context = resolvedContext;
|
|
6225
|
+
}).catch((error) => {
|
|
6226
|
+
initialization = null;
|
|
6227
|
+
throw error;
|
|
6228
|
+
});
|
|
6229
|
+
}
|
|
6230
|
+
await initialization;
|
|
4887
6231
|
},
|
|
4888
6232
|
getContext,
|
|
4889
6233
|
auth: {
|
|
@@ -4898,8 +6242,11 @@ function createBacanoClient(config) {
|
|
|
4898
6242
|
},
|
|
4899
6243
|
catalog: {
|
|
4900
6244
|
getCatalogPage: catalogModule.getCatalogPage.bind(catalogModule),
|
|
6245
|
+
getStaticCatalogSnapshot: catalogModule.getStaticCatalogSnapshot.bind(catalogModule),
|
|
6246
|
+
getStaticCatalogBootstrap: catalogModule.getStaticCatalogBootstrap.bind(catalogModule),
|
|
4901
6247
|
getProductSeoEntries: catalogModule.getProductSeoEntries.bind(catalogModule),
|
|
4902
6248
|
getProductRecommendations: catalogModule.getProductRecommendations.bind(catalogModule),
|
|
6249
|
+
getProductLiveState: catalogModule.getProductLiveState.bind(catalogModule),
|
|
4903
6250
|
getProducts: catalogModule.getProducts.bind(catalogModule),
|
|
4904
6251
|
getProduct: catalogModule.getProduct.bind(catalogModule),
|
|
4905
6252
|
getProductBySlug: catalogModule.getProductBySlug.bind(catalogModule),
|
|
@@ -4951,6 +6298,9 @@ function createBacanoClient(config) {
|
|
|
4951
6298
|
get: profileModule.get.bind(profileModule),
|
|
4952
6299
|
getAddresses: profileModule.getAddresses.bind(profileModule),
|
|
4953
6300
|
update: profileModule.update.bind(profileModule)
|
|
6301
|
+
},
|
|
6302
|
+
marketing: {
|
|
6303
|
+
captureLead: marketingModule.captureLead.bind(marketingModule)
|
|
4954
6304
|
}
|
|
4955
6305
|
};
|
|
4956
6306
|
}
|