@bacano/sdk 1.0.1 → 1.1.8
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 +439 -18
- package/STOREFRONT_CLARITY.md +140 -0
- package/STOREFRONT_CONSTRUCTION.md +105 -0
- package/STOREFRONT_LEADS.md +130 -0
- package/STOREFRONT_LEGAL.md +66 -0
- package/STOREFRONT_MARKETING.md +291 -0
- package/STOREFRONT_PERFORMANCE.md +304 -0
- package/STOREFRONT_PREVIEW.md +499 -0
- package/STOREFRONT_PRODUCT_SPECIFICATIONS.md +127 -0
- package/STOREFRONT_VOLUME_PRICING.md +131 -0
- package/client.d.ts +8 -1
- package/errors.d.ts +9 -1
- package/graphql.cjs +266 -3
- package/graphql.d.ts +2 -2
- package/graphql.js +266 -3
- package/index.cjs +2035 -255
- package/index.d.ts +8 -6
- package/index.js +2035 -255
- package/marketing/attribution.d.ts +53 -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 +111 -0
- package/media.d.ts +45 -2
- package/modules/auth.d.ts +3 -2
- package/modules/availability.d.ts +2 -2
- package/modules/cart-errors.d.ts +2 -0
- package/modules/cart-session.d.ts +2 -2
- package/modules/cart.d.ts +4 -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 +24 -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 +3893 -1795
- package/react.d.ts +8 -4
- package/react.js +3891 -1789
- package/skills/bacano-web/SKILL.md +116 -0
- package/skills/bacano-web/agents/openai.yaml +4 -0
- package/skills/bacano-web/references/architecture-runtime.md +109 -0
- package/skills/bacano-web/references/common.md +92 -0
- package/skills/bacano-web/references/informational.md +39 -0
- package/skills/bacano-web/references/report.md +70 -0
- package/skills/bacano-web/references/storefront.md +116 -0
- package/skills/bacano-web/scripts/audit-http.mjs +284 -0
- package/skills/bacano-web/scripts/cli.mjs +222 -0
- package/skills/bacano-web/scripts/postinstall.mjs +8 -0
- package/transport/documents.d.ts +23 -18
- package/transport/graphql.d.ts +13 -1
- package/types.d.ts +338 -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,80 @@ 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 branding = raw.branding ?? {
|
|
225
|
+
schema_version: 1,
|
|
226
|
+
favicon: null
|
|
227
|
+
};
|
|
228
|
+
const legal = raw.legal ?? {
|
|
229
|
+
schema_version: 1,
|
|
230
|
+
version: 0,
|
|
231
|
+
content_hash: null,
|
|
232
|
+
documents: {
|
|
233
|
+
terms: null,
|
|
234
|
+
shipping: null,
|
|
235
|
+
returns: null,
|
|
236
|
+
privacy: null,
|
|
237
|
+
cookies: null
|
|
238
|
+
},
|
|
239
|
+
inquiries_channel: null,
|
|
240
|
+
direct_marketing_consent: null,
|
|
241
|
+
cookie_notice: {
|
|
242
|
+
enabled: false,
|
|
243
|
+
message: "Usamos cookies propias y de terceros para mejorar tu experiencia y medir el funcionamiento del sitio.",
|
|
244
|
+
button_label: "Entendido"
|
|
245
|
+
}
|
|
246
|
+
};
|
|
247
|
+
const mapConstructionImage = (image) => {
|
|
248
|
+
if (!image) return null;
|
|
249
|
+
const urls = buildStorageContentMediaUrls(
|
|
250
|
+
`${apiUrl}/storage/v1`,
|
|
251
|
+
image.file_id,
|
|
252
|
+
image.width
|
|
253
|
+
);
|
|
254
|
+
return {
|
|
255
|
+
fileId: image.file_id,
|
|
256
|
+
name: image.name,
|
|
257
|
+
altText: image.alt_text,
|
|
258
|
+
width: image.width,
|
|
259
|
+
height: image.height,
|
|
260
|
+
originalUrl: urls.originalUrl,
|
|
261
|
+
heroUrl: urls.heroUrl,
|
|
262
|
+
responsiveSources: urls.responsiveSources
|
|
263
|
+
};
|
|
264
|
+
};
|
|
265
|
+
const construction = raw.construction ?? {
|
|
266
|
+
schema_version: 1,
|
|
267
|
+
enabled: false,
|
|
268
|
+
enforced: false,
|
|
269
|
+
preview_access_granted: false,
|
|
270
|
+
title: null,
|
|
271
|
+
message: null,
|
|
272
|
+
desktop_image: null,
|
|
273
|
+
mobile_image: null
|
|
274
|
+
};
|
|
275
|
+
const volumePricing = raw.volume_pricing ?? {
|
|
276
|
+
schema_version: 1,
|
|
277
|
+
enabled: false,
|
|
278
|
+
tiers: []
|
|
279
|
+
};
|
|
96
280
|
return {
|
|
97
281
|
websiteId: raw.website_id,
|
|
98
282
|
companyId: raw.company_id,
|
|
283
|
+
contextSchemaVersion: raw.context_schema_version === 1 ? 1 : null,
|
|
284
|
+
chainId: raw.chain_id ?? null,
|
|
99
285
|
slug: raw.slug,
|
|
100
286
|
name: raw.name,
|
|
101
287
|
domain: raw.domain,
|
|
288
|
+
config: raw.config ?? null,
|
|
102
289
|
buyerAccountsEnabled: raw.buyer_accounts_enabled,
|
|
103
290
|
clerkPublishableKey: raw.clerk_publishable_key,
|
|
104
291
|
customerIdentification: {
|
|
@@ -114,56 +301,89 @@ async function getWebsitePublicConfig(options) {
|
|
|
114
301
|
isDefault: documentType.is_default
|
|
115
302
|
})
|
|
116
303
|
)
|
|
304
|
+
},
|
|
305
|
+
branding: {
|
|
306
|
+
schemaVersion: branding.schema_version,
|
|
307
|
+
favicon: branding.favicon ? {
|
|
308
|
+
fileId: branding.favicon.file_id,
|
|
309
|
+
name: branding.favicon.name,
|
|
310
|
+
altText: branding.favicon.alt_text,
|
|
311
|
+
width: branding.favicon.width,
|
|
312
|
+
height: branding.favicon.height,
|
|
313
|
+
originalUrl: buildStorageFileUrl(
|
|
314
|
+
`${apiUrl}/storage/v1`,
|
|
315
|
+
branding.favicon.file_id
|
|
316
|
+
)
|
|
317
|
+
} : null
|
|
318
|
+
},
|
|
319
|
+
marketing: {
|
|
320
|
+
schemaVersion: marketing.schema_version,
|
|
321
|
+
enabled: marketing.enabled,
|
|
322
|
+
currencyCode: marketing.currency_code,
|
|
323
|
+
timeZone: marketing.time_zone,
|
|
324
|
+
attributionWindowDays: marketing.attribution_window_days,
|
|
325
|
+
consentPolicy: marketing.consent_policy ? {
|
|
326
|
+
id: marketing.consent_policy.id,
|
|
327
|
+
version: marketing.consent_policy.version,
|
|
328
|
+
statement: marketing.consent_policy.statement,
|
|
329
|
+
statementHash: marketing.consent_policy.statement_hash
|
|
330
|
+
} : null,
|
|
331
|
+
destinations: {
|
|
332
|
+
ga4: marketing.destinations.ga4 ? {
|
|
333
|
+
measurementId: marketing.destinations.ga4.measurement_id,
|
|
334
|
+
diagnosticMode: marketing.destinations.ga4.diagnostic_mode ?? false
|
|
335
|
+
} : null,
|
|
336
|
+
meta: marketing.destinations.meta ? {
|
|
337
|
+
pixelId: marketing.destinations.meta.pixel_id,
|
|
338
|
+
diagnosticMode: marketing.destinations.meta.diagnostic_mode ?? false
|
|
339
|
+
} : null,
|
|
340
|
+
clarity: marketing.destinations.clarity ? { projectId: marketing.destinations.clarity.project_id } : null
|
|
341
|
+
}
|
|
342
|
+
},
|
|
343
|
+
legal: {
|
|
344
|
+
schemaVersion: legal.schema_version,
|
|
345
|
+
version: legal.version,
|
|
346
|
+
contentHash: legal.content_hash,
|
|
347
|
+
documents: {
|
|
348
|
+
terms: legal.documents.terms,
|
|
349
|
+
shipping: legal.documents.shipping ?? null,
|
|
350
|
+
returns: legal.documents.returns ?? null,
|
|
351
|
+
privacy: legal.documents.privacy,
|
|
352
|
+
cookies: legal.documents.cookies
|
|
353
|
+
},
|
|
354
|
+
inquiriesChannel: legal.inquiries_channel,
|
|
355
|
+
directMarketingConsent: legal.direct_marketing_consent ? {
|
|
356
|
+
version: legal.direct_marketing_consent.version,
|
|
357
|
+
statement: legal.direct_marketing_consent.statement,
|
|
358
|
+
statementHash: legal.direct_marketing_consent.statement_hash
|
|
359
|
+
} : null,
|
|
360
|
+
cookieNotice: {
|
|
361
|
+
enabled: legal.cookie_notice.enabled,
|
|
362
|
+
message: legal.cookie_notice.message,
|
|
363
|
+
buttonLabel: legal.cookie_notice.button_label
|
|
364
|
+
}
|
|
365
|
+
},
|
|
366
|
+
construction: {
|
|
367
|
+
schemaVersion: construction.schema_version,
|
|
368
|
+
enabled: construction.enabled,
|
|
369
|
+
enforced: construction.enforced,
|
|
370
|
+
previewAccessGranted: construction.preview_access_granted,
|
|
371
|
+
title: construction.title,
|
|
372
|
+
message: construction.message,
|
|
373
|
+
desktopImage: mapConstructionImage(construction.desktop_image),
|
|
374
|
+
mobileImage: mapConstructionImage(construction.mobile_image)
|
|
375
|
+
},
|
|
376
|
+
volumePricing: {
|
|
377
|
+
schemaVersion: volumePricing.schema_version,
|
|
378
|
+
enabled: volumePricing.enabled,
|
|
379
|
+
tiers: volumePricing.tiers.map((tier) => ({
|
|
380
|
+
priceType: tier.price_type,
|
|
381
|
+
minQuantity: tier.min_quantity
|
|
382
|
+
}))
|
|
117
383
|
}
|
|
118
384
|
};
|
|
119
385
|
}
|
|
120
386
|
|
|
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
387
|
// libs/sdk/src/transport/documents.ts
|
|
168
388
|
var RESOLVE_WEBSITE_BY_SLUG = (
|
|
169
389
|
/* GraphQL */
|
|
@@ -307,7 +527,6 @@ var GET_PRODUCTS = (
|
|
|
307
527
|
) {
|
|
308
528
|
id
|
|
309
529
|
related_product_id
|
|
310
|
-
display_label
|
|
311
530
|
sort_order
|
|
312
531
|
related_product {
|
|
313
532
|
id
|
|
@@ -329,6 +548,52 @@ var GET_PRODUCTS = (
|
|
|
329
548
|
}
|
|
330
549
|
}
|
|
331
550
|
}
|
|
551
|
+
product_option_group_members(limit: 1) {
|
|
552
|
+
group {
|
|
553
|
+
id
|
|
554
|
+
display_title
|
|
555
|
+
members(
|
|
556
|
+
where: {
|
|
557
|
+
product: {
|
|
558
|
+
product_website_publications: {
|
|
559
|
+
website_id: { _eq: $websiteId }
|
|
560
|
+
is_visible: { _eq: true }
|
|
561
|
+
}
|
|
562
|
+
chain_products: {
|
|
563
|
+
chain_id: { _eq: $chainId }
|
|
564
|
+
is_enabled: { _eq: true }
|
|
565
|
+
status: { _eq: active }
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
order_by: { sort_order: asc }
|
|
570
|
+
) {
|
|
571
|
+
id
|
|
572
|
+
product_id
|
|
573
|
+
display_label
|
|
574
|
+
sort_order
|
|
575
|
+
product {
|
|
576
|
+
id
|
|
577
|
+
slug
|
|
578
|
+
name
|
|
579
|
+
sku
|
|
580
|
+
product_media(
|
|
581
|
+
where: { file: { mimeType: { _ilike: "image/%" } } }
|
|
582
|
+
order_by: { order: asc }
|
|
583
|
+
limit: 1
|
|
584
|
+
) {
|
|
585
|
+
id
|
|
586
|
+
file_id
|
|
587
|
+
order
|
|
588
|
+
file {
|
|
589
|
+
mimeType
|
|
590
|
+
name
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
}
|
|
332
597
|
product_attributes {
|
|
333
598
|
id
|
|
334
599
|
value
|
|
@@ -413,6 +678,52 @@ var GET_PRODUCT_BY_PK = (
|
|
|
413
678
|
limit: 1
|
|
414
679
|
) {
|
|
415
680
|
id
|
|
681
|
+
product_option_group_members(limit: 1) {
|
|
682
|
+
group {
|
|
683
|
+
id
|
|
684
|
+
display_title
|
|
685
|
+
members(
|
|
686
|
+
where: {
|
|
687
|
+
product: {
|
|
688
|
+
product_website_publications: {
|
|
689
|
+
website_id: { _eq: $websiteId }
|
|
690
|
+
is_visible: { _eq: true }
|
|
691
|
+
}
|
|
692
|
+
chain_products: {
|
|
693
|
+
chain_id: { _eq: $chainId }
|
|
694
|
+
is_enabled: { _eq: true }
|
|
695
|
+
status: { _eq: active }
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
order_by: { sort_order: asc }
|
|
700
|
+
) {
|
|
701
|
+
id
|
|
702
|
+
product_id
|
|
703
|
+
display_label
|
|
704
|
+
sort_order
|
|
705
|
+
product {
|
|
706
|
+
id
|
|
707
|
+
slug
|
|
708
|
+
name
|
|
709
|
+
sku
|
|
710
|
+
product_media(
|
|
711
|
+
where: { file: { mimeType: { _ilike: "image/%" } } }
|
|
712
|
+
order_by: { order: asc }
|
|
713
|
+
limit: 1
|
|
714
|
+
) {
|
|
715
|
+
id
|
|
716
|
+
file_id
|
|
717
|
+
order
|
|
718
|
+
file {
|
|
719
|
+
mimeType
|
|
720
|
+
name
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
}
|
|
416
727
|
name
|
|
417
728
|
description
|
|
418
729
|
long_description
|
|
@@ -499,7 +810,6 @@ var GET_PRODUCT_BY_PK = (
|
|
|
499
810
|
) {
|
|
500
811
|
id
|
|
501
812
|
related_product_id
|
|
502
|
-
display_label
|
|
503
813
|
sort_order
|
|
504
814
|
related_product {
|
|
505
815
|
id
|
|
@@ -873,6 +1183,24 @@ var WEBSITE_GET_PRODUCT_RECOMMENDATIONS = (
|
|
|
873
1183
|
}
|
|
874
1184
|
`
|
|
875
1185
|
);
|
|
1186
|
+
var WEBSITE_GET_PRODUCT_SPECIFICATIONS = (
|
|
1187
|
+
/* GraphQL */
|
|
1188
|
+
`
|
|
1189
|
+
mutation WebsiteGetProductSpecifications(
|
|
1190
|
+
$websiteId: uuid!
|
|
1191
|
+
$productIds: [uuid!]!
|
|
1192
|
+
) {
|
|
1193
|
+
result: websiteGetProductSpecifications(
|
|
1194
|
+
website_id: $websiteId
|
|
1195
|
+
product_ids: $productIds
|
|
1196
|
+
) {
|
|
1197
|
+
success
|
|
1198
|
+
specifications
|
|
1199
|
+
errors
|
|
1200
|
+
}
|
|
1201
|
+
}
|
|
1202
|
+
`
|
|
1203
|
+
);
|
|
876
1204
|
var GET_BRANDS = (
|
|
877
1205
|
/* GraphQL */
|
|
878
1206
|
`
|
|
@@ -981,14 +1309,76 @@ var WEBSITE_GET_PRODUCT_CONTENT = (
|
|
|
981
1309
|
var CHECK_AVAILABILITY = (
|
|
982
1310
|
/* GraphQL */
|
|
983
1311
|
`
|
|
984
|
-
query CheckAvailability(
|
|
1312
|
+
query CheckAvailability(
|
|
1313
|
+
$variantIds: [uuid!]!
|
|
1314
|
+
$chainId: uuid!
|
|
1315
|
+
$websiteId: uuid
|
|
1316
|
+
) {
|
|
1317
|
+
websiteProductAvailability(
|
|
1318
|
+
product_variant_ids: $variantIds
|
|
1319
|
+
chain_id: $chainId
|
|
1320
|
+
website_id: $websiteId
|
|
1321
|
+
) {
|
|
1322
|
+
product_variant_id
|
|
1323
|
+
in_stock
|
|
1324
|
+
label
|
|
1325
|
+
max_purchasable_quantity
|
|
1326
|
+
}
|
|
1327
|
+
}
|
|
1328
|
+
`
|
|
1329
|
+
);
|
|
1330
|
+
var GET_PRODUCT_LIVE_STATE = (
|
|
1331
|
+
/* GraphQL */
|
|
1332
|
+
`
|
|
1333
|
+
query GetProductLiveState(
|
|
1334
|
+
$variantIds: [uuid!]!
|
|
1335
|
+
$companyId: uuid!
|
|
1336
|
+
$chainId: uuid!
|
|
1337
|
+
$websiteId: uuid!
|
|
1338
|
+
) {
|
|
1339
|
+
product_variants(
|
|
1340
|
+
where: {
|
|
1341
|
+
id: { _in: $variantIds }
|
|
1342
|
+
is_disabled: { _eq: false }
|
|
1343
|
+
product: {
|
|
1344
|
+
company_id: { _eq: $companyId }
|
|
1345
|
+
product_website_publications: {
|
|
1346
|
+
website_id: { _eq: $websiteId }
|
|
1347
|
+
is_visible: { _eq: true }
|
|
1348
|
+
}
|
|
1349
|
+
chain_products: {
|
|
1350
|
+
chain_id: { _eq: $chainId }
|
|
1351
|
+
is_enabled: { _eq: true }
|
|
1352
|
+
status: { _eq: active }
|
|
1353
|
+
}
|
|
1354
|
+
}
|
|
1355
|
+
}
|
|
1356
|
+
) {
|
|
1357
|
+
id
|
|
1358
|
+
product_variant_prices(
|
|
1359
|
+
where: {
|
|
1360
|
+
price_type: { _in: [CLIENT, SALE, WHOLESALE, WHOLESALE_2] }
|
|
1361
|
+
_or: [
|
|
1362
|
+
{ chain_id: { _eq: $chainId } }
|
|
1363
|
+
{ chain_id: { _is_null: true } }
|
|
1364
|
+
]
|
|
1365
|
+
}
|
|
1366
|
+
) {
|
|
1367
|
+
id
|
|
1368
|
+
price
|
|
1369
|
+
price_type
|
|
1370
|
+
chain_id
|
|
1371
|
+
}
|
|
1372
|
+
}
|
|
985
1373
|
websiteProductAvailability(
|
|
986
1374
|
product_variant_ids: $variantIds
|
|
987
1375
|
chain_id: $chainId
|
|
1376
|
+
website_id: $websiteId
|
|
988
1377
|
) {
|
|
989
1378
|
product_variant_id
|
|
990
1379
|
in_stock
|
|
991
1380
|
label
|
|
1381
|
+
max_purchasable_quantity
|
|
992
1382
|
}
|
|
993
1383
|
}
|
|
994
1384
|
`
|
|
@@ -1021,10 +1411,28 @@ var CREATE_CART = (
|
|
|
1021
1411
|
total_with_discount
|
|
1022
1412
|
created_at
|
|
1023
1413
|
updated_at
|
|
1414
|
+
volume_pricing {
|
|
1415
|
+
enabled
|
|
1416
|
+
eligibleQuantity
|
|
1417
|
+
appliedPriceType
|
|
1418
|
+
appliedMinQuantity
|
|
1419
|
+
nextPriceType
|
|
1420
|
+
nextMinQuantity
|
|
1421
|
+
remainingQuantity
|
|
1422
|
+
totalSavings
|
|
1423
|
+
}
|
|
1024
1424
|
items {
|
|
1025
1425
|
id
|
|
1026
1426
|
product_variant_id
|
|
1027
1427
|
quantity
|
|
1428
|
+
unit_price
|
|
1429
|
+
line_total
|
|
1430
|
+
base_unit_price
|
|
1431
|
+
unit_savings
|
|
1432
|
+
line_savings
|
|
1433
|
+
volume_pricing_eligible
|
|
1434
|
+
resolved_price_type
|
|
1435
|
+
volume_pricing_min_quantity
|
|
1028
1436
|
product_variant {
|
|
1029
1437
|
id
|
|
1030
1438
|
sku
|
|
@@ -1064,10 +1472,28 @@ var GET_CART = (
|
|
|
1064
1472
|
total_with_discount
|
|
1065
1473
|
created_at
|
|
1066
1474
|
updated_at
|
|
1475
|
+
volume_pricing {
|
|
1476
|
+
enabled
|
|
1477
|
+
eligibleQuantity
|
|
1478
|
+
appliedPriceType
|
|
1479
|
+
appliedMinQuantity
|
|
1480
|
+
nextPriceType
|
|
1481
|
+
nextMinQuantity
|
|
1482
|
+
remainingQuantity
|
|
1483
|
+
totalSavings
|
|
1484
|
+
}
|
|
1067
1485
|
items {
|
|
1068
1486
|
id
|
|
1069
1487
|
product_variant_id
|
|
1070
1488
|
quantity
|
|
1489
|
+
unit_price
|
|
1490
|
+
line_total
|
|
1491
|
+
base_unit_price
|
|
1492
|
+
unit_savings
|
|
1493
|
+
line_savings
|
|
1494
|
+
volume_pricing_eligible
|
|
1495
|
+
resolved_price_type
|
|
1496
|
+
volume_pricing_min_quantity
|
|
1071
1497
|
product_variant {
|
|
1072
1498
|
id
|
|
1073
1499
|
sku
|
|
@@ -1098,6 +1524,7 @@ var START_GUEST_CHECKOUT = (
|
|
|
1098
1524
|
$contact: WebsiteGuestContactInput!
|
|
1099
1525
|
$items: [WebsiteCheckoutItemInput!]!
|
|
1100
1526
|
$fulfillmentFamily: String
|
|
1527
|
+
$marketingAttribution: WebsiteMarketingAttributionInput
|
|
1101
1528
|
) {
|
|
1102
1529
|
websiteStartGuestCheckout(
|
|
1103
1530
|
website_id: $websiteId
|
|
@@ -1105,6 +1532,7 @@ var START_GUEST_CHECKOUT = (
|
|
|
1105
1532
|
contact: $contact
|
|
1106
1533
|
items: $items
|
|
1107
1534
|
fulfillment_family: $fulfillmentFamily
|
|
1535
|
+
marketing_attribution: $marketingAttribution
|
|
1108
1536
|
) {
|
|
1109
1537
|
success
|
|
1110
1538
|
order_id
|
|
@@ -1119,10 +1547,28 @@ var START_GUEST_CHECKOUT = (
|
|
|
1119
1547
|
total_with_discount
|
|
1120
1548
|
created_at
|
|
1121
1549
|
updated_at
|
|
1550
|
+
volume_pricing {
|
|
1551
|
+
enabled
|
|
1552
|
+
eligibleQuantity
|
|
1553
|
+
appliedPriceType
|
|
1554
|
+
appliedMinQuantity
|
|
1555
|
+
nextPriceType
|
|
1556
|
+
nextMinQuantity
|
|
1557
|
+
remainingQuantity
|
|
1558
|
+
totalSavings
|
|
1559
|
+
}
|
|
1122
1560
|
items {
|
|
1123
1561
|
id
|
|
1124
1562
|
product_variant_id
|
|
1125
1563
|
quantity
|
|
1564
|
+
unit_price
|
|
1565
|
+
line_total
|
|
1566
|
+
base_unit_price
|
|
1567
|
+
unit_savings
|
|
1568
|
+
line_savings
|
|
1569
|
+
volume_pricing_eligible
|
|
1570
|
+
resolved_price_type
|
|
1571
|
+
volume_pricing_min_quantity
|
|
1126
1572
|
product_variant {
|
|
1127
1573
|
id
|
|
1128
1574
|
sku
|
|
@@ -1172,10 +1618,28 @@ var SET_CART_ITEM = (
|
|
|
1172
1618
|
total_with_discount
|
|
1173
1619
|
created_at
|
|
1174
1620
|
updated_at
|
|
1621
|
+
volume_pricing {
|
|
1622
|
+
enabled
|
|
1623
|
+
eligibleQuantity
|
|
1624
|
+
appliedPriceType
|
|
1625
|
+
appliedMinQuantity
|
|
1626
|
+
nextPriceType
|
|
1627
|
+
nextMinQuantity
|
|
1628
|
+
remainingQuantity
|
|
1629
|
+
totalSavings
|
|
1630
|
+
}
|
|
1175
1631
|
items {
|
|
1176
1632
|
id
|
|
1177
1633
|
product_variant_id
|
|
1178
1634
|
quantity
|
|
1635
|
+
unit_price
|
|
1636
|
+
line_total
|
|
1637
|
+
base_unit_price
|
|
1638
|
+
unit_savings
|
|
1639
|
+
line_savings
|
|
1640
|
+
volume_pricing_eligible
|
|
1641
|
+
resolved_price_type
|
|
1642
|
+
volume_pricing_min_quantity
|
|
1179
1643
|
product_variant {
|
|
1180
1644
|
id
|
|
1181
1645
|
sku
|
|
@@ -1223,10 +1687,28 @@ var REMOVE_CART_ITEM = (
|
|
|
1223
1687
|
total_with_discount
|
|
1224
1688
|
created_at
|
|
1225
1689
|
updated_at
|
|
1690
|
+
volume_pricing {
|
|
1691
|
+
enabled
|
|
1692
|
+
eligibleQuantity
|
|
1693
|
+
appliedPriceType
|
|
1694
|
+
appliedMinQuantity
|
|
1695
|
+
nextPriceType
|
|
1696
|
+
nextMinQuantity
|
|
1697
|
+
remainingQuantity
|
|
1698
|
+
totalSavings
|
|
1699
|
+
}
|
|
1226
1700
|
items {
|
|
1227
1701
|
id
|
|
1228
1702
|
product_variant_id
|
|
1229
1703
|
quantity
|
|
1704
|
+
unit_price
|
|
1705
|
+
line_total
|
|
1706
|
+
base_unit_price
|
|
1707
|
+
unit_savings
|
|
1708
|
+
line_savings
|
|
1709
|
+
volume_pricing_eligible
|
|
1710
|
+
resolved_price_type
|
|
1711
|
+
volume_pricing_min_quantity
|
|
1230
1712
|
product_variant {
|
|
1231
1713
|
id
|
|
1232
1714
|
sku
|
|
@@ -1248,6 +1730,19 @@ var REMOVE_CART_ITEM = (
|
|
|
1248
1730
|
}
|
|
1249
1731
|
`
|
|
1250
1732
|
);
|
|
1733
|
+
var DISCARD_CART = (
|
|
1734
|
+
/* GraphQL */
|
|
1735
|
+
`
|
|
1736
|
+
mutation DiscardCart($orderId: uuid!, $cartToken: String!) {
|
|
1737
|
+
websiteDiscardCart(order_id: $orderId, cart_token: $cartToken) {
|
|
1738
|
+
success
|
|
1739
|
+
order_id
|
|
1740
|
+
website_cart_status
|
|
1741
|
+
errors
|
|
1742
|
+
}
|
|
1743
|
+
}
|
|
1744
|
+
`
|
|
1745
|
+
);
|
|
1251
1746
|
var CHECKOUT = (
|
|
1252
1747
|
/* GraphQL */
|
|
1253
1748
|
`
|
|
@@ -1262,6 +1757,8 @@ var CHECKOUT = (
|
|
|
1262
1757
|
$paymentMethodId: uuid
|
|
1263
1758
|
$buyerAccountOptIn: Boolean
|
|
1264
1759
|
$initialPayment: WebsiteInitialPaymentInput
|
|
1760
|
+
$marketingAttribution: WebsiteMarketingAttributionInput
|
|
1761
|
+
$advertisingConsent: WebsiteAdvertisingConsentInput
|
|
1265
1762
|
) {
|
|
1266
1763
|
websiteCheckout(
|
|
1267
1764
|
order_id: $orderId
|
|
@@ -1274,6 +1771,8 @@ var CHECKOUT = (
|
|
|
1274
1771
|
payment_method_id: $paymentMethodId
|
|
1275
1772
|
buyer_account_opt_in: $buyerAccountOptIn
|
|
1276
1773
|
initial_payment: $initialPayment
|
|
1774
|
+
marketing_attribution: $marketingAttribution
|
|
1775
|
+
advertising_consent: $advertisingConsent
|
|
1277
1776
|
) {
|
|
1278
1777
|
success
|
|
1279
1778
|
order_id
|
|
@@ -2200,45 +2699,157 @@ var GET_SEARCH_SUGGESTIONS = (
|
|
|
2200
2699
|
`
|
|
2201
2700
|
);
|
|
2202
2701
|
|
|
2203
|
-
// libs/sdk/src/
|
|
2204
|
-
var
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2702
|
+
// libs/sdk/src/transport/graphql.ts
|
|
2703
|
+
var DEFAULT_MAX_ATTEMPTS = 4;
|
|
2704
|
+
var DEFAULT_MAX_TOTAL_WAIT_MS = 3 * 60 * 1e3;
|
|
2705
|
+
var DEFAULT_JITTER_RATIO = 0.1;
|
|
2706
|
+
function parseRetryAfterSeconds(res, body) {
|
|
2707
|
+
const header = res.headers?.get?.("Retry-After")?.trim();
|
|
2708
|
+
if (header) {
|
|
2709
|
+
const seconds = Number(header);
|
|
2710
|
+
if (Number.isFinite(seconds) && seconds >= 0) return Math.ceil(seconds);
|
|
2711
|
+
const retryAt = Date.parse(header);
|
|
2712
|
+
if (Number.isFinite(retryAt)) {
|
|
2713
|
+
return Math.max(Math.ceil((retryAt - Date.now()) / 1e3), 0);
|
|
2714
|
+
}
|
|
2715
|
+
}
|
|
2716
|
+
if (body && typeof body === "object") {
|
|
2717
|
+
const seconds = Number(
|
|
2718
|
+
body.retry_after_seconds
|
|
2719
|
+
);
|
|
2720
|
+
if (Number.isFinite(seconds) && seconds >= 0) return Math.ceil(seconds);
|
|
2721
|
+
}
|
|
2722
|
+
return 1;
|
|
2211
2723
|
}
|
|
2212
|
-
function
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2724
|
+
async function createRateLimitError(res) {
|
|
2725
|
+
let body;
|
|
2726
|
+
try {
|
|
2727
|
+
body = await res.json();
|
|
2728
|
+
} catch {
|
|
2729
|
+
body = null;
|
|
2216
2730
|
}
|
|
2217
|
-
|
|
2731
|
+
const message = body && typeof body === "object" && typeof body.message === "string" ? body.message : "Rate limit exceeded. Please try again later.";
|
|
2732
|
+
return new RateLimitError(message, parseRetryAfterSeconds(res, body));
|
|
2218
2733
|
}
|
|
2219
|
-
function
|
|
2220
|
-
|
|
2734
|
+
function getRetryDelayMs(error, options) {
|
|
2735
|
+
const retryAfterMs = (error.retryAfterSeconds ?? 1) * 1e3;
|
|
2736
|
+
const jitterRatio = Math.max(
|
|
2737
|
+
0,
|
|
2738
|
+
Math.min(options.jitterRatio ?? DEFAULT_JITTER_RATIO, 1)
|
|
2739
|
+
);
|
|
2740
|
+
return Math.ceil(retryAfterMs * (1 + Math.random() * jitterRatio));
|
|
2221
2741
|
}
|
|
2222
|
-
function
|
|
2223
|
-
return
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2742
|
+
function wait(delayMs) {
|
|
2743
|
+
return new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
2744
|
+
}
|
|
2745
|
+
async function executeGraphQL(url, query, variables, headers, options) {
|
|
2746
|
+
const retry = options?.retry;
|
|
2747
|
+
const maxAttempts = Math.max(
|
|
2748
|
+
1,
|
|
2749
|
+
Math.floor(retry?.maxAttempts ?? DEFAULT_MAX_ATTEMPTS)
|
|
2750
|
+
);
|
|
2751
|
+
const maxTotalWaitMs = Math.max(
|
|
2752
|
+
0,
|
|
2753
|
+
retry?.maxTotalWaitMs ?? DEFAULT_MAX_TOTAL_WAIT_MS
|
|
2754
|
+
);
|
|
2755
|
+
let totalWaitMs = 0;
|
|
2756
|
+
let res = null;
|
|
2757
|
+
for (let attempt = 1; attempt <= (retry ? maxAttempts : 1); attempt += 1) {
|
|
2758
|
+
const resolvedHeaders = typeof headers === "function" ? await headers() : headers ?? {};
|
|
2759
|
+
try {
|
|
2760
|
+
res = await fetch(url, {
|
|
2761
|
+
method: "POST",
|
|
2762
|
+
headers: {
|
|
2763
|
+
"Content-Type": "application/json",
|
|
2764
|
+
...resolvedHeaders
|
|
2765
|
+
},
|
|
2766
|
+
body: JSON.stringify({ query, variables })
|
|
2767
|
+
});
|
|
2768
|
+
} catch (err) {
|
|
2769
|
+
throw new WebsiteError(
|
|
2770
|
+
`Network error connecting to ${url}: ${err instanceof Error ? err.message : String(err)}`,
|
|
2771
|
+
"NETWORK_ERROR"
|
|
2772
|
+
);
|
|
2773
|
+
}
|
|
2774
|
+
if (res.status !== 429) break;
|
|
2775
|
+
const rateLimitError = await createRateLimitError(res);
|
|
2776
|
+
const delayMs = getRetryDelayMs(rateLimitError, retry ?? {});
|
|
2777
|
+
const canRetry = Boolean(retry) && attempt < maxAttempts && totalWaitMs + delayMs <= maxTotalWaitMs;
|
|
2778
|
+
if (!canRetry) throw rateLimitError;
|
|
2779
|
+
totalWaitMs += delayMs;
|
|
2780
|
+
await wait(delayMs);
|
|
2781
|
+
}
|
|
2782
|
+
if (!res) {
|
|
2783
|
+
throw new WebsiteError(
|
|
2784
|
+
"GraphQL request did not produce a response",
|
|
2785
|
+
"EMPTY_RESPONSE"
|
|
2786
|
+
);
|
|
2787
|
+
}
|
|
2788
|
+
let body;
|
|
2789
|
+
try {
|
|
2790
|
+
body = await res.json();
|
|
2791
|
+
} catch {
|
|
2792
|
+
throw new WebsiteError(
|
|
2793
|
+
`Non-JSON response from ${url} (status ${res.status})`,
|
|
2794
|
+
"INVALID_RESPONSE"
|
|
2795
|
+
);
|
|
2796
|
+
}
|
|
2797
|
+
if (body.errors && body.errors.length > 0) {
|
|
2798
|
+
throw new GraphQLRequestError(body.errors);
|
|
2799
|
+
}
|
|
2800
|
+
if (!body.data) {
|
|
2801
|
+
throw new WebsiteError(
|
|
2802
|
+
"GraphQL response contains no data",
|
|
2803
|
+
"EMPTY_RESPONSE"
|
|
2804
|
+
);
|
|
2805
|
+
}
|
|
2806
|
+
return body.data;
|
|
2807
|
+
}
|
|
2808
|
+
|
|
2809
|
+
// libs/sdk/src/modules/availability.ts
|
|
2810
|
+
var MAX_AVAILABILITY_VARIANT_IDS = 100;
|
|
2811
|
+
function mapAvailability(raw) {
|
|
2812
|
+
return {
|
|
2813
|
+
productVariantId: raw.product_variant_id,
|
|
2814
|
+
inStock: raw.in_stock,
|
|
2815
|
+
label: raw.label,
|
|
2816
|
+
maxPurchasableQuantity: raw.max_purchasable_quantity
|
|
2817
|
+
};
|
|
2818
|
+
}
|
|
2819
|
+
function chunkArray(items, chunkSize) {
|
|
2820
|
+
const chunks = [];
|
|
2821
|
+
for (let index = 0; index < items.length; index += chunkSize) {
|
|
2822
|
+
chunks.push(items.slice(index, index + chunkSize));
|
|
2823
|
+
}
|
|
2824
|
+
return chunks;
|
|
2825
|
+
}
|
|
2826
|
+
function uniqueValues(values) {
|
|
2827
|
+
return Array.from(new Set(values));
|
|
2828
|
+
}
|
|
2829
|
+
function createAvailabilityModule(graphqlUrl, getHeaders, getContext) {
|
|
2830
|
+
return {
|
|
2831
|
+
async checkAvailability(productVariantIds) {
|
|
2832
|
+
const ctx = getContext();
|
|
2833
|
+
if (!ctx.chainId) {
|
|
2834
|
+
throw new WebsiteError(
|
|
2835
|
+
"This website has no chain configured. Stock availability requires a chain_id.",
|
|
2836
|
+
"NO_CHAIN"
|
|
2837
|
+
);
|
|
2231
2838
|
}
|
|
2232
2839
|
if (productVariantIds.length === 0) return [];
|
|
2233
2840
|
const variantIds = uniqueValues(productVariantIds);
|
|
2234
2841
|
const results = [];
|
|
2235
|
-
for (const chunk of chunkArray(
|
|
2842
|
+
for (const chunk of chunkArray(
|
|
2843
|
+
variantIds,
|
|
2844
|
+
MAX_AVAILABILITY_VARIANT_IDS
|
|
2845
|
+
)) {
|
|
2236
2846
|
const data = await executeGraphQL(
|
|
2237
2847
|
graphqlUrl,
|
|
2238
2848
|
CHECK_AVAILABILITY,
|
|
2239
2849
|
{
|
|
2240
2850
|
variantIds: chunk,
|
|
2241
|
-
chainId: ctx.chainId
|
|
2851
|
+
chainId: ctx.chainId,
|
|
2852
|
+
websiteId: ctx.websiteId
|
|
2242
2853
|
},
|
|
2243
2854
|
getHeaders
|
|
2244
2855
|
);
|
|
@@ -2251,12 +2862,297 @@ function createAvailabilityModule(graphqlUrl, getHeaders, getContext) {
|
|
|
2251
2862
|
};
|
|
2252
2863
|
}
|
|
2253
2864
|
|
|
2865
|
+
// libs/sdk/src/marketing/sanitize.ts
|
|
2866
|
+
var SENSITIVE_PARAMETER_PATTERNS = [
|
|
2867
|
+
/token/i,
|
|
2868
|
+
/secret/i,
|
|
2869
|
+
/password/i,
|
|
2870
|
+
/authorization/i,
|
|
2871
|
+
/session/i,
|
|
2872
|
+
/ticket/i,
|
|
2873
|
+
/code_verifier/i,
|
|
2874
|
+
/client_secret/i,
|
|
2875
|
+
/payment/i,
|
|
2876
|
+
/order[_-]?(?:id|token)/i,
|
|
2877
|
+
/public[_-]?order/i,
|
|
2878
|
+
/clerk/i,
|
|
2879
|
+
/oauth/i,
|
|
2880
|
+
/email/i,
|
|
2881
|
+
/phone/i,
|
|
2882
|
+
/document/i
|
|
2883
|
+
];
|
|
2884
|
+
var ALLOWED_MARKETING_PARAMETERS = /* @__PURE__ */ new Set([
|
|
2885
|
+
"utm_source",
|
|
2886
|
+
"utm_medium",
|
|
2887
|
+
"utm_campaign",
|
|
2888
|
+
"utm_term",
|
|
2889
|
+
"utm_content",
|
|
2890
|
+
"utm_source_platform",
|
|
2891
|
+
"utm_id",
|
|
2892
|
+
"bacano_origin",
|
|
2893
|
+
"gclid",
|
|
2894
|
+
"gbraid",
|
|
2895
|
+
"wbraid",
|
|
2896
|
+
"fbclid",
|
|
2897
|
+
"ttclid",
|
|
2898
|
+
"msclkid"
|
|
2899
|
+
]);
|
|
2900
|
+
function sanitizeMarketingUrl(input, baseUrl) {
|
|
2901
|
+
try {
|
|
2902
|
+
const url = new URL(input, baseUrl);
|
|
2903
|
+
const safeParameters = new URLSearchParams();
|
|
2904
|
+
for (const [key, value] of url.searchParams.entries()) {
|
|
2905
|
+
if (ALLOWED_MARKETING_PARAMETERS.has(key.toLowerCase()) && !SENSITIVE_PARAMETER_PATTERNS.some((pattern) => pattern.test(key))) {
|
|
2906
|
+
safeParameters.set(key.toLowerCase(), value.slice(0, 500));
|
|
2907
|
+
}
|
|
2908
|
+
}
|
|
2909
|
+
url.search = safeParameters.toString();
|
|
2910
|
+
url.hash = "";
|
|
2911
|
+
return url.toString();
|
|
2912
|
+
} catch {
|
|
2913
|
+
return "";
|
|
2914
|
+
}
|
|
2915
|
+
}
|
|
2916
|
+
function getAllowedMarketingParameters(input, baseUrl) {
|
|
2917
|
+
const sanitized = sanitizeMarketingUrl(input, baseUrl);
|
|
2918
|
+
if (!sanitized) return {};
|
|
2919
|
+
const url = new URL(sanitized);
|
|
2920
|
+
return Object.fromEntries(url.searchParams.entries());
|
|
2921
|
+
}
|
|
2922
|
+
|
|
2923
|
+
// libs/sdk/src/marketing/attribution.ts
|
|
2924
|
+
var STORAGE_PREFIX = "bacano:marketing:attribution:v1";
|
|
2925
|
+
var GA4_CLIENT_ID_PATTERN = /^\d+\.\d+$/;
|
|
2926
|
+
var META_BROWSER_ID_PATTERN = /^fb\.\d+\.\d+\.[A-Za-z0-9_-]+$/;
|
|
2927
|
+
var SALES_ORIGIN_KEY_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
2928
|
+
function storageKey(websiteId) {
|
|
2929
|
+
return `${STORAGE_PREFIX}:${websiteId}`;
|
|
2930
|
+
}
|
|
2931
|
+
function readStored(websiteId) {
|
|
2932
|
+
if (typeof window === "undefined") return null;
|
|
2933
|
+
try {
|
|
2934
|
+
const value = window.localStorage.getItem(storageKey(websiteId));
|
|
2935
|
+
if (!value) return null;
|
|
2936
|
+
const parsed = JSON.parse(value);
|
|
2937
|
+
if (new Date(parsed.expiresAt).getTime() <= Date.now()) {
|
|
2938
|
+
window.localStorage.removeItem(storageKey(websiteId));
|
|
2939
|
+
return null;
|
|
2940
|
+
}
|
|
2941
|
+
return parsed;
|
|
2942
|
+
} catch {
|
|
2943
|
+
return null;
|
|
2944
|
+
}
|
|
2945
|
+
}
|
|
2946
|
+
function writeStored(websiteId, value) {
|
|
2947
|
+
if (typeof window === "undefined") return;
|
|
2948
|
+
try {
|
|
2949
|
+
window.localStorage.setItem(storageKey(websiteId), JSON.stringify(value));
|
|
2950
|
+
} catch {
|
|
2951
|
+
}
|
|
2952
|
+
}
|
|
2953
|
+
function readCookie(name) {
|
|
2954
|
+
if (typeof document === "undefined") return void 0;
|
|
2955
|
+
try {
|
|
2956
|
+
const prefix = `${name}=`;
|
|
2957
|
+
return document.cookie.split(";").map((part) => part.trim()).find((part) => part.startsWith(prefix))?.slice(prefix.length);
|
|
2958
|
+
} catch {
|
|
2959
|
+
return void 0;
|
|
2960
|
+
}
|
|
2961
|
+
}
|
|
2962
|
+
function validMetaBrowserId(value) {
|
|
2963
|
+
const normalized = value?.trim();
|
|
2964
|
+
return normalized && normalized.length <= 512 && META_BROWSER_ID_PATTERN.test(normalized) ? normalized : void 0;
|
|
2965
|
+
}
|
|
2966
|
+
function optional(value) {
|
|
2967
|
+
const normalized = value?.trim();
|
|
2968
|
+
return normalized ? normalized.slice(0, 500) : void 0;
|
|
2969
|
+
}
|
|
2970
|
+
function validSalesOriginKey(value) {
|
|
2971
|
+
const normalized = value?.trim().toLowerCase();
|
|
2972
|
+
return normalized && normalized.length <= 100 && SALES_ORIGIN_KEY_PATTERN.test(normalized) ? normalized : void 0;
|
|
2973
|
+
}
|
|
2974
|
+
function buildTouchpoint() {
|
|
2975
|
+
if (typeof window === "undefined") return null;
|
|
2976
|
+
const params = getAllowedMarketingParameters(window.location.href);
|
|
2977
|
+
const referrerHost = (() => {
|
|
2978
|
+
if (!document.referrer) return void 0;
|
|
2979
|
+
try {
|
|
2980
|
+
const hostname = new URL(document.referrer).hostname;
|
|
2981
|
+
return hostname === window.location.hostname ? void 0 : hostname.slice(0, 255);
|
|
2982
|
+
} catch {
|
|
2983
|
+
return void 0;
|
|
2984
|
+
}
|
|
2985
|
+
})();
|
|
2986
|
+
return {
|
|
2987
|
+
capturedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2988
|
+
landingUrl: sanitizeMarketingUrl(window.location.href),
|
|
2989
|
+
referrerHost,
|
|
2990
|
+
source: optional(params.utm_source),
|
|
2991
|
+
medium: optional(params.utm_medium),
|
|
2992
|
+
campaign: optional(params.utm_campaign),
|
|
2993
|
+
term: optional(params.utm_term),
|
|
2994
|
+
content: optional(params.utm_content),
|
|
2995
|
+
sourcePlatform: optional(params.utm_source_platform),
|
|
2996
|
+
campaignId: optional(params.utm_id),
|
|
2997
|
+
salesOriginKey: validSalesOriginKey(params.bacano_origin),
|
|
2998
|
+
gclid: optional(params.gclid),
|
|
2999
|
+
gbraid: optional(params.gbraid),
|
|
3000
|
+
wbraid: optional(params.wbraid),
|
|
3001
|
+
fbclid: optional(params.fbclid),
|
|
3002
|
+
ttclid: optional(params.ttclid),
|
|
3003
|
+
msclkid: optional(params.msclkid)
|
|
3004
|
+
};
|
|
3005
|
+
}
|
|
3006
|
+
function isNonDirect(touchpoint) {
|
|
3007
|
+
return Boolean(
|
|
3008
|
+
touchpoint.salesOriginKey || touchpoint.source || touchpoint.medium || touchpoint.campaign || touchpoint.gclid || touchpoint.gbraid || touchpoint.wbraid || touchpoint.fbclid || touchpoint.ttclid || touchpoint.msclkid || touchpoint.referrerHost
|
|
3009
|
+
);
|
|
3010
|
+
}
|
|
3011
|
+
function toPublic(stored) {
|
|
3012
|
+
return {
|
|
3013
|
+
schemaVersion: 1,
|
|
3014
|
+
firstTouch: stored.firstTouch,
|
|
3015
|
+
lastNonDirectTouch: stored.lastNonDirectTouch,
|
|
3016
|
+
ga4ClientId: stored.ga4ClientId,
|
|
3017
|
+
ga4SessionId: stored.ga4SessionId,
|
|
3018
|
+
metaFbp: stored.metaFbp,
|
|
3019
|
+
metaFbc: stored.metaFbc,
|
|
3020
|
+
clientUserAgent: stored.clientUserAgent,
|
|
3021
|
+
capturedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3022
|
+
expiresAt: stored.expiresAt
|
|
3023
|
+
};
|
|
3024
|
+
}
|
|
3025
|
+
function refreshWebsiteMarketingMetaContext(websiteId) {
|
|
3026
|
+
try {
|
|
3027
|
+
const stored = readStored(websiteId);
|
|
3028
|
+
if (!stored || typeof window === "undefined") return false;
|
|
3029
|
+
const fbp = validMetaBrowserId(readCookie("_fbp"));
|
|
3030
|
+
const clickTouchpoint = stored.lastNonDirectTouch ?? stored.firstTouch;
|
|
3031
|
+
const derivedFbc = clickTouchpoint.fbclid ? `fb.1.${new Date(clickTouchpoint.capturedAt).getTime()}.${clickTouchpoint.fbclid}` : void 0;
|
|
3032
|
+
const fbc = validMetaBrowserId(readCookie("_fbc") ?? derivedFbc);
|
|
3033
|
+
const clientUserAgent = window.navigator?.userAgent.trim().slice(0, 1024);
|
|
3034
|
+
writeStored(websiteId, {
|
|
3035
|
+
...stored,
|
|
3036
|
+
metaFbp: fbp ?? stored.metaFbp,
|
|
3037
|
+
metaFbc: fbc ?? stored.metaFbc,
|
|
3038
|
+
clientUserAgent: clientUserAgent || stored.clientUserAgent
|
|
3039
|
+
});
|
|
3040
|
+
return Boolean(fbp || fbc || clientUserAgent);
|
|
3041
|
+
} catch {
|
|
3042
|
+
return false;
|
|
3043
|
+
}
|
|
3044
|
+
}
|
|
3045
|
+
function storeWebsiteMarketingGa4ClientId(websiteId, clientId) {
|
|
3046
|
+
if (typeof clientId !== "string") return false;
|
|
3047
|
+
const normalized = clientId.trim();
|
|
3048
|
+
if (!GA4_CLIENT_ID_PATTERN.test(normalized)) return false;
|
|
3049
|
+
const stored = readStored(websiteId);
|
|
3050
|
+
if (!stored) return false;
|
|
3051
|
+
writeStored(websiteId, { ...stored, ga4ClientId: normalized });
|
|
3052
|
+
return true;
|
|
3053
|
+
}
|
|
3054
|
+
function storeWebsiteMarketingGa4SessionId(websiteId, sessionId) {
|
|
3055
|
+
const normalized = typeof sessionId === "number" && Number.isSafeInteger(sessionId) ? String(sessionId) : typeof sessionId === "string" ? sessionId.trim() : "";
|
|
3056
|
+
const numericValue = Number(normalized);
|
|
3057
|
+
if (!/^\d{1,16}$/.test(normalized) || !Number.isSafeInteger(numericValue) || numericValue <= 0) {
|
|
3058
|
+
return false;
|
|
3059
|
+
}
|
|
3060
|
+
const stored = readStored(websiteId);
|
|
3061
|
+
if (!stored) return false;
|
|
3062
|
+
writeStored(websiteId, { ...stored, ga4SessionId: normalized });
|
|
3063
|
+
return true;
|
|
3064
|
+
}
|
|
3065
|
+
function captureWebsiteMarketingAttribution(websiteId, windowDays) {
|
|
3066
|
+
const current = buildTouchpoint();
|
|
3067
|
+
if (!current) return null;
|
|
3068
|
+
const stored = readStored(websiteId);
|
|
3069
|
+
const expiresAt = stored?.expiresAt ?? new Date(Date.now() + Math.max(1, windowDays) * 864e5).toISOString();
|
|
3070
|
+
const next = {
|
|
3071
|
+
firstTouch: stored?.firstTouch ?? current,
|
|
3072
|
+
lastNonDirectTouch: isNonDirect(current) ? current : stored?.lastNonDirectTouch ?? null,
|
|
3073
|
+
ga4ClientId: stored?.ga4ClientId,
|
|
3074
|
+
ga4SessionId: stored?.ga4SessionId,
|
|
3075
|
+
metaFbp: stored?.metaFbp,
|
|
3076
|
+
metaFbc: stored?.metaFbc,
|
|
3077
|
+
clientUserAgent: stored?.clientUserAgent,
|
|
3078
|
+
expiresAt
|
|
3079
|
+
};
|
|
3080
|
+
writeStored(websiteId, next);
|
|
3081
|
+
return toPublic(next);
|
|
3082
|
+
}
|
|
3083
|
+
function getWebsiteMarketingAttribution(websiteId) {
|
|
3084
|
+
const stored = readStored(websiteId);
|
|
3085
|
+
return stored ? toPublic(stored) : null;
|
|
3086
|
+
}
|
|
3087
|
+
function serializeTouchpoint(touchpoint) {
|
|
3088
|
+
return {
|
|
3089
|
+
captured_at: touchpoint.capturedAt,
|
|
3090
|
+
landing_url: touchpoint.landingUrl,
|
|
3091
|
+
referrer_host: touchpoint.referrerHost,
|
|
3092
|
+
source: touchpoint.source,
|
|
3093
|
+
medium: touchpoint.medium,
|
|
3094
|
+
campaign: touchpoint.campaign,
|
|
3095
|
+
term: touchpoint.term,
|
|
3096
|
+
content: touchpoint.content,
|
|
3097
|
+
source_platform: touchpoint.sourcePlatform,
|
|
3098
|
+
campaign_id: touchpoint.campaignId,
|
|
3099
|
+
sales_origin_key: touchpoint.salesOriginKey,
|
|
3100
|
+
gclid: touchpoint.gclid,
|
|
3101
|
+
gbraid: touchpoint.gbraid,
|
|
3102
|
+
wbraid: touchpoint.wbraid,
|
|
3103
|
+
fbclid: touchpoint.fbclid,
|
|
3104
|
+
ttclid: touchpoint.ttclid,
|
|
3105
|
+
msclkid: touchpoint.msclkid
|
|
3106
|
+
};
|
|
3107
|
+
}
|
|
3108
|
+
function serializeWebsiteMarketingAttribution(attribution) {
|
|
3109
|
+
return {
|
|
3110
|
+
schema_version: attribution.schemaVersion,
|
|
3111
|
+
first_touch: serializeTouchpoint(attribution.firstTouch),
|
|
3112
|
+
last_non_direct_touch: attribution.lastNonDirectTouch ? serializeTouchpoint(attribution.lastNonDirectTouch) : null,
|
|
3113
|
+
ga4_client_id: attribution.ga4ClientId,
|
|
3114
|
+
ga4_session_id: attribution.ga4SessionId,
|
|
3115
|
+
meta_fbp: attribution.metaFbp,
|
|
3116
|
+
meta_fbc: attribution.metaFbc,
|
|
3117
|
+
client_user_agent: attribution.clientUserAgent,
|
|
3118
|
+
captured_at: attribution.capturedAt
|
|
3119
|
+
};
|
|
3120
|
+
}
|
|
3121
|
+
|
|
3122
|
+
// libs/sdk/src/modules/cart-errors.ts
|
|
3123
|
+
var RECOVERABLE_CART_SESSION_ERRORS = /* @__PURE__ */ new Set([
|
|
3124
|
+
"INVALID_CART_TOKEN",
|
|
3125
|
+
"CART_NOT_FOUND"
|
|
3126
|
+
]);
|
|
3127
|
+
function firstStableErrorCode(errors) {
|
|
3128
|
+
const candidate = errors?.[0]?.split(":", 1)[0]?.trim();
|
|
3129
|
+
return candidate && /^[A-Z][A-Z0-9_]+$/.test(candidate) ? candidate : void 0;
|
|
3130
|
+
}
|
|
3131
|
+
function getStableCartActionErrorCode(errors, fallbackCode) {
|
|
3132
|
+
return firstStableErrorCode(errors) ?? fallbackCode;
|
|
3133
|
+
}
|
|
3134
|
+
function hasRecoverableCartSessionError(errors) {
|
|
3135
|
+
return Boolean(
|
|
3136
|
+
errors?.some(
|
|
3137
|
+
(error) => RECOVERABLE_CART_SESSION_ERRORS.has(error.split(":", 1)[0]?.trim())
|
|
3138
|
+
)
|
|
3139
|
+
);
|
|
3140
|
+
}
|
|
3141
|
+
|
|
2254
3142
|
// libs/sdk/src/modules/cart.ts
|
|
2255
3143
|
function mapCartItem(raw) {
|
|
2256
3144
|
return {
|
|
2257
3145
|
id: raw.id,
|
|
2258
3146
|
productVariantId: raw.product_variant_id,
|
|
2259
3147
|
quantity: raw.quantity,
|
|
3148
|
+
unitPrice: raw.unit_price ?? null,
|
|
3149
|
+
lineTotal: raw.line_total ?? null,
|
|
3150
|
+
baseUnitPrice: raw.base_unit_price ?? raw.unit_price ?? null,
|
|
3151
|
+
unitSavings: raw.unit_savings ?? 0,
|
|
3152
|
+
lineSavings: raw.line_savings ?? 0,
|
|
3153
|
+
volumePricingEligible: raw.volume_pricing_eligible ?? false,
|
|
3154
|
+
resolvedPriceType: raw.resolved_price_type ?? null,
|
|
3155
|
+
volumePricingMinQuantity: raw.volume_pricing_min_quantity ?? null,
|
|
2260
3156
|
productVariant: {
|
|
2261
3157
|
sku: raw.product_variant.sku,
|
|
2262
3158
|
product: raw.product_variant.product,
|
|
@@ -2282,13 +3178,29 @@ function mapCart(raw, cartToken) {
|
|
|
2282
3178
|
},
|
|
2283
3179
|
status: raw.status,
|
|
2284
3180
|
createdAt: raw.created_at,
|
|
2285
|
-
updatedAt: raw.updated_at
|
|
3181
|
+
updatedAt: raw.updated_at,
|
|
3182
|
+
volumePricing: raw.volume_pricing ? {
|
|
3183
|
+
...raw.volume_pricing,
|
|
3184
|
+
totalSavings: raw.volume_pricing.totalSavings ?? 0
|
|
3185
|
+
} : {
|
|
3186
|
+
enabled: false,
|
|
3187
|
+
eligibleQuantity: 0,
|
|
3188
|
+
appliedPriceType: null,
|
|
3189
|
+
appliedMinQuantity: null,
|
|
3190
|
+
nextPriceType: null,
|
|
3191
|
+
nextMinQuantity: null,
|
|
3192
|
+
remainingQuantity: null,
|
|
3193
|
+
totalSavings: 0
|
|
3194
|
+
}
|
|
2286
3195
|
};
|
|
2287
3196
|
}
|
|
2288
3197
|
function assertActionSuccess(result, fallbackCode) {
|
|
2289
3198
|
if (!result.success || !result.order_id || !result.cart_token || !result.cart) {
|
|
2290
3199
|
const message = result.errors?.join("; ") || "Cart action failed";
|
|
2291
|
-
throw new WebsiteError(
|
|
3200
|
+
throw new WebsiteError(
|
|
3201
|
+
message,
|
|
3202
|
+
getStableCartActionErrorCode(result.errors, fallbackCode)
|
|
3203
|
+
);
|
|
2292
3204
|
}
|
|
2293
3205
|
}
|
|
2294
3206
|
function requireCartSession(storage) {
|
|
@@ -2306,7 +3218,7 @@ function createCartModule(graphqlUrl, getHeaders, getContext, cartSessionStorage
|
|
|
2306
3218
|
const data = await executeGraphQL(graphqlUrl, GET_CART, { orderId, cartToken }, getHeaders);
|
|
2307
3219
|
const result = data.websiteGetCart;
|
|
2308
3220
|
if (!result.success) {
|
|
2309
|
-
if (result.errors
|
|
3221
|
+
if (hasRecoverableCartSessionError(result.errors)) {
|
|
2310
3222
|
cartSessionStorage.clear();
|
|
2311
3223
|
return null;
|
|
2312
3224
|
}
|
|
@@ -2359,23 +3271,31 @@ function createCartModule(graphqlUrl, getHeaders, getContext, cartSessionStorage
|
|
|
2359
3271
|
"NO_CHAIN"
|
|
2360
3272
|
);
|
|
2361
3273
|
}
|
|
3274
|
+
const requestStartCheckout = async (cartToken) => {
|
|
3275
|
+
const data = await executeGraphQL(
|
|
3276
|
+
graphqlUrl,
|
|
3277
|
+
START_GUEST_CHECKOUT,
|
|
3278
|
+
{
|
|
3279
|
+
websiteId: ctx.websiteId,
|
|
3280
|
+
cartToken,
|
|
3281
|
+
contact: input.contact,
|
|
3282
|
+
fulfillmentFamily: input.fulfillmentFamily,
|
|
3283
|
+
items: input.items.map((item) => ({
|
|
3284
|
+
product_variant_id: item.productVariantId,
|
|
3285
|
+
quantity: item.quantity
|
|
3286
|
+
})),
|
|
3287
|
+
marketingAttribution: input.marketingAttribution ? serializeWebsiteMarketingAttribution(input.marketingAttribution) : void 0
|
|
3288
|
+
},
|
|
3289
|
+
getHeaders
|
|
3290
|
+
);
|
|
3291
|
+
return data.websiteStartGuestCheckout;
|
|
3292
|
+
};
|
|
2362
3293
|
const existing = cartSessionStorage.get();
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
cartToken: existing?.cartToken,
|
|
2369
|
-
contact: input.contact,
|
|
2370
|
-
fulfillmentFamily: input.fulfillmentFamily,
|
|
2371
|
-
items: input.items.map((item) => ({
|
|
2372
|
-
product_variant_id: item.productVariantId,
|
|
2373
|
-
quantity: item.quantity
|
|
2374
|
-
}))
|
|
2375
|
-
},
|
|
2376
|
-
getHeaders
|
|
2377
|
-
);
|
|
2378
|
-
const result = data.websiteStartGuestCheckout;
|
|
3294
|
+
let result = await requestStartCheckout(existing?.cartToken);
|
|
3295
|
+
if (existing && !result.success && hasRecoverableCartSessionError(result.errors)) {
|
|
3296
|
+
cartSessionStorage.clear();
|
|
3297
|
+
result = await requestStartCheckout();
|
|
3298
|
+
}
|
|
2379
3299
|
assertActionSuccess(result, "CHECKOUT_START_ERROR");
|
|
2380
3300
|
cartSessionStorage.set({
|
|
2381
3301
|
orderId: result.order_id,
|
|
@@ -2398,7 +3318,7 @@ function createCartModule(graphqlUrl, getHeaders, getContext, cartSessionStorage
|
|
|
2398
3318
|
const session = cartSessionStorage.get();
|
|
2399
3319
|
if (!session) {
|
|
2400
3320
|
throw new WebsiteError(
|
|
2401
|
-
"
|
|
3321
|
+
"Create or recover a Bacano cart session before syncing cart items",
|
|
2402
3322
|
"CART_SESSION_MISSING"
|
|
2403
3323
|
);
|
|
2404
3324
|
}
|
|
@@ -2466,11 +3386,37 @@ function createCartModule(graphqlUrl, getHeaders, getContext, cartSessionStorage
|
|
|
2466
3386
|
);
|
|
2467
3387
|
const result = data.websiteRemoveCartItem;
|
|
2468
3388
|
assertActionSuccess(result, "CART_ITEM_ERROR");
|
|
3389
|
+
const cart = mapCart(result.cart, result.cart_token);
|
|
3390
|
+
if (cart.items.length === 0) {
|
|
3391
|
+
cartSessionStorage.clear();
|
|
3392
|
+
return cart;
|
|
3393
|
+
}
|
|
2469
3394
|
cartSessionStorage.set({
|
|
2470
3395
|
orderId: result.order_id,
|
|
2471
3396
|
cartToken: result.cart_token
|
|
2472
3397
|
});
|
|
2473
|
-
return
|
|
3398
|
+
return cart;
|
|
3399
|
+
},
|
|
3400
|
+
async clear() {
|
|
3401
|
+
const session = cartSessionStorage.get();
|
|
3402
|
+
if (!session) return;
|
|
3403
|
+
const data = await executeGraphQL(
|
|
3404
|
+
graphqlUrl,
|
|
3405
|
+
DISCARD_CART,
|
|
3406
|
+
{
|
|
3407
|
+
orderId: session.orderId,
|
|
3408
|
+
cartToken: session.cartToken
|
|
3409
|
+
},
|
|
3410
|
+
getHeaders
|
|
3411
|
+
);
|
|
3412
|
+
const result = data.websiteDiscardCart;
|
|
3413
|
+
if (!result.success && !hasRecoverableCartSessionError(result.errors)) {
|
|
3414
|
+
throw new WebsiteError(
|
|
3415
|
+
result.errors?.join("; ") || "Cart could not be discarded",
|
|
3416
|
+
getStableCartActionErrorCode(result.errors, "CART_DISCARD_ERROR")
|
|
3417
|
+
);
|
|
3418
|
+
}
|
|
3419
|
+
cartSessionStorage.clear();
|
|
2474
3420
|
}
|
|
2475
3421
|
};
|
|
2476
3422
|
}
|
|
@@ -2493,7 +3439,9 @@ function createCartSessionStorage(kind, getSlug) {
|
|
|
2493
3439
|
const key = readKey();
|
|
2494
3440
|
const match = document.cookie.split("; ").find((row) => row.startsWith(`${key}=`));
|
|
2495
3441
|
if (!match) return null;
|
|
2496
|
-
return JSON.parse(
|
|
3442
|
+
return JSON.parse(
|
|
3443
|
+
decodeURIComponent(match.split("=").slice(1).join("="))
|
|
3444
|
+
);
|
|
2497
3445
|
} catch {
|
|
2498
3446
|
return null;
|
|
2499
3447
|
}
|
|
@@ -2533,46 +3481,129 @@ function createCartSessionStorage(kind, getSlug) {
|
|
|
2533
3481
|
};
|
|
2534
3482
|
}
|
|
2535
3483
|
|
|
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
|
-
|
|
3484
|
+
// libs/sdk/src/modules/static-catalog.ts
|
|
3485
|
+
var STATIC_CATALOG_PAGE_SIZE = 100;
|
|
3486
|
+
var STATIC_CATALOG_MAX_PRODUCTS = 1e4;
|
|
3487
|
+
var STATIC_CATALOG_INITIAL_PAGE_SIZE = 24;
|
|
3488
|
+
async function collectStaticCatalog({
|
|
3489
|
+
websiteId,
|
|
3490
|
+
snapshot: options,
|
|
3491
|
+
bootstrap,
|
|
3492
|
+
loadPage
|
|
3493
|
+
}) {
|
|
3494
|
+
const pageSize = options.pageSize ?? STATIC_CATALOG_PAGE_SIZE;
|
|
3495
|
+
const maxProducts = options.maxProducts ?? STATIC_CATALOG_MAX_PRODUCTS;
|
|
3496
|
+
const initialPageSize = bootstrap?.initialPageSize ?? STATIC_CATALOG_INITIAL_PAGE_SIZE;
|
|
3497
|
+
const sortBy = bootstrap?.sortBy ?? "name_asc";
|
|
3498
|
+
if (!Number.isInteger(pageSize) || pageSize < 1 || pageSize > 100) {
|
|
3499
|
+
throw new WebsiteError(
|
|
3500
|
+
"Static catalog pageSize must be an integer between 1 and 100.",
|
|
3501
|
+
"INVALID_STATIC_CATALOG_PAGE_SIZE"
|
|
3502
|
+
);
|
|
3503
|
+
}
|
|
3504
|
+
if (!Number.isInteger(maxProducts) || maxProducts < 1 || maxProducts > STATIC_CATALOG_MAX_PRODUCTS) {
|
|
3505
|
+
throw new WebsiteError(
|
|
3506
|
+
`Static catalog maxProducts must be an integer between 1 and ${STATIC_CATALOG_MAX_PRODUCTS}.`,
|
|
3507
|
+
"INVALID_STATIC_CATALOG_MAX_PRODUCTS"
|
|
3508
|
+
);
|
|
3509
|
+
}
|
|
3510
|
+
if (bootstrap && (!Number.isInteger(initialPageSize) || initialPageSize < 1 || initialPageSize > 100)) {
|
|
3511
|
+
throw new WebsiteError(
|
|
3512
|
+
"Static catalog initialPageSize must be an integer between 1 and 100.",
|
|
3513
|
+
"INVALID_STATIC_CATALOG_INITIAL_PAGE_SIZE"
|
|
3514
|
+
);
|
|
3515
|
+
}
|
|
3516
|
+
const generatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
3517
|
+
const products = [];
|
|
3518
|
+
let pageCount = 0;
|
|
3519
|
+
let expectedTotal = null;
|
|
3520
|
+
let firstPage = null;
|
|
3521
|
+
for (let offset = 0; ; ) {
|
|
3522
|
+
const includeBootstrapData = Boolean(bootstrap && offset === 0);
|
|
3523
|
+
const requestLimit = includeBootstrapData ? Math.max(pageSize, initialPageSize) : pageSize;
|
|
3524
|
+
const page = await loadPage({
|
|
3525
|
+
categoryListKey: options.categoryListKey,
|
|
3526
|
+
attributeListKey: options.attributeListKey,
|
|
3527
|
+
sortBy,
|
|
3528
|
+
limit: requestLimit,
|
|
3529
|
+
offset,
|
|
3530
|
+
include: {
|
|
3531
|
+
products: true,
|
|
3532
|
+
detailVariants: true,
|
|
3533
|
+
categories: includeBootstrapData,
|
|
3534
|
+
attributes: true,
|
|
3535
|
+
filters: includeBootstrapData,
|
|
3536
|
+
brands: includeBootstrapData,
|
|
3537
|
+
debug: false
|
|
3538
|
+
}
|
|
3539
|
+
});
|
|
3540
|
+
if (offset === 0) firstPage = page;
|
|
3541
|
+
pageCount += 1;
|
|
3542
|
+
const pageTotal = page.pagination.total;
|
|
3543
|
+
if (expectedTotal === null) {
|
|
3544
|
+
expectedTotal = pageTotal;
|
|
3545
|
+
} else if (pageTotal !== expectedTotal) {
|
|
3546
|
+
throwCatalogChanged();
|
|
3547
|
+
}
|
|
3548
|
+
if (pageTotal > maxProducts) {
|
|
3549
|
+
throw new WebsiteError(
|
|
3550
|
+
`Static catalog contains ${pageTotal} products and exceeds maxProducts ${maxProducts}.`,
|
|
3551
|
+
"STATIC_CATALOG_LIMIT_EXCEEDED"
|
|
3552
|
+
);
|
|
3553
|
+
}
|
|
3554
|
+
products.push(...page.products);
|
|
3555
|
+
if (!page.pagination.hasMore) break;
|
|
3556
|
+
if (page.products.length === 0) {
|
|
3557
|
+
throw new WebsiteError(
|
|
3558
|
+
"Static catalog pagination did not advance.",
|
|
3559
|
+
"STATIC_CATALOG_PAGINATION_STALLED"
|
|
3560
|
+
);
|
|
3561
|
+
}
|
|
3562
|
+
offset += page.products.length;
|
|
3563
|
+
}
|
|
3564
|
+
const total = expectedTotal ?? 0;
|
|
3565
|
+
const uniqueProductIds = new Set(products.map((product) => product.id));
|
|
3566
|
+
if (products.length !== total || uniqueProductIds.size !== total) {
|
|
3567
|
+
throwCatalogChanged();
|
|
3568
|
+
}
|
|
3569
|
+
const snapshot = {
|
|
3570
|
+
schemaVersion: 1,
|
|
3571
|
+
snapshotId: `${websiteId}:${generatedAt}`,
|
|
3572
|
+
generatedAt,
|
|
3573
|
+
websiteId,
|
|
3574
|
+
products,
|
|
3575
|
+
total,
|
|
3576
|
+
pageSize,
|
|
3577
|
+
pageCount
|
|
2570
3578
|
};
|
|
3579
|
+
const initialPage = bootstrap && firstPage ? {
|
|
3580
|
+
...firstPage,
|
|
3581
|
+
products: firstPage.products.slice(0, initialPageSize),
|
|
3582
|
+
pagination: {
|
|
3583
|
+
...firstPage.pagination,
|
|
3584
|
+
limit: initialPageSize,
|
|
3585
|
+
offset: 0,
|
|
3586
|
+
hasMore: total > initialPageSize
|
|
3587
|
+
}
|
|
3588
|
+
} : null;
|
|
3589
|
+
return { snapshot, initialPage };
|
|
3590
|
+
}
|
|
3591
|
+
function throwCatalogChanged() {
|
|
3592
|
+
throw new WebsiteError(
|
|
3593
|
+
"Static catalog changed while it was being collected. Retry the build to get a consistent snapshot.",
|
|
3594
|
+
"STATIC_CATALOG_CHANGED_DURING_BUILD"
|
|
3595
|
+
);
|
|
2571
3596
|
}
|
|
2572
3597
|
|
|
2573
3598
|
// libs/sdk/src/modules/catalog.ts
|
|
2574
3599
|
var MAX_AVAILABILITY_VARIANT_IDS2 = 100;
|
|
2575
3600
|
var DEFAULT_FILTER_ATTRIBUTE_LIST_KEY = "atributos";
|
|
3601
|
+
var MAX_LIVE_STATE_VARIANT_IDS = 100;
|
|
3602
|
+
var STATIC_CATALOG_RETRY = {
|
|
3603
|
+
maxAttempts: 4,
|
|
3604
|
+
maxTotalWaitMs: 3 * 60 * 1e3,
|
|
3605
|
+
jitterRatio: 0.1
|
|
3606
|
+
};
|
|
2576
3607
|
function mapPrice(raw) {
|
|
2577
3608
|
return {
|
|
2578
3609
|
id: raw.id,
|
|
@@ -2605,6 +3636,9 @@ function getProductImages(media) {
|
|
|
2605
3636
|
function getProductVideos(media) {
|
|
2606
3637
|
return getProductMediaByKind(media, "video");
|
|
2607
3638
|
}
|
|
3639
|
+
function getProductDetailVariants(product) {
|
|
3640
|
+
return product.detailVariants ?? product.variants;
|
|
3641
|
+
}
|
|
2608
3642
|
function getPrimaryProductImage(media) {
|
|
2609
3643
|
return getProductImages(media)[0] ?? null;
|
|
2610
3644
|
}
|
|
@@ -2754,7 +3788,6 @@ function mapRelatedProduct(raw, storageUrl) {
|
|
|
2754
3788
|
slug: raw.related_product.slug,
|
|
2755
3789
|
name: raw.related_product.name,
|
|
2756
3790
|
sku: raw.related_product.sku,
|
|
2757
|
-
displayLabel: raw.display_label,
|
|
2758
3791
|
sortOrder: raw.sort_order ?? 0,
|
|
2759
3792
|
media: raw.related_product.product_media.map(
|
|
2760
3793
|
(m) => mapMedia(m, storageUrl)
|
|
@@ -2762,6 +3795,26 @@ function mapRelatedProduct(raw, storageUrl) {
|
|
|
2762
3795
|
};
|
|
2763
3796
|
}
|
|
2764
3797
|
function mapProduct(raw, storageUrl, labels, chainId) {
|
|
3798
|
+
const detailVariants = raw.product_variants.map((variant) => mapVariant(variant, labels, chainId)).filter((variant) => variant.pricing.currentPrice != null);
|
|
3799
|
+
const listingVariantIds = raw.listing_variant_ids ? new Set(raw.listing_variant_ids) : null;
|
|
3800
|
+
const variants = listingVariantIds ? detailVariants.filter((variant) => listingVariantIds.has(variant.id)) : detailVariants;
|
|
3801
|
+
const rawOptionGroup = raw.product_option_group_members?.[0]?.group;
|
|
3802
|
+
const optionGroup = rawOptionGroup && rawOptionGroup.members.length >= 2 ? {
|
|
3803
|
+
id: rawOptionGroup.id,
|
|
3804
|
+
title: rawOptionGroup.display_title,
|
|
3805
|
+
members: rawOptionGroup.members.map((member) => ({
|
|
3806
|
+
id: member.id,
|
|
3807
|
+
productId: member.product_id,
|
|
3808
|
+
slug: member.product.slug,
|
|
3809
|
+
name: member.product.name,
|
|
3810
|
+
sku: member.product.sku,
|
|
3811
|
+
displayLabel: member.display_label,
|
|
3812
|
+
sortOrder: member.sort_order ?? 0,
|
|
3813
|
+
media: member.product.product_media.map(
|
|
3814
|
+
(media) => mapMedia(media, storageUrl)
|
|
3815
|
+
)
|
|
3816
|
+
}))
|
|
3817
|
+
} : null;
|
|
2765
3818
|
return {
|
|
2766
3819
|
id: raw.id,
|
|
2767
3820
|
name: raw.name,
|
|
@@ -2778,16 +3831,17 @@ function mapProduct(raw, storageUrl, labels, chainId) {
|
|
|
2778
3831
|
measurementUnit: raw.measurement_unit,
|
|
2779
3832
|
createdAt: raw.created_at,
|
|
2780
3833
|
updatedAt: raw.updated_at ?? raw.created_at,
|
|
2781
|
-
variants
|
|
2782
|
-
|
|
2783
|
-
),
|
|
3834
|
+
variants,
|
|
3835
|
+
detailVariants,
|
|
2784
3836
|
media: raw.product_media.map((m) => mapMedia(m, storageUrl)),
|
|
2785
3837
|
attributes: raw.product_attributes.map(
|
|
2786
3838
|
(attribute) => mapProductAttribute(attribute, labels)
|
|
2787
3839
|
),
|
|
2788
3840
|
relatedProducts: (raw.product_related_products ?? []).map(
|
|
2789
3841
|
(relation) => mapRelatedProduct(relation, storageUrl)
|
|
2790
|
-
)
|
|
3842
|
+
),
|
|
3843
|
+
optionGroup,
|
|
3844
|
+
specification: raw.product_specification ?? null
|
|
2791
3845
|
};
|
|
2792
3846
|
}
|
|
2793
3847
|
function mapCategory(raw) {
|
|
@@ -3118,7 +4172,7 @@ function hasMeaningfulCategoryListFilters(filters) {
|
|
|
3118
4172
|
)
|
|
3119
4173
|
);
|
|
3120
4174
|
}
|
|
3121
|
-
async function getWebsiteAvailabilityByVariantId(graphqlUrl, getHeaders, variantIds, chainId) {
|
|
4175
|
+
async function getWebsiteAvailabilityByVariantId(graphqlUrl, getHeaders, variantIds, chainId, websiteId) {
|
|
3122
4176
|
const uniqueVariantIds = uniqueValues2(variantIds);
|
|
3123
4177
|
const availabilityByVariantId = /* @__PURE__ */ new Map();
|
|
3124
4178
|
for (const chunk of chunkArray2(
|
|
@@ -3128,7 +4182,7 @@ async function getWebsiteAvailabilityByVariantId(graphqlUrl, getHeaders, variant
|
|
|
3128
4182
|
const data = await executeGraphQL(
|
|
3129
4183
|
graphqlUrl,
|
|
3130
4184
|
CHECK_AVAILABILITY,
|
|
3131
|
-
{ variantIds: chunk, chainId },
|
|
4185
|
+
{ variantIds: chunk, chainId, websiteId },
|
|
3132
4186
|
getHeaders
|
|
3133
4187
|
);
|
|
3134
4188
|
for (const item of data.websiteProductAvailability ?? []) {
|
|
@@ -3139,7 +4193,7 @@ async function getWebsiteAvailabilityByVariantId(graphqlUrl, getHeaders, variant
|
|
|
3139
4193
|
}
|
|
3140
4194
|
return availabilityByVariantId;
|
|
3141
4195
|
}
|
|
3142
|
-
async function filterProductsForWebsiteStock(graphqlUrl, getHeaders, products, chainId) {
|
|
4196
|
+
async function filterProductsForWebsiteStock(graphqlUrl, getHeaders, products, chainId, websiteId) {
|
|
3143
4197
|
const variantIds = products.flatMap(
|
|
3144
4198
|
(product) => product.variants.map((variant) => variant.id)
|
|
3145
4199
|
);
|
|
@@ -3150,7 +4204,8 @@ async function filterProductsForWebsiteStock(graphqlUrl, getHeaders, products, c
|
|
|
3150
4204
|
graphqlUrl,
|
|
3151
4205
|
getHeaders,
|
|
3152
4206
|
variantIds,
|
|
3153
|
-
chainId
|
|
4207
|
+
chainId,
|
|
4208
|
+
websiteId
|
|
3154
4209
|
)).entries()
|
|
3155
4210
|
).filter(([, item]) => item.inStock).map(([variantId]) => variantId)
|
|
3156
4211
|
);
|
|
@@ -3168,7 +4223,7 @@ async function filterProductsForWebsiteStock(graphqlUrl, getHeaders, products, c
|
|
|
3168
4223
|
)
|
|
3169
4224
|
})).filter((product) => product.variants.length > 0);
|
|
3170
4225
|
}
|
|
3171
|
-
async function filterSeoProductsForWebsiteStock(graphqlUrl, getHeaders, products, chainId) {
|
|
4226
|
+
async function filterSeoProductsForWebsiteStock(graphqlUrl, getHeaders, products, chainId, websiteId) {
|
|
3172
4227
|
const variantIds = products.flatMap(
|
|
3173
4228
|
(product) => product.product_variants.map((variant) => variant.id)
|
|
3174
4229
|
);
|
|
@@ -3179,7 +4234,8 @@ async function filterSeoProductsForWebsiteStock(graphqlUrl, getHeaders, products
|
|
|
3179
4234
|
graphqlUrl,
|
|
3180
4235
|
getHeaders,
|
|
3181
4236
|
variantIds,
|
|
3182
|
-
chainId
|
|
4237
|
+
chainId,
|
|
4238
|
+
websiteId
|
|
3183
4239
|
)).entries()
|
|
3184
4240
|
).filter(([, item]) => item.inStock).map(([variantId]) => variantId)
|
|
3185
4241
|
);
|
|
@@ -3210,6 +4266,32 @@ function mapProductSeoEntry(raw, storageUrl) {
|
|
|
3210
4266
|
};
|
|
3211
4267
|
}
|
|
3212
4268
|
function createCatalogModule(graphqlUrl, getHeaders, getContext, storageUrl) {
|
|
4269
|
+
const loadProductSpecifications = async (productIds) => {
|
|
4270
|
+
const ctx = getContext();
|
|
4271
|
+
const uniqueProductIds = [...new Set(productIds)];
|
|
4272
|
+
const specifications = {};
|
|
4273
|
+
for (let offset = 0; offset < uniqueProductIds.length; offset += 100) {
|
|
4274
|
+
const batch = uniqueProductIds.slice(offset, offset + 100);
|
|
4275
|
+
try {
|
|
4276
|
+
const data = await executeGraphQL(
|
|
4277
|
+
graphqlUrl,
|
|
4278
|
+
WEBSITE_GET_PRODUCT_SPECIFICATIONS,
|
|
4279
|
+
{ websiteId: ctx.websiteId, productIds: batch },
|
|
4280
|
+
getHeaders
|
|
4281
|
+
);
|
|
4282
|
+
if (!data.result?.success) continue;
|
|
4283
|
+
Object.assign(
|
|
4284
|
+
specifications,
|
|
4285
|
+
parseActionJson(
|
|
4286
|
+
data.result.specifications,
|
|
4287
|
+
{}
|
|
4288
|
+
)
|
|
4289
|
+
);
|
|
4290
|
+
} catch {
|
|
4291
|
+
}
|
|
4292
|
+
}
|
|
4293
|
+
return specifications;
|
|
4294
|
+
};
|
|
3213
4295
|
const mapSingleProductResult = async (data) => {
|
|
3214
4296
|
const ctx = getContext();
|
|
3215
4297
|
if (!ctx.chainId) {
|
|
@@ -3223,15 +4305,187 @@ function createCatalogModule(graphqlUrl, getHeaders, getContext, storageUrl) {
|
|
|
3223
4305
|
const publicAttributeLabels = buildPublicAttributeLabels(
|
|
3224
4306
|
data.catalog_attribute_lists
|
|
3225
4307
|
);
|
|
4308
|
+
const mappedProduct = mapProduct(
|
|
4309
|
+
product,
|
|
4310
|
+
storageUrl,
|
|
4311
|
+
publicAttributeLabels,
|
|
4312
|
+
ctx.chainId
|
|
4313
|
+
);
|
|
3226
4314
|
const filteredProducts = await filterProductsForWebsiteStock(
|
|
3227
4315
|
graphqlUrl,
|
|
3228
4316
|
getHeaders,
|
|
3229
|
-
[
|
|
3230
|
-
ctx.chainId
|
|
4317
|
+
[mappedProduct],
|
|
4318
|
+
ctx.chainId,
|
|
4319
|
+
ctx.websiteId
|
|
3231
4320
|
);
|
|
3232
|
-
|
|
4321
|
+
if (!filteredProducts[0]) return null;
|
|
4322
|
+
const specifications = await loadProductSpecifications([product.id]);
|
|
4323
|
+
return {
|
|
4324
|
+
...mappedProduct,
|
|
4325
|
+
variants: getProductDetailVariants(mappedProduct),
|
|
4326
|
+
specification: specifications[product.id] ?? null
|
|
4327
|
+
};
|
|
4328
|
+
};
|
|
4329
|
+
const loadCatalogPage = async (opts, retryRateLimits = false) => {
|
|
4330
|
+
const ctx = getContext();
|
|
4331
|
+
if (!ctx.chainId) {
|
|
4332
|
+
throw new WebsiteError(
|
|
4333
|
+
"This website has no chain configured. Catalog requires a chain_id.",
|
|
4334
|
+
"NO_CHAIN"
|
|
4335
|
+
);
|
|
4336
|
+
}
|
|
4337
|
+
const chainId = ctx.chainId;
|
|
4338
|
+
const filters = {
|
|
4339
|
+
category_slug: opts.categorySlug,
|
|
4340
|
+
subcategory_slug: opts.subcategorySlug,
|
|
4341
|
+
tags: opts.tags,
|
|
4342
|
+
search: opts.search,
|
|
4343
|
+
brand_id: opts.brandId,
|
|
4344
|
+
min_price: opts.minPrice,
|
|
4345
|
+
max_price: opts.maxPrice,
|
|
4346
|
+
attributes_by_slug: opts.attributesBySlug,
|
|
4347
|
+
technical_by_field: opts.technicalByField ? Object.fromEntries(
|
|
4348
|
+
Object.entries(opts.technicalByField).map(([fieldId, filter]) => [
|
|
4349
|
+
fieldId,
|
|
4350
|
+
{
|
|
4351
|
+
option_ids: filter.optionIds,
|
|
4352
|
+
boolean_values: filter.booleanValues,
|
|
4353
|
+
min: filter.min,
|
|
4354
|
+
max: filter.max,
|
|
4355
|
+
date_from: filter.dateFrom,
|
|
4356
|
+
date_to: filter.dateTo
|
|
4357
|
+
}
|
|
4358
|
+
])
|
|
4359
|
+
) : void 0,
|
|
4360
|
+
sort_by: opts.sortBy,
|
|
4361
|
+
limit: opts.limit,
|
|
4362
|
+
offset: opts.offset
|
|
4363
|
+
};
|
|
4364
|
+
const data = await executeGraphQL(
|
|
4365
|
+
graphqlUrl,
|
|
4366
|
+
WEBSITE_GET_CATALOG_PAGE,
|
|
4367
|
+
{
|
|
4368
|
+
websiteId: ctx.websiteId,
|
|
4369
|
+
categoryListKey: opts.categoryListKey,
|
|
4370
|
+
attributeListKey: opts.attributeListKey,
|
|
4371
|
+
filters,
|
|
4372
|
+
include: opts.include
|
|
4373
|
+
},
|
|
4374
|
+
getHeaders,
|
|
4375
|
+
retryRateLimits ? { retry: STATIC_CATALOG_RETRY } : void 0
|
|
4376
|
+
);
|
|
4377
|
+
if (!data.result.success) {
|
|
4378
|
+
throw new WebsiteError(
|
|
4379
|
+
data.result.errors.join("; ") || "Unable to resolve catalog page",
|
|
4380
|
+
"CATALOG_PAGE_ERROR"
|
|
4381
|
+
);
|
|
4382
|
+
}
|
|
4383
|
+
const products = parseActionJson(data.result.products, []);
|
|
4384
|
+
const categoryList = parseActionJson(
|
|
4385
|
+
data.result.category_list,
|
|
4386
|
+
null
|
|
4387
|
+
);
|
|
4388
|
+
const attributeList = parseActionJson(
|
|
4389
|
+
data.result.attribute_list,
|
|
4390
|
+
null
|
|
4391
|
+
);
|
|
4392
|
+
const filtersResult = parseActionJson(
|
|
4393
|
+
data.result.filters,
|
|
4394
|
+
null
|
|
4395
|
+
);
|
|
4396
|
+
const brands = parseActionJson(data.result.brands, []);
|
|
4397
|
+
const pagination = parseActionJson(
|
|
4398
|
+
data.result.pagination,
|
|
4399
|
+
{
|
|
4400
|
+
limit: opts.limit ?? 24,
|
|
4401
|
+
offset: opts.offset ?? 0,
|
|
4402
|
+
total: 0,
|
|
4403
|
+
hasMore: false
|
|
4404
|
+
}
|
|
4405
|
+
);
|
|
4406
|
+
const appliedFilters = parseActionJson(
|
|
4407
|
+
data.result.applied_filters,
|
|
4408
|
+
{}
|
|
4409
|
+
);
|
|
4410
|
+
const debug = parseActionJson(
|
|
4411
|
+
data.result.debug,
|
|
4412
|
+
null
|
|
4413
|
+
);
|
|
4414
|
+
const labels = buildPublicAttributeLabels(
|
|
4415
|
+
attributeList ? [attributeList] : []
|
|
4416
|
+
);
|
|
4417
|
+
return {
|
|
4418
|
+
products: products.map(
|
|
4419
|
+
(product) => mapProduct(product, storageUrl, labels, chainId)
|
|
4420
|
+
),
|
|
4421
|
+
categories: categoryList ? mapCategoryList(categoryList) : null,
|
|
4422
|
+
attributes: attributeList ? mapAttributeList(attributeList) : null,
|
|
4423
|
+
filters: filtersResult,
|
|
4424
|
+
brands,
|
|
4425
|
+
pagination,
|
|
4426
|
+
appliedFilters,
|
|
4427
|
+
debug
|
|
4428
|
+
};
|
|
3233
4429
|
};
|
|
3234
4430
|
return {
|
|
4431
|
+
async getProductLiveState(productVariantIds) {
|
|
4432
|
+
const ctx = getContext();
|
|
4433
|
+
if (!ctx.chainId) {
|
|
4434
|
+
throw new WebsiteError(
|
|
4435
|
+
"This website has no chain configured. Product live state requires a chain_id.",
|
|
4436
|
+
"NO_CHAIN"
|
|
4437
|
+
);
|
|
4438
|
+
}
|
|
4439
|
+
const chainId = ctx.chainId;
|
|
4440
|
+
const variantIds = [...new Set(productVariantIds)];
|
|
4441
|
+
if (variantIds.length === 0) return [];
|
|
4442
|
+
if (variantIds.length > MAX_LIVE_STATE_VARIANT_IDS) {
|
|
4443
|
+
throw new WebsiteError(
|
|
4444
|
+
`Product live state accepts at most ${MAX_LIVE_STATE_VARIANT_IDS} variants per request.`,
|
|
4445
|
+
"PRODUCT_LIVE_STATE_LIMIT_EXCEEDED"
|
|
4446
|
+
);
|
|
4447
|
+
}
|
|
4448
|
+
const data = await executeGraphQL(
|
|
4449
|
+
graphqlUrl,
|
|
4450
|
+
GET_PRODUCT_LIVE_STATE,
|
|
4451
|
+
{
|
|
4452
|
+
variantIds,
|
|
4453
|
+
companyId: ctx.companyId,
|
|
4454
|
+
chainId,
|
|
4455
|
+
websiteId: ctx.websiteId
|
|
4456
|
+
},
|
|
4457
|
+
getHeaders
|
|
4458
|
+
);
|
|
4459
|
+
const variantsById = new Map(
|
|
4460
|
+
(data.product_variants ?? []).map((variant) => [variant.id, variant])
|
|
4461
|
+
);
|
|
4462
|
+
const availabilityById = new Map(
|
|
4463
|
+
(data.websiteProductAvailability ?? []).map((availability) => [
|
|
4464
|
+
availability.product_variant_id,
|
|
4465
|
+
availability
|
|
4466
|
+
])
|
|
4467
|
+
);
|
|
4468
|
+
return variantIds.flatMap((variantId) => {
|
|
4469
|
+
const variant = variantsById.get(variantId);
|
|
4470
|
+
if (!variant) return [];
|
|
4471
|
+
const availability = availabilityById.get(variantId);
|
|
4472
|
+
const pricing = resolveProductPricing(
|
|
4473
|
+
selectPublicPrices(
|
|
4474
|
+
variant.product_variant_prices.map(mapPrice),
|
|
4475
|
+
chainId
|
|
4476
|
+
)
|
|
4477
|
+
);
|
|
4478
|
+
return [
|
|
4479
|
+
{
|
|
4480
|
+
productVariantId: variantId,
|
|
4481
|
+
inStock: availability?.in_stock ?? false,
|
|
4482
|
+
availabilityLabel: availability?.label ?? "Agotado",
|
|
4483
|
+
maxPurchasableQuantity: availability?.max_purchasable_quantity ?? 0,
|
|
4484
|
+
pricing
|
|
4485
|
+
}
|
|
4486
|
+
];
|
|
4487
|
+
});
|
|
4488
|
+
},
|
|
3235
4489
|
async getProductRecommendations(opts) {
|
|
3236
4490
|
const ctx = getContext();
|
|
3237
4491
|
const limit = opts.limit ?? 16;
|
|
@@ -3294,7 +4548,8 @@ function createCatalogModule(graphqlUrl, getHeaders, getContext, storageUrl) {
|
|
|
3294
4548
|
graphqlUrl,
|
|
3295
4549
|
getHeaders,
|
|
3296
4550
|
data.products ?? [],
|
|
3297
|
-
ctx.chainId
|
|
4551
|
+
ctx.chainId,
|
|
4552
|
+
ctx.websiteId
|
|
3298
4553
|
);
|
|
3299
4554
|
const entries = availableProducts.slice(0, limit).map((product) => mapProductSeoEntry(product, storageUrl)).filter((entry) => Boolean(entry));
|
|
3300
4555
|
return {
|
|
@@ -3306,86 +4561,36 @@ function createCatalogModule(graphqlUrl, getHeaders, getContext, storageUrl) {
|
|
|
3306
4561
|
}
|
|
3307
4562
|
};
|
|
3308
4563
|
},
|
|
3309
|
-
|
|
4564
|
+
getCatalogPage: loadCatalogPage,
|
|
4565
|
+
async getStaticCatalogSnapshot(opts) {
|
|
3310
4566
|
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
|
|
4567
|
+
return (await collectStaticCatalog({
|
|
4568
|
+
websiteId: ctx.websiteId,
|
|
4569
|
+
snapshot: opts,
|
|
4570
|
+
loadPage: (options) => loadCatalogPage(options, true)
|
|
4571
|
+
})).snapshot;
|
|
4572
|
+
},
|
|
4573
|
+
async getStaticCatalogBootstrap(opts) {
|
|
4574
|
+
const ctx = getContext();
|
|
4575
|
+
const { snapshot, initialPage } = await collectStaticCatalog({
|
|
4576
|
+
websiteId: ctx.websiteId,
|
|
4577
|
+
snapshot: opts,
|
|
4578
|
+
bootstrap: {
|
|
4579
|
+
initialPageSize: opts.initialPageSize,
|
|
4580
|
+
sortBy: opts.sortBy
|
|
3340
4581
|
},
|
|
3341
|
-
|
|
3342
|
-
);
|
|
3343
|
-
if (!
|
|
4582
|
+
loadPage: (options) => loadCatalogPage(options, true)
|
|
4583
|
+
});
|
|
4584
|
+
if (!initialPage) {
|
|
3344
4585
|
throw new WebsiteError(
|
|
3345
|
-
|
|
3346
|
-
"
|
|
4586
|
+
"Static catalog bootstrap could not resolve its initial page.",
|
|
4587
|
+
"STATIC_CATALOG_BOOTSTRAP_MISSING_INITIAL_PAGE"
|
|
3347
4588
|
);
|
|
3348
4589
|
}
|
|
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
4590
|
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
|
|
4591
|
+
...snapshot,
|
|
4592
|
+
bootstrapSchemaVersion: 1,
|
|
4593
|
+
initialPage
|
|
3389
4594
|
};
|
|
3390
4595
|
},
|
|
3391
4596
|
async getProducts(opts) {
|
|
@@ -3420,8 +4625,16 @@ function createCatalogModule(graphqlUrl, getHeaders, getContext, storageUrl) {
|
|
|
3420
4625
|
(data.products ?? []).map(
|
|
3421
4626
|
(p) => mapProduct(p, storageUrl, publicAttributeLabels, chainId)
|
|
3422
4627
|
),
|
|
3423
|
-
chainId
|
|
4628
|
+
chainId,
|
|
4629
|
+
ctx.websiteId
|
|
3424
4630
|
);
|
|
4631
|
+
const specifications = await loadProductSpecifications(
|
|
4632
|
+
products.map((product) => product.id)
|
|
4633
|
+
);
|
|
4634
|
+
products = products.map((product) => ({
|
|
4635
|
+
...product,
|
|
4636
|
+
specification: specifications[product.id] ?? null
|
|
4637
|
+
}));
|
|
3425
4638
|
if (opts?.minPrice !== void 0 || opts?.maxPrice !== void 0) {
|
|
3426
4639
|
products = products.filter(
|
|
3427
4640
|
(product) => product.variants.some((variant) => {
|
|
@@ -3532,7 +4745,8 @@ function createCatalogModule(graphqlUrl, getHeaders, getContext, storageUrl) {
|
|
|
3532
4745
|
graphqlUrl,
|
|
3533
4746
|
getHeaders,
|
|
3534
4747
|
variantIds,
|
|
3535
|
-
chainId
|
|
4748
|
+
chainId,
|
|
4749
|
+
ctx.websiteId
|
|
3536
4750
|
);
|
|
3537
4751
|
const applicableCategories = /* @__PURE__ */ new Set();
|
|
3538
4752
|
const applicableSubcategoryIds = /* @__PURE__ */ new Set();
|
|
@@ -3650,7 +4864,8 @@ function createCatalogModule(graphqlUrl, getHeaders, getContext, storageUrl) {
|
|
|
3650
4864
|
graphqlUrl,
|
|
3651
4865
|
getHeaders,
|
|
3652
4866
|
variantIds,
|
|
3653
|
-
chainId
|
|
4867
|
+
chainId,
|
|
4868
|
+
ctx.websiteId
|
|
3654
4869
|
);
|
|
3655
4870
|
const publicAttributeLabels = buildPublicAttributeLabels(
|
|
3656
4871
|
data.catalog_attribute_lists
|
|
@@ -3755,7 +4970,8 @@ function createCatalogModule(graphqlUrl, getHeaders, getContext, storageUrl) {
|
|
|
3755
4970
|
min: minPrice,
|
|
3756
4971
|
max: maxPrice
|
|
3757
4972
|
},
|
|
3758
|
-
attributes
|
|
4973
|
+
attributes,
|
|
4974
|
+
technical: []
|
|
3759
4975
|
};
|
|
3760
4976
|
},
|
|
3761
4977
|
async getSearchSuggestions(opts) {
|
|
@@ -3809,6 +5025,14 @@ function mapCartItem2(raw) {
|
|
|
3809
5025
|
id: raw.id,
|
|
3810
5026
|
productVariantId: raw.product_variant_id,
|
|
3811
5027
|
quantity: raw.quantity,
|
|
5028
|
+
unitPrice: raw.unit_price ?? null,
|
|
5029
|
+
lineTotal: raw.line_total ?? null,
|
|
5030
|
+
baseUnitPrice: raw.base_unit_price ?? raw.unit_price ?? null,
|
|
5031
|
+
unitSavings: raw.unit_savings ?? 0,
|
|
5032
|
+
lineSavings: raw.line_savings ?? 0,
|
|
5033
|
+
volumePricingEligible: raw.volume_pricing_eligible ?? false,
|
|
5034
|
+
resolvedPriceType: raw.resolved_price_type ?? null,
|
|
5035
|
+
volumePricingMinQuantity: raw.volume_pricing_min_quantity ?? null,
|
|
3812
5036
|
productVariant: {
|
|
3813
5037
|
sku: raw.product_variant.sku,
|
|
3814
5038
|
product: raw.product_variant.product,
|
|
@@ -3833,7 +5057,20 @@ function mapCart2(raw) {
|
|
|
3833
5057
|
},
|
|
3834
5058
|
status: raw.status,
|
|
3835
5059
|
createdAt: raw.created_at,
|
|
3836
|
-
updatedAt: raw.updated_at
|
|
5060
|
+
updatedAt: raw.updated_at,
|
|
5061
|
+
volumePricing: raw.volume_pricing ? {
|
|
5062
|
+
...raw.volume_pricing,
|
|
5063
|
+
totalSavings: raw.volume_pricing.totalSavings ?? 0
|
|
5064
|
+
} : {
|
|
5065
|
+
enabled: false,
|
|
5066
|
+
eligibleQuantity: 0,
|
|
5067
|
+
appliedPriceType: null,
|
|
5068
|
+
appliedMinQuantity: null,
|
|
5069
|
+
nextPriceType: null,
|
|
5070
|
+
nextMinQuantity: null,
|
|
5071
|
+
remainingQuantity: null,
|
|
5072
|
+
totalSavings: 0
|
|
5073
|
+
}
|
|
3837
5074
|
};
|
|
3838
5075
|
}
|
|
3839
5076
|
function mapCouponResult(raw) {
|
|
@@ -3852,6 +5089,7 @@ function mapCouponResult(raw) {
|
|
|
3852
5089
|
};
|
|
3853
5090
|
}
|
|
3854
5091
|
function createCheckoutModule(graphqlUrl, getHeaders, getContext, cartSessionStorage) {
|
|
5092
|
+
let shippingPolicyCache = null;
|
|
3855
5093
|
return {
|
|
3856
5094
|
async getLocationOptions() {
|
|
3857
5095
|
const context = getContext();
|
|
@@ -3949,22 +5187,39 @@ function createCheckoutModule(graphqlUrl, getHeaders, getContext, cartSessionSto
|
|
|
3949
5187
|
}));
|
|
3950
5188
|
},
|
|
3951
5189
|
async getShippingPolicy() {
|
|
5190
|
+
const now = Date.now();
|
|
5191
|
+
if (shippingPolicyCache && shippingPolicyCache.expiresAt > now) {
|
|
5192
|
+
return shippingPolicyCache.request;
|
|
5193
|
+
}
|
|
3952
5194
|
const context = getContext();
|
|
3953
|
-
const
|
|
5195
|
+
const request = executeGraphQL(
|
|
3954
5196
|
graphqlUrl,
|
|
3955
5197
|
GET_SHIPPING_POLICY,
|
|
3956
5198
|
{ websiteId: context.websiteId },
|
|
3957
5199
|
getHeaders
|
|
3958
|
-
)
|
|
3959
|
-
|
|
3960
|
-
|
|
3961
|
-
|
|
3962
|
-
|
|
3963
|
-
|
|
3964
|
-
|
|
3965
|
-
|
|
3966
|
-
|
|
5200
|
+
).then((data) => {
|
|
5201
|
+
const result = data.websiteGetShippingPolicy;
|
|
5202
|
+
if (!result.success || !result.policy) {
|
|
5203
|
+
throw new CheckoutError(result.errors ?? ["SHIPPING_POLICY_FAILED"]);
|
|
5204
|
+
}
|
|
5205
|
+
return {
|
|
5206
|
+
freeShippingEnabled: result.policy.free_shipping_enabled,
|
|
5207
|
+
freeShippingThreshold: result.policy.free_shipping_threshold,
|
|
5208
|
+
currency: result.policy.currency
|
|
5209
|
+
};
|
|
5210
|
+
});
|
|
5211
|
+
shippingPolicyCache = {
|
|
5212
|
+
expiresAt: now + 5 * 60 * 1e3,
|
|
5213
|
+
request
|
|
3967
5214
|
};
|
|
5215
|
+
try {
|
|
5216
|
+
return await request;
|
|
5217
|
+
} catch (error) {
|
|
5218
|
+
if (shippingPolicyCache?.request === request) {
|
|
5219
|
+
shippingPolicyCache = null;
|
|
5220
|
+
}
|
|
5221
|
+
throw error;
|
|
5222
|
+
}
|
|
3968
5223
|
},
|
|
3969
5224
|
async getFreeShippingProgress(input) {
|
|
3970
5225
|
if (input.items.length === 0) {
|
|
@@ -4130,6 +5385,13 @@ function createCheckoutModule(graphqlUrl, getHeaders, getContext, cartSessionSto
|
|
|
4130
5385
|
initialPayment: input.initialPayment ? {
|
|
4131
5386
|
amount: input.initialPayment.amount,
|
|
4132
5387
|
payment_method_id: input.initialPayment.paymentMethodId
|
|
5388
|
+
} : void 0,
|
|
5389
|
+
marketingAttribution: input.marketingAttribution ? serializeWebsiteMarketingAttribution(input.marketingAttribution) : void 0,
|
|
5390
|
+
advertisingConsent: input.advertisingConsent ? {
|
|
5391
|
+
policy_id: input.advertisingConsent.policyId,
|
|
5392
|
+
policy_version: input.advertisingConsent.policyVersion,
|
|
5393
|
+
statement_hash: input.advertisingConsent.statementHash,
|
|
5394
|
+
granted: input.advertisingConsent.granted
|
|
4133
5395
|
} : void 0
|
|
4134
5396
|
},
|
|
4135
5397
|
getHeaders
|
|
@@ -4265,18 +5527,23 @@ function readOptionalText(value) {
|
|
|
4265
5527
|
function enrichMediaValue(value, storageUrl) {
|
|
4266
5528
|
const fileId = typeof value.fileId === "string" && value.fileId.trim() ? value.fileId.trim() : "";
|
|
4267
5529
|
if (!fileId) return value;
|
|
4268
|
-
const
|
|
5530
|
+
const isProductMedia = typeof value.id === "string" && typeof value.order === "number";
|
|
5531
|
+
const isEditorialImage = value.mediaKind === "image" && !isProductMedia;
|
|
5532
|
+
const originalWidth = typeof value.originalWidth === "number" ? value.originalWidth : null;
|
|
5533
|
+
const urls = isEditorialImage ? buildStorageContentMediaUrls(storageUrl, fileId, originalWidth) : buildStorageMediaUrls(storageUrl, fileId);
|
|
4269
5534
|
return {
|
|
4270
5535
|
...value,
|
|
4271
5536
|
...urls,
|
|
4272
|
-
url: value.url ?? urls.originalUrl
|
|
5537
|
+
url: value.url ?? urls.originalUrl,
|
|
5538
|
+
src: value.src ?? value.url ?? urls.originalUrl
|
|
4273
5539
|
};
|
|
4274
5540
|
}
|
|
4275
5541
|
function normalizeRichTextNode(value, storageUrl) {
|
|
4276
5542
|
const type = value.type;
|
|
4277
5543
|
const attrs = isRecord(value.attrs) ? value.attrs : null;
|
|
4278
5544
|
if (attrs && (type === "bacanoImage" || type === "bacanoVideo") && typeof attrs.fileId === "string") {
|
|
4279
|
-
const
|
|
5545
|
+
const originalWidth = typeof attrs.originalWidth === "number" ? attrs.originalWidth : null;
|
|
5546
|
+
const urls = type === "bacanoImage" ? buildStorageContentMediaUrls(storageUrl, attrs.fileId, originalWidth) : buildStorageMediaUrls(storageUrl, attrs.fileId);
|
|
4280
5547
|
const altText = readOptionalText(attrs.altText) ?? readOptionalText(attrs.alt);
|
|
4281
5548
|
const caption = readOptionalText(attrs.caption);
|
|
4282
5549
|
const showCaption = Boolean(caption) && attrs.showCaption !== false;
|
|
@@ -4408,6 +5675,452 @@ function createContentModule(graphqlUrl, getHeaders, getContext, storageUrl) {
|
|
|
4408
5675
|
};
|
|
4409
5676
|
}
|
|
4410
5677
|
|
|
5678
|
+
// libs/sdk/src/marketing/clarity.ts
|
|
5679
|
+
var initializedDestinations = /* @__PURE__ */ new Set();
|
|
5680
|
+
function clarityWindow() {
|
|
5681
|
+
return typeof window === "undefined" ? null : window;
|
|
5682
|
+
}
|
|
5683
|
+
function prepareClarity(projectId) {
|
|
5684
|
+
const target = clarityWindow();
|
|
5685
|
+
if (!target || initializedDestinations.has(projectId)) return;
|
|
5686
|
+
if (!target.clarity) {
|
|
5687
|
+
const clarity = function() {
|
|
5688
|
+
clarity.q ??= [];
|
|
5689
|
+
clarity.q.push(arguments);
|
|
5690
|
+
};
|
|
5691
|
+
target.clarity = clarity;
|
|
5692
|
+
}
|
|
5693
|
+
initializedDestinations.add(projectId);
|
|
5694
|
+
}
|
|
5695
|
+
function loadClarityScript(projectId) {
|
|
5696
|
+
if (typeof document === "undefined") return;
|
|
5697
|
+
const id = `bacano-clarity-${projectId}`;
|
|
5698
|
+
if (document.getElementById(id)) return;
|
|
5699
|
+
const script = document.createElement("script");
|
|
5700
|
+
script.id = id;
|
|
5701
|
+
script.async = true;
|
|
5702
|
+
script.src = `https://www.clarity.ms/tag/${encodeURIComponent(projectId)}`;
|
|
5703
|
+
script.referrerPolicy = "strict-origin-when-cross-origin";
|
|
5704
|
+
document.head.appendChild(script);
|
|
5705
|
+
}
|
|
5706
|
+
function sendClarityEvent(eventName) {
|
|
5707
|
+
clarityWindow()?.clarity?.("event", eventName);
|
|
5708
|
+
}
|
|
5709
|
+
|
|
5710
|
+
// libs/sdk/src/marketing/domain.ts
|
|
5711
|
+
function matchesCanonicalMarketingHost(canonicalDomain, currentHostname) {
|
|
5712
|
+
if (canonicalDomain === void 0) return true;
|
|
5713
|
+
if (!canonicalDomain?.trim()) return false;
|
|
5714
|
+
try {
|
|
5715
|
+
const configuredHost = new URL(
|
|
5716
|
+
canonicalDomain.includes("://") ? canonicalDomain : `https://${canonicalDomain}`
|
|
5717
|
+
).hostname.toLowerCase();
|
|
5718
|
+
return currentHostname.toLowerCase() === configuredHost;
|
|
5719
|
+
} catch {
|
|
5720
|
+
return false;
|
|
5721
|
+
}
|
|
5722
|
+
}
|
|
5723
|
+
|
|
5724
|
+
// libs/sdk/src/marketing/ga4.ts
|
|
5725
|
+
function mapGa4Items(items) {
|
|
5726
|
+
return items?.map((item) => ({
|
|
5727
|
+
item_id: item.itemId,
|
|
5728
|
+
item_name: item.itemName,
|
|
5729
|
+
item_variant: item.itemVariant,
|
|
5730
|
+
item_brand: item.itemBrand,
|
|
5731
|
+
item_category: item.itemCategory,
|
|
5732
|
+
price: item.price,
|
|
5733
|
+
quantity: item.quantity,
|
|
5734
|
+
index: item.index,
|
|
5735
|
+
sku: item.sku
|
|
5736
|
+
}));
|
|
5737
|
+
}
|
|
5738
|
+
|
|
5739
|
+
// libs/sdk/src/marketing/types.ts
|
|
5740
|
+
var MARKETING_SCHEMA_VERSION = 1;
|
|
5741
|
+
var WEBSITE_MARKETING_CONTACT_PLACEMENTS = [
|
|
5742
|
+
"product_detail",
|
|
5743
|
+
"floating",
|
|
5744
|
+
"header",
|
|
5745
|
+
"footer",
|
|
5746
|
+
"contact_page"
|
|
5747
|
+
];
|
|
5748
|
+
|
|
5749
|
+
// libs/sdk/src/marketing/runtime.ts
|
|
5750
|
+
var initializedGa4Destinations = /* @__PURE__ */ new Set();
|
|
5751
|
+
var initializedMetaDestinations = /* @__PURE__ */ new Set();
|
|
5752
|
+
function marketingWindow() {
|
|
5753
|
+
return typeof window === "undefined" ? null : window;
|
|
5754
|
+
}
|
|
5755
|
+
function loadScriptOnce(id, source) {
|
|
5756
|
+
if (typeof document === "undefined" || document.getElementById(id)) return;
|
|
5757
|
+
const script = document.createElement("script");
|
|
5758
|
+
script.id = id;
|
|
5759
|
+
script.async = true;
|
|
5760
|
+
script.src = source;
|
|
5761
|
+
script.referrerPolicy = "strict-origin-when-cross-origin";
|
|
5762
|
+
document.head.appendChild(script);
|
|
5763
|
+
}
|
|
5764
|
+
function prepareGa4(measurementId) {
|
|
5765
|
+
const target = marketingWindow();
|
|
5766
|
+
if (!target) return;
|
|
5767
|
+
if (initializedGa4Destinations.has(measurementId)) return;
|
|
5768
|
+
target.dataLayer ??= [];
|
|
5769
|
+
target.gtag ??= function() {
|
|
5770
|
+
target.dataLayer?.push(arguments);
|
|
5771
|
+
};
|
|
5772
|
+
target.gtag("js", /* @__PURE__ */ new Date());
|
|
5773
|
+
target.gtag("config", measurementId, { send_page_view: false });
|
|
5774
|
+
initializedGa4Destinations.add(measurementId);
|
|
5775
|
+
}
|
|
5776
|
+
function loadGa4Script(measurementId) {
|
|
5777
|
+
loadScriptOnce(
|
|
5778
|
+
`bacano-ga4-${measurementId}`,
|
|
5779
|
+
`https://www.googletagmanager.com/gtag/js?id=${encodeURIComponent(measurementId)}`
|
|
5780
|
+
);
|
|
5781
|
+
}
|
|
5782
|
+
function captureGa4ClientId(measurementId, websiteId, diagnostic) {
|
|
5783
|
+
const target = marketingWindow();
|
|
5784
|
+
target?.gtag?.("get", measurementId, "client_id", (clientId) => {
|
|
5785
|
+
if (storeWebsiteMarketingGa4ClientId(websiteId, clientId)) return;
|
|
5786
|
+
diagnostic?.({
|
|
5787
|
+
level: "warning",
|
|
5788
|
+
code: "GA4_CLIENT_ID_UNAVAILABLE",
|
|
5789
|
+
message: "GA4 did not provide a valid browser client identifier"
|
|
5790
|
+
});
|
|
5791
|
+
});
|
|
5792
|
+
}
|
|
5793
|
+
function captureGa4SessionId(measurementId, websiteId, diagnostic) {
|
|
5794
|
+
const target = marketingWindow();
|
|
5795
|
+
target?.gtag?.("get", measurementId, "session_id", (sessionId) => {
|
|
5796
|
+
if (storeWebsiteMarketingGa4SessionId(websiteId, sessionId)) return;
|
|
5797
|
+
diagnostic?.({
|
|
5798
|
+
level: "warning",
|
|
5799
|
+
code: "GA4_SESSION_ID_UNAVAILABLE",
|
|
5800
|
+
message: "GA4 did not provide a valid browser session identifier"
|
|
5801
|
+
});
|
|
5802
|
+
});
|
|
5803
|
+
}
|
|
5804
|
+
function prepareMeta(pixelId) {
|
|
5805
|
+
const target = marketingWindow();
|
|
5806
|
+
if (!target) return;
|
|
5807
|
+
if (initializedMetaDestinations.has(pixelId)) return;
|
|
5808
|
+
if (!target.fbq) {
|
|
5809
|
+
const queue = [];
|
|
5810
|
+
const fbq = ((...args) => {
|
|
5811
|
+
if (fbq.callMethod) {
|
|
5812
|
+
fbq.callMethod(...args);
|
|
5813
|
+
return;
|
|
5814
|
+
}
|
|
5815
|
+
queue.push(args);
|
|
5816
|
+
});
|
|
5817
|
+
fbq.push = fbq;
|
|
5818
|
+
fbq.loaded = true;
|
|
5819
|
+
fbq.version = "2.0";
|
|
5820
|
+
fbq.queue = queue;
|
|
5821
|
+
target.fbq = fbq;
|
|
5822
|
+
target._fbq = fbq;
|
|
5823
|
+
}
|
|
5824
|
+
target.fbq("init", pixelId);
|
|
5825
|
+
initializedMetaDestinations.add(pixelId);
|
|
5826
|
+
}
|
|
5827
|
+
function loadMetaScript() {
|
|
5828
|
+
loadScriptOnce(
|
|
5829
|
+
"bacano-meta-pixel",
|
|
5830
|
+
"https://connect.facebook.net/en_US/fbevents.js"
|
|
5831
|
+
);
|
|
5832
|
+
}
|
|
5833
|
+
function scheduleScriptLoad(load, strategy) {
|
|
5834
|
+
const target = marketingWindow();
|
|
5835
|
+
if (!target || strategy !== "idle") {
|
|
5836
|
+
load();
|
|
5837
|
+
return () => void 0;
|
|
5838
|
+
}
|
|
5839
|
+
let completed = false;
|
|
5840
|
+
let idleHandle = null;
|
|
5841
|
+
let timeoutHandle = null;
|
|
5842
|
+
const interactionEvents = ["pointerdown", "keydown", "touchstart"];
|
|
5843
|
+
const cleanup = () => {
|
|
5844
|
+
for (const eventName of interactionEvents) {
|
|
5845
|
+
target.removeEventListener(eventName, finish, true);
|
|
5846
|
+
}
|
|
5847
|
+
if (idleHandle !== null) target.cancelIdleCallback?.(idleHandle);
|
|
5848
|
+
if (timeoutHandle !== null) target.clearTimeout(timeoutHandle);
|
|
5849
|
+
};
|
|
5850
|
+
const finish = () => {
|
|
5851
|
+
if (completed) return;
|
|
5852
|
+
completed = true;
|
|
5853
|
+
cleanup();
|
|
5854
|
+
load();
|
|
5855
|
+
};
|
|
5856
|
+
for (const eventName of interactionEvents) {
|
|
5857
|
+
target.addEventListener(eventName, finish, {
|
|
5858
|
+
capture: true,
|
|
5859
|
+
once: true,
|
|
5860
|
+
passive: true
|
|
5861
|
+
});
|
|
5862
|
+
}
|
|
5863
|
+
if (target.requestIdleCallback) {
|
|
5864
|
+
idleHandle = target.requestIdleCallback(finish, { timeout: 2e3 });
|
|
5865
|
+
} else {
|
|
5866
|
+
timeoutHandle = target.setTimeout(finish, 1500);
|
|
5867
|
+
}
|
|
5868
|
+
return cleanup;
|
|
5869
|
+
}
|
|
5870
|
+
function metaEventName(eventName) {
|
|
5871
|
+
const standard = {
|
|
5872
|
+
page_view: "PageView",
|
|
5873
|
+
search: "Search",
|
|
5874
|
+
view_item: "ViewContent",
|
|
5875
|
+
add_to_cart: "AddToCart",
|
|
5876
|
+
begin_checkout: "InitiateCheckout",
|
|
5877
|
+
add_payment_info: "AddPaymentInfo",
|
|
5878
|
+
contact: "Contact"
|
|
5879
|
+
};
|
|
5880
|
+
return standard[eventName] ?? eventName;
|
|
5881
|
+
}
|
|
5882
|
+
function isWebsiteMarketingContactPlacement(value) {
|
|
5883
|
+
return WEBSITE_MARKETING_CONTACT_PLACEMENTS.some(
|
|
5884
|
+
(candidate) => candidate === value
|
|
5885
|
+
);
|
|
5886
|
+
}
|
|
5887
|
+
function contactPlacementFor(event) {
|
|
5888
|
+
const placement = event.contactPlacement;
|
|
5889
|
+
if (event.eventName !== "contact" || !isWebsiteMarketingContactPlacement(placement)) {
|
|
5890
|
+
return void 0;
|
|
5891
|
+
}
|
|
5892
|
+
return placement;
|
|
5893
|
+
}
|
|
5894
|
+
function sendGa4(event, diagnosticMode) {
|
|
5895
|
+
const target = marketingWindow();
|
|
5896
|
+
const contactPlacement = contactPlacementFor(event);
|
|
5897
|
+
target?.gtag?.("event", event.eventName, {
|
|
5898
|
+
currency: event.currency,
|
|
5899
|
+
value: event.value,
|
|
5900
|
+
search_term: event.searchTerm,
|
|
5901
|
+
item_list_id: event.itemListId,
|
|
5902
|
+
item_list_name: event.itemListName,
|
|
5903
|
+
shipping_tier: event.shippingTier,
|
|
5904
|
+
payment_type: event.paymentType,
|
|
5905
|
+
method: event.contactMethod,
|
|
5906
|
+
...contactPlacement ? { contact_placement: contactPlacement } : void 0,
|
|
5907
|
+
page_location: event.pageLocation,
|
|
5908
|
+
page_title: event.pageTitle,
|
|
5909
|
+
items: mapGa4Items(event.items),
|
|
5910
|
+
event_id: event.eventId,
|
|
5911
|
+
debug_mode: diagnosticMode ? true : void 0
|
|
5912
|
+
});
|
|
5913
|
+
}
|
|
5914
|
+
function sendMeta(event) {
|
|
5915
|
+
const target = marketingWindow();
|
|
5916
|
+
const eventName = metaEventName(event.eventName);
|
|
5917
|
+
const isStandard = eventName !== event.eventName;
|
|
5918
|
+
const contactPlacement = contactPlacementFor(event);
|
|
5919
|
+
target?.fbq?.(
|
|
5920
|
+
isStandard ? "track" : "trackCustom",
|
|
5921
|
+
eventName,
|
|
5922
|
+
{
|
|
5923
|
+
currency: event.currency,
|
|
5924
|
+
value: event.value,
|
|
5925
|
+
search_string: event.searchTerm,
|
|
5926
|
+
content_ids: event.items?.map((item) => item.itemId),
|
|
5927
|
+
contents: event.items?.map((item) => ({
|
|
5928
|
+
id: item.itemId,
|
|
5929
|
+
quantity: item.quantity ?? 1,
|
|
5930
|
+
item_price: item.price
|
|
5931
|
+
})),
|
|
5932
|
+
content_type: event.items?.length ? "product" : void 0,
|
|
5933
|
+
contact_method: event.contactMethod,
|
|
5934
|
+
...contactPlacement ? { contact_placement: contactPlacement } : void 0
|
|
5935
|
+
},
|
|
5936
|
+
{ eventID: event.eventId }
|
|
5937
|
+
);
|
|
5938
|
+
}
|
|
5939
|
+
function newEventId() {
|
|
5940
|
+
return typeof crypto !== "undefined" && "randomUUID" in crypto ? crypto.randomUUID() : `evt_${Date.now()}_${Math.random().toString(16).slice(2)}`;
|
|
5941
|
+
}
|
|
5942
|
+
function createWebsiteMarketingRuntime(options) {
|
|
5943
|
+
let destroyed = false;
|
|
5944
|
+
let cancelScheduledLoad = () => void 0;
|
|
5945
|
+
const target = marketingWindow();
|
|
5946
|
+
const enabled = options.config.enabled && Boolean(
|
|
5947
|
+
target && matchesCanonicalMarketingHost(
|
|
5948
|
+
options.canonicalDomain,
|
|
5949
|
+
target.location.hostname
|
|
5950
|
+
)
|
|
5951
|
+
) && Boolean(
|
|
5952
|
+
options.config.destinations.ga4 || options.config.destinations.meta || options.config.destinations.clarity
|
|
5953
|
+
);
|
|
5954
|
+
if (enabled) {
|
|
5955
|
+
try {
|
|
5956
|
+
if (options.config.destinations.ga4) {
|
|
5957
|
+
prepareGa4(options.config.destinations.ga4.measurementId);
|
|
5958
|
+
}
|
|
5959
|
+
if (options.config.destinations.meta) {
|
|
5960
|
+
prepareMeta(options.config.destinations.meta.pixelId);
|
|
5961
|
+
}
|
|
5962
|
+
if (options.config.destinations.clarity) {
|
|
5963
|
+
prepareClarity(options.config.destinations.clarity.projectId);
|
|
5964
|
+
}
|
|
5965
|
+
cancelScheduledLoad = scheduleScriptLoad(() => {
|
|
5966
|
+
if (destroyed) return;
|
|
5967
|
+
if (options.config.destinations.ga4) {
|
|
5968
|
+
loadGa4Script(options.config.destinations.ga4.measurementId);
|
|
5969
|
+
}
|
|
5970
|
+
if (options.config.destinations.meta) loadMetaScript();
|
|
5971
|
+
if (options.config.destinations.clarity) {
|
|
5972
|
+
loadClarityScript(options.config.destinations.clarity.projectId);
|
|
5973
|
+
}
|
|
5974
|
+
}, options.scriptLoadStrategy);
|
|
5975
|
+
captureWebsiteMarketingAttribution(
|
|
5976
|
+
options.websiteId,
|
|
5977
|
+
options.config.attributionWindowDays
|
|
5978
|
+
);
|
|
5979
|
+
if (options.config.destinations.ga4) {
|
|
5980
|
+
captureGa4ClientId(
|
|
5981
|
+
options.config.destinations.ga4.measurementId,
|
|
5982
|
+
options.websiteId,
|
|
5983
|
+
options.diagnostic
|
|
5984
|
+
);
|
|
5985
|
+
captureGa4SessionId(
|
|
5986
|
+
options.config.destinations.ga4.measurementId,
|
|
5987
|
+
options.websiteId,
|
|
5988
|
+
options.diagnostic
|
|
5989
|
+
);
|
|
5990
|
+
}
|
|
5991
|
+
if (options.config.destinations.meta) {
|
|
5992
|
+
refreshWebsiteMarketingMetaContext(options.websiteId);
|
|
5993
|
+
}
|
|
5994
|
+
} catch (error) {
|
|
5995
|
+
options.diagnostic?.({
|
|
5996
|
+
level: "warning",
|
|
5997
|
+
code: "RUNTIME_INITIALIZATION_FAILED",
|
|
5998
|
+
message: error instanceof Error ? error.message : "Initialization failed"
|
|
5999
|
+
});
|
|
6000
|
+
}
|
|
6001
|
+
}
|
|
6002
|
+
return {
|
|
6003
|
+
enabled,
|
|
6004
|
+
track(input) {
|
|
6005
|
+
if (!enabled || destroyed) return;
|
|
6006
|
+
const event = {
|
|
6007
|
+
...input,
|
|
6008
|
+
schemaVersion: 1,
|
|
6009
|
+
eventId: input.eventId ?? newEventId(),
|
|
6010
|
+
occurredAt: input.occurredAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
6011
|
+
currency: input.currency ?? options.config.currencyCode,
|
|
6012
|
+
pageLocation: sanitizeMarketingUrl(
|
|
6013
|
+
input.pageLocation ?? (typeof window === "undefined" ? "" : window.location.href)
|
|
6014
|
+
)
|
|
6015
|
+
};
|
|
6016
|
+
try {
|
|
6017
|
+
if (options.config.destinations.ga4) {
|
|
6018
|
+
sendGa4(event, options.config.destinations.ga4.diagnosticMode);
|
|
6019
|
+
}
|
|
6020
|
+
if (options.config.destinations.meta) sendMeta(event);
|
|
6021
|
+
if (options.config.destinations.clarity) {
|
|
6022
|
+
sendClarityEvent(event.eventName);
|
|
6023
|
+
}
|
|
6024
|
+
options.diagnostic?.({
|
|
6025
|
+
level: "debug",
|
|
6026
|
+
code: "EVENT_DISPATCHED",
|
|
6027
|
+
message: "Marketing event dispatched",
|
|
6028
|
+
eventName: event.eventName
|
|
6029
|
+
});
|
|
6030
|
+
} catch (error) {
|
|
6031
|
+
options.diagnostic?.({
|
|
6032
|
+
level: "warning",
|
|
6033
|
+
code: "EVENT_DISPATCH_FAILED",
|
|
6034
|
+
message: error instanceof Error ? error.message : "Dispatch failed",
|
|
6035
|
+
eventName: event.eventName
|
|
6036
|
+
});
|
|
6037
|
+
}
|
|
6038
|
+
},
|
|
6039
|
+
captureAttribution() {
|
|
6040
|
+
if (!enabled) return null;
|
|
6041
|
+
captureWebsiteMarketingAttribution(
|
|
6042
|
+
options.websiteId,
|
|
6043
|
+
options.config.attributionWindowDays
|
|
6044
|
+
);
|
|
6045
|
+
if (options.config.destinations.meta) {
|
|
6046
|
+
refreshWebsiteMarketingMetaContext(options.websiteId);
|
|
6047
|
+
}
|
|
6048
|
+
return getWebsiteMarketingAttribution(options.websiteId);
|
|
6049
|
+
},
|
|
6050
|
+
getAttribution() {
|
|
6051
|
+
if (!enabled) return null;
|
|
6052
|
+
if (options.config.destinations.meta) {
|
|
6053
|
+
refreshWebsiteMarketingMetaContext(options.websiteId);
|
|
6054
|
+
}
|
|
6055
|
+
return getWebsiteMarketingAttribution(options.websiteId);
|
|
6056
|
+
},
|
|
6057
|
+
destroy() {
|
|
6058
|
+
destroyed = true;
|
|
6059
|
+
cancelScheduledLoad();
|
|
6060
|
+
}
|
|
6061
|
+
};
|
|
6062
|
+
}
|
|
6063
|
+
|
|
6064
|
+
// libs/sdk/src/modules/marketing.ts
|
|
6065
|
+
function readRetryAfter(response) {
|
|
6066
|
+
const value = Number(response.headers.get("Retry-After") ?? "1");
|
|
6067
|
+
return Number.isFinite(value) && value >= 0 ? Math.ceil(value) : 1;
|
|
6068
|
+
}
|
|
6069
|
+
function createMarketingModule(apiUrl, websiteSlug, getContext) {
|
|
6070
|
+
return {
|
|
6071
|
+
async captureLead(input) {
|
|
6072
|
+
const context = getContext();
|
|
6073
|
+
const attribution = getWebsiteMarketingAttribution(context.websiteId);
|
|
6074
|
+
let response;
|
|
6075
|
+
try {
|
|
6076
|
+
response = await fetch(
|
|
6077
|
+
`${apiUrl.replace(/\/+$/, "")}/api/v1/website/campaign-captures`,
|
|
6078
|
+
{
|
|
6079
|
+
method: "POST",
|
|
6080
|
+
headers: { "Content-Type": "application/json" },
|
|
6081
|
+
body: JSON.stringify({
|
|
6082
|
+
websiteSlug,
|
|
6083
|
+
campaignKey: input.campaignKey,
|
|
6084
|
+
idempotencyKey: input.idempotencyKey,
|
|
6085
|
+
source: input.source ?? "WEBSITE",
|
|
6086
|
+
commercialConsent: input.commercialConsent,
|
|
6087
|
+
contact: input.contact,
|
|
6088
|
+
answers: input.answers ?? {},
|
|
6089
|
+
attribution: attribution ? serializeWebsiteMarketingAttribution(attribution) : null
|
|
6090
|
+
})
|
|
6091
|
+
}
|
|
6092
|
+
);
|
|
6093
|
+
} catch (error) {
|
|
6094
|
+
throw new WebsiteError(
|
|
6095
|
+
`Could not capture the campaign lead: ${error instanceof Error ? error.message : String(error)}`,
|
|
6096
|
+
"NETWORK_ERROR"
|
|
6097
|
+
);
|
|
6098
|
+
}
|
|
6099
|
+
if (response.status === 429) {
|
|
6100
|
+
throw new RateLimitError(
|
|
6101
|
+
"Rate limit exceeded. Please try again later.",
|
|
6102
|
+
readRetryAfter(response)
|
|
6103
|
+
);
|
|
6104
|
+
}
|
|
6105
|
+
if (!response.ok) {
|
|
6106
|
+
throw new WebsiteError(
|
|
6107
|
+
`Could not capture the campaign lead (status ${response.status})`,
|
|
6108
|
+
response.status === 403 ? "ORIGIN_NOT_ALLOWED" : "INVALID_RESPONSE",
|
|
6109
|
+
{ status: response.status }
|
|
6110
|
+
);
|
|
6111
|
+
}
|
|
6112
|
+
const result = await response.json();
|
|
6113
|
+
if (typeof result.accepted !== "boolean" || typeof result.message !== "string") {
|
|
6114
|
+
throw new WebsiteError(
|
|
6115
|
+
"Campaign capture returned an invalid response",
|
|
6116
|
+
"INVALID_RESPONSE"
|
|
6117
|
+
);
|
|
6118
|
+
}
|
|
6119
|
+
return { accepted: result.accepted, message: result.message };
|
|
6120
|
+
}
|
|
6121
|
+
};
|
|
6122
|
+
}
|
|
6123
|
+
|
|
4411
6124
|
// libs/sdk/src/modules/orders.ts
|
|
4412
6125
|
function mapOrderItem(raw) {
|
|
4413
6126
|
return {
|
|
@@ -4582,13 +6295,13 @@ function createOrdersModule(graphqlUrl, getHeaders, getContext) {
|
|
|
4582
6295
|
)) {
|
|
4583
6296
|
return order;
|
|
4584
6297
|
}
|
|
4585
|
-
await
|
|
6298
|
+
await wait2(intervalMs);
|
|
4586
6299
|
}
|
|
4587
6300
|
throw new CheckoutError(["PAYMENT_STATUS_TIMEOUT"]);
|
|
4588
6301
|
}
|
|
4589
6302
|
};
|
|
4590
6303
|
}
|
|
4591
|
-
function
|
|
6304
|
+
function wait2(ms) {
|
|
4592
6305
|
return new Promise((resolve) => {
|
|
4593
6306
|
setTimeout(resolve, ms);
|
|
4594
6307
|
});
|
|
@@ -4747,6 +6460,9 @@ function createProfileModule(graphqlUrl, getHeaders) {
|
|
|
4747
6460
|
}
|
|
4748
6461
|
|
|
4749
6462
|
// libs/sdk/src/modules/website.ts
|
|
6463
|
+
var WEBSITE_CONTEXT_CACHE_TTL_MS = 5 * 60 * 1e3;
|
|
6464
|
+
var WEBSITE_CONTEXT_CACHE_MAX_ENTRIES = 100;
|
|
6465
|
+
var websiteContextCache = /* @__PURE__ */ new Map();
|
|
4750
6466
|
function mapWebsite(raw) {
|
|
4751
6467
|
return {
|
|
4752
6468
|
websiteId: raw.id,
|
|
@@ -4760,10 +6476,7 @@ function mapWebsite(raw) {
|
|
|
4760
6476
|
}
|
|
4761
6477
|
async function resolveWebsite(graphqlUrl, slug, headers) {
|
|
4762
6478
|
if (!slug.trim()) {
|
|
4763
|
-
throw new WebsiteError(
|
|
4764
|
-
"websiteSlug is required",
|
|
4765
|
-
"INVALID_CONFIG"
|
|
4766
|
-
);
|
|
6479
|
+
throw new WebsiteError("websiteSlug is required", "INVALID_CONFIG");
|
|
4767
6480
|
}
|
|
4768
6481
|
const data = await executeGraphQL(
|
|
4769
6482
|
graphqlUrl,
|
|
@@ -4779,6 +6492,31 @@ async function resolveWebsite(graphqlUrl, slug, headers) {
|
|
|
4779
6492
|
}
|
|
4780
6493
|
return mapWebsite(data.websites[0]);
|
|
4781
6494
|
}
|
|
6495
|
+
function resolveWebsiteCached(graphqlUrl, slug, headers) {
|
|
6496
|
+
const normalizedSlug = slug.trim();
|
|
6497
|
+
const cacheKey = `${graphqlUrl}::${normalizedSlug}`;
|
|
6498
|
+
const now = Date.now();
|
|
6499
|
+
const cached = websiteContextCache.get(cacheKey);
|
|
6500
|
+
if (cached && cached.expiresAt > now) return cached.request;
|
|
6501
|
+
if (cached) websiteContextCache.delete(cacheKey);
|
|
6502
|
+
const request = resolveWebsite(graphqlUrl, normalizedSlug, headers).catch(
|
|
6503
|
+
(error) => {
|
|
6504
|
+
if (websiteContextCache.get(cacheKey)?.request === request) {
|
|
6505
|
+
websiteContextCache.delete(cacheKey);
|
|
6506
|
+
}
|
|
6507
|
+
throw error;
|
|
6508
|
+
}
|
|
6509
|
+
);
|
|
6510
|
+
if (websiteContextCache.size >= WEBSITE_CONTEXT_CACHE_MAX_ENTRIES) {
|
|
6511
|
+
const oldestKey = websiteContextCache.keys().next().value;
|
|
6512
|
+
if (oldestKey) websiteContextCache.delete(oldestKey);
|
|
6513
|
+
}
|
|
6514
|
+
websiteContextCache.set(cacheKey, {
|
|
6515
|
+
expiresAt: now + WEBSITE_CONTEXT_CACHE_TTL_MS,
|
|
6516
|
+
request
|
|
6517
|
+
});
|
|
6518
|
+
return request;
|
|
6519
|
+
}
|
|
4782
6520
|
|
|
4783
6521
|
// libs/sdk/src/client.ts
|
|
4784
6522
|
function normalizeApiUrl(apiUrl) {
|
|
@@ -4789,10 +6527,17 @@ function createBacanoClient(config) {
|
|
|
4789
6527
|
throw new WebsiteError("apiUrl is required", "INVALID_CONFIG");
|
|
4790
6528
|
if (!config.websiteSlug?.trim())
|
|
4791
6529
|
throw new WebsiteError("websiteSlug is required", "INVALID_CONFIG");
|
|
6530
|
+
if (config.initialContext && config.initialContext.slug.trim() !== config.websiteSlug.trim()) {
|
|
6531
|
+
throw new WebsiteError(
|
|
6532
|
+
"initialContext slug must match websiteSlug",
|
|
6533
|
+
"INVALID_CONFIG"
|
|
6534
|
+
);
|
|
6535
|
+
}
|
|
4792
6536
|
const apiUrl = normalizeApiUrl(config.apiUrl);
|
|
4793
6537
|
const graphqlUrl = `${apiUrl}/api/v1/website/graphql`;
|
|
4794
6538
|
const storageUrl = `${apiUrl}/storage/v1`;
|
|
4795
|
-
let context = null;
|
|
6539
|
+
let context = config.initialContext ? { ...config.initialContext } : null;
|
|
6540
|
+
let initialization = null;
|
|
4796
6541
|
function getContext() {
|
|
4797
6542
|
if (!context) throw new NotInitializedError();
|
|
4798
6543
|
return context;
|
|
@@ -4837,13 +6582,27 @@ function createBacanoClient(config) {
|
|
|
4837
6582
|
getContext,
|
|
4838
6583
|
storageUrl
|
|
4839
6584
|
);
|
|
6585
|
+
const marketingModule = createMarketingModule(
|
|
6586
|
+
apiUrl,
|
|
6587
|
+
config.websiteSlug,
|
|
6588
|
+
getContext
|
|
6589
|
+
);
|
|
4840
6590
|
return {
|
|
4841
6591
|
async init() {
|
|
4842
|
-
context
|
|
4843
|
-
|
|
4844
|
-
|
|
4845
|
-
|
|
4846
|
-
|
|
6592
|
+
if (context) return;
|
|
6593
|
+
if (!initialization) {
|
|
6594
|
+
initialization = resolveWebsiteCached(
|
|
6595
|
+
graphqlUrl,
|
|
6596
|
+
config.websiteSlug,
|
|
6597
|
+
getWebsiteHeaders
|
|
6598
|
+
).then((resolvedContext) => {
|
|
6599
|
+
context = resolvedContext;
|
|
6600
|
+
}).catch((error) => {
|
|
6601
|
+
initialization = null;
|
|
6602
|
+
throw error;
|
|
6603
|
+
});
|
|
6604
|
+
}
|
|
6605
|
+
await initialization;
|
|
4847
6606
|
},
|
|
4848
6607
|
getContext,
|
|
4849
6608
|
auth: {
|
|
@@ -4858,8 +6617,11 @@ function createBacanoClient(config) {
|
|
|
4858
6617
|
},
|
|
4859
6618
|
catalog: {
|
|
4860
6619
|
getCatalogPage: catalogModule.getCatalogPage.bind(catalogModule),
|
|
6620
|
+
getStaticCatalogSnapshot: catalogModule.getStaticCatalogSnapshot.bind(catalogModule),
|
|
6621
|
+
getStaticCatalogBootstrap: catalogModule.getStaticCatalogBootstrap.bind(catalogModule),
|
|
4861
6622
|
getProductSeoEntries: catalogModule.getProductSeoEntries.bind(catalogModule),
|
|
4862
6623
|
getProductRecommendations: catalogModule.getProductRecommendations.bind(catalogModule),
|
|
6624
|
+
getProductLiveState: catalogModule.getProductLiveState.bind(catalogModule),
|
|
4863
6625
|
getProducts: catalogModule.getProducts.bind(catalogModule),
|
|
4864
6626
|
getProduct: catalogModule.getProduct.bind(catalogModule),
|
|
4865
6627
|
getProductBySlug: catalogModule.getProductBySlug.bind(catalogModule),
|
|
@@ -4884,7 +6646,8 @@ function createBacanoClient(config) {
|
|
|
4884
6646
|
setItem: cartModule.setItem.bind(cartModule),
|
|
4885
6647
|
addItem: cartModule.addItem.bind(cartModule),
|
|
4886
6648
|
updateItem: cartModule.updateItem.bind(cartModule),
|
|
4887
|
-
removeItem: cartModule.removeItem.bind(cartModule)
|
|
6649
|
+
removeItem: cartModule.removeItem.bind(cartModule),
|
|
6650
|
+
clear: cartModule.clear.bind(cartModule)
|
|
4888
6651
|
},
|
|
4889
6652
|
checkout: {
|
|
4890
6653
|
getLocationOptions: checkoutModule.getLocationOptions.bind(checkoutModule),
|
|
@@ -4911,6 +6674,9 @@ function createBacanoClient(config) {
|
|
|
4911
6674
|
get: profileModule.get.bind(profileModule),
|
|
4912
6675
|
getAddresses: profileModule.getAddresses.bind(profileModule),
|
|
4913
6676
|
update: profileModule.update.bind(profileModule)
|
|
6677
|
+
},
|
|
6678
|
+
marketing: {
|
|
6679
|
+
captureLead: marketingModule.captureLead.bind(marketingModule)
|
|
4914
6680
|
}
|
|
4915
6681
|
};
|
|
4916
6682
|
}
|
|
@@ -4918,16 +6684,30 @@ export {
|
|
|
4918
6684
|
AuthError,
|
|
4919
6685
|
CheckoutError,
|
|
4920
6686
|
GraphQLRequestError,
|
|
6687
|
+
MARKETING_SCHEMA_VERSION,
|
|
4921
6688
|
NotInitializedError,
|
|
6689
|
+
RateLimitError,
|
|
6690
|
+
WEBSITE_MARKETING_CONTACT_PLACEMENTS,
|
|
4922
6691
|
WebsiteError,
|
|
6692
|
+
buildStorageContentMediaUrls,
|
|
4923
6693
|
buildStorageFileUrl,
|
|
4924
6694
|
buildStorageMediaUrls,
|
|
6695
|
+
buildStorageResponsiveSources,
|
|
6696
|
+
buildStorageTransformedFileUrl,
|
|
6697
|
+
captureWebsiteMarketingAttribution,
|
|
4925
6698
|
createBacanoClient,
|
|
6699
|
+
createWebsiteMarketingRuntime,
|
|
6700
|
+
formatMediaSrcSet,
|
|
6701
|
+
getAllowedMarketingParameters,
|
|
4926
6702
|
getPrimaryProductImage,
|
|
6703
|
+
getProductDetailVariants,
|
|
4927
6704
|
getProductImages,
|
|
4928
6705
|
getProductMediaByKind,
|
|
4929
6706
|
getProductMediaKind,
|
|
4930
6707
|
getProductVideos,
|
|
6708
|
+
getWebsiteContextFromPublicConfig,
|
|
6709
|
+
getWebsiteMarketingAttribution,
|
|
4931
6710
|
getWebsitePublicConfig,
|
|
4932
|
-
resolveProductPricing
|
|
6711
|
+
resolveProductPricing,
|
|
6712
|
+
sanitizeMarketingUrl
|
|
4933
6713
|
};
|