@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.cjs
CHANGED
|
@@ -23,28 +23,55 @@ __export(index_exports, {
|
|
|
23
23
|
AuthError: () => AuthError,
|
|
24
24
|
CheckoutError: () => CheckoutError,
|
|
25
25
|
GraphQLRequestError: () => GraphQLRequestError,
|
|
26
|
+
MARKETING_SCHEMA_VERSION: () => MARKETING_SCHEMA_VERSION,
|
|
26
27
|
NotInitializedError: () => NotInitializedError,
|
|
28
|
+
RateLimitError: () => RateLimitError,
|
|
29
|
+
WEBSITE_MARKETING_CONTACT_PLACEMENTS: () => WEBSITE_MARKETING_CONTACT_PLACEMENTS,
|
|
27
30
|
WebsiteError: () => WebsiteError,
|
|
31
|
+
buildStorageContentMediaUrls: () => buildStorageContentMediaUrls,
|
|
28
32
|
buildStorageFileUrl: () => buildStorageFileUrl,
|
|
29
33
|
buildStorageMediaUrls: () => buildStorageMediaUrls,
|
|
34
|
+
buildStorageResponsiveSources: () => buildStorageResponsiveSources,
|
|
35
|
+
buildStorageTransformedFileUrl: () => buildStorageTransformedFileUrl,
|
|
36
|
+
captureWebsiteMarketingAttribution: () => captureWebsiteMarketingAttribution,
|
|
30
37
|
createBacanoClient: () => createBacanoClient,
|
|
38
|
+
createWebsiteMarketingRuntime: () => createWebsiteMarketingRuntime,
|
|
39
|
+
formatMediaSrcSet: () => formatMediaSrcSet,
|
|
40
|
+
getAllowedMarketingParameters: () => getAllowedMarketingParameters,
|
|
31
41
|
getPrimaryProductImage: () => getPrimaryProductImage,
|
|
42
|
+
getProductDetailVariants: () => getProductDetailVariants,
|
|
32
43
|
getProductImages: () => getProductImages,
|
|
33
44
|
getProductMediaByKind: () => getProductMediaByKind,
|
|
34
45
|
getProductMediaKind: () => getProductMediaKind,
|
|
35
46
|
getProductVideos: () => getProductVideos,
|
|
47
|
+
getWebsiteContextFromPublicConfig: () => getWebsiteContextFromPublicConfig,
|
|
48
|
+
getWebsiteMarketingAttribution: () => getWebsiteMarketingAttribution,
|
|
36
49
|
getWebsitePublicConfig: () => getWebsitePublicConfig,
|
|
37
|
-
resolveProductPricing: () => resolveProductPricing
|
|
50
|
+
resolveProductPricing: () => resolveProductPricing,
|
|
51
|
+
sanitizeMarketingUrl: () => sanitizeMarketingUrl
|
|
38
52
|
});
|
|
39
53
|
module.exports = __toCommonJS(index_exports);
|
|
40
54
|
|
|
41
55
|
// libs/sdk/src/errors.ts
|
|
42
56
|
var WebsiteError = class extends Error {
|
|
43
57
|
code;
|
|
44
|
-
|
|
58
|
+
status;
|
|
59
|
+
retryAfterSeconds;
|
|
60
|
+
constructor(message, code = "WEBSITE_ERROR", options) {
|
|
45
61
|
super(message);
|
|
46
62
|
this.name = "WebsiteError";
|
|
47
63
|
this.code = code;
|
|
64
|
+
this.status = options?.status ?? null;
|
|
65
|
+
this.retryAfterSeconds = options?.retryAfterSeconds ?? null;
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
var RateLimitError = class extends WebsiteError {
|
|
69
|
+
constructor(message, retryAfterSeconds) {
|
|
70
|
+
super(message, "RATE_LIMITED", {
|
|
71
|
+
status: 429,
|
|
72
|
+
retryAfterSeconds
|
|
73
|
+
});
|
|
74
|
+
this.name = "RateLimitError";
|
|
48
75
|
}
|
|
49
76
|
};
|
|
50
77
|
var NotInitializedError = class extends WebsiteError {
|
|
@@ -80,7 +107,109 @@ var GraphQLRequestError = class extends WebsiteError {
|
|
|
80
107
|
}
|
|
81
108
|
};
|
|
82
109
|
|
|
110
|
+
// libs/sdk/src/media.ts
|
|
111
|
+
var DEFAULT_RESPONSIVE_WIDTHS = [240, 384, 640, 960, 1400];
|
|
112
|
+
var CONTENT_HERO_WIDTHS = [640, 960, 1400, 1920, 2560];
|
|
113
|
+
var CONTENT_HERO_MAX_WIDTH = 1920;
|
|
114
|
+
var MEDIA_VARIANTS = {
|
|
115
|
+
thumbnail: { width: 240, quality: 75, format: "webp" },
|
|
116
|
+
card: { width: 640, quality: 80, format: "webp" },
|
|
117
|
+
detail: { width: 1400, quality: 85, format: "webp" }
|
|
118
|
+
};
|
|
119
|
+
function toSafeInteger(value) {
|
|
120
|
+
if (!Number.isFinite(value)) return void 0;
|
|
121
|
+
return Math.max(1, Math.floor(value));
|
|
122
|
+
}
|
|
123
|
+
function buildQuery(options) {
|
|
124
|
+
const params = new URLSearchParams();
|
|
125
|
+
const width = toSafeInteger(options.width);
|
|
126
|
+
const height = toSafeInteger(options.height);
|
|
127
|
+
const quality = toSafeInteger(options.quality);
|
|
128
|
+
if (width) params.set("w", String(width));
|
|
129
|
+
if (height) params.set("h", String(height));
|
|
130
|
+
if (quality) params.set("q", String(Math.min(100, quality)));
|
|
131
|
+
if (options.format) params.set("f", options.format);
|
|
132
|
+
const query = params.toString();
|
|
133
|
+
return query ? `?${query}` : "";
|
|
134
|
+
}
|
|
135
|
+
function buildStorageFileUrl(storageUrl, fileId, variant = "original") {
|
|
136
|
+
if (!storageUrl || !fileId) return "";
|
|
137
|
+
const baseUrl = `${storageUrl.replace(/\/+$/, "")}/files/${fileId}`;
|
|
138
|
+
if (variant === "original") return baseUrl;
|
|
139
|
+
return `${baseUrl}${buildQuery(MEDIA_VARIANTS[variant])}`;
|
|
140
|
+
}
|
|
141
|
+
function buildStorageTransformedFileUrl(storageUrl, fileId, options) {
|
|
142
|
+
if (!storageUrl || !fileId) return "";
|
|
143
|
+
const baseUrl = `${storageUrl.replace(/\/+$/, "")}/files/${fileId}`;
|
|
144
|
+
return `${baseUrl}${buildQuery(options)}`;
|
|
145
|
+
}
|
|
146
|
+
function buildStorageResponsiveSources(storageUrl, fileId, options = {}) {
|
|
147
|
+
const widths = options.widths ?? [...DEFAULT_RESPONSIVE_WIDTHS];
|
|
148
|
+
const uniqueWidths = [...new Set(widths.map(toSafeInteger))].filter((width) => Boolean(width)).sort((left, right) => left - right);
|
|
149
|
+
return uniqueWidths.map((width) => ({
|
|
150
|
+
width,
|
|
151
|
+
url: buildStorageTransformedFileUrl(storageUrl, fileId, {
|
|
152
|
+
width,
|
|
153
|
+
quality: options.quality ?? 80,
|
|
154
|
+
format: options.format ?? "webp"
|
|
155
|
+
})
|
|
156
|
+
}));
|
|
157
|
+
}
|
|
158
|
+
function formatMediaSrcSet(sources) {
|
|
159
|
+
return sources.filter((source) => source.url && source.width > 0).map((source) => `${source.url} ${source.width}w`).join(", ");
|
|
160
|
+
}
|
|
161
|
+
function buildStorageMediaUrls(storageUrl, fileId) {
|
|
162
|
+
return {
|
|
163
|
+
originalUrl: buildStorageFileUrl(storageUrl, fileId, "original"),
|
|
164
|
+
thumbnailUrl: buildStorageFileUrl(storageUrl, fileId, "thumbnail"),
|
|
165
|
+
cardUrl: buildStorageFileUrl(storageUrl, fileId, "card"),
|
|
166
|
+
detailUrl: buildStorageFileUrl(storageUrl, fileId, "detail"),
|
|
167
|
+
responsiveSources: buildStorageResponsiveSources(storageUrl, fileId)
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
function buildStorageContentMediaUrls(storageUrl, fileId, originalWidth) {
|
|
171
|
+
const legacyUrls = buildStorageMediaUrls(storageUrl, fileId);
|
|
172
|
+
const safeOriginalWidth = toSafeInteger(originalWidth ?? void 0);
|
|
173
|
+
if (!safeOriginalWidth) {
|
|
174
|
+
return {
|
|
175
|
+
...legacyUrls,
|
|
176
|
+
heroUrl: legacyUrls.originalUrl,
|
|
177
|
+
responsiveSources: []
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
const responsiveWidths = [
|
|
181
|
+
...CONTENT_HERO_WIDTHS.filter((width) => width <= safeOriginalWidth),
|
|
182
|
+
safeOriginalWidth
|
|
183
|
+
];
|
|
184
|
+
const heroWidth = Math.min(safeOriginalWidth, CONTENT_HERO_MAX_WIDTH);
|
|
185
|
+
return {
|
|
186
|
+
...legacyUrls,
|
|
187
|
+
heroUrl: buildStorageTransformedFileUrl(storageUrl, fileId, {
|
|
188
|
+
width: heroWidth,
|
|
189
|
+
quality: 88,
|
|
190
|
+
format: "webp"
|
|
191
|
+
}),
|
|
192
|
+
responsiveSources: buildStorageResponsiveSources(storageUrl, fileId, {
|
|
193
|
+
widths: responsiveWidths,
|
|
194
|
+
quality: 85,
|
|
195
|
+
format: "webp"
|
|
196
|
+
})
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
|
|
83
200
|
// libs/sdk/src/modules/auth.ts
|
|
201
|
+
function getWebsiteContextFromPublicConfig(config) {
|
|
202
|
+
if (config.contextSchemaVersion !== 1) return void 0;
|
|
203
|
+
return {
|
|
204
|
+
websiteId: config.websiteId,
|
|
205
|
+
companyId: config.companyId,
|
|
206
|
+
chainId: config.chainId ?? null,
|
|
207
|
+
name: config.name,
|
|
208
|
+
slug: config.slug,
|
|
209
|
+
domain: config.domain,
|
|
210
|
+
config: config.config ?? null
|
|
211
|
+
};
|
|
212
|
+
}
|
|
84
213
|
function createAuthHeaders(accessToken) {
|
|
85
214
|
return accessToken ? { Authorization: `Bearer ${accessToken}` } : {};
|
|
86
215
|
}
|
|
@@ -102,14 +231,18 @@ function createRequestHeadersProvider(websiteSlug, getAccessToken) {
|
|
|
102
231
|
}
|
|
103
232
|
async function getWebsitePublicConfig(options) {
|
|
104
233
|
const apiUrl = options.apiUrl.replace(/\/+$/, "");
|
|
105
|
-
const headers =
|
|
234
|
+
const headers = {};
|
|
235
|
+
if (options.origin) headers.Origin = options.origin;
|
|
236
|
+
if (options.previewToken) {
|
|
237
|
+
headers["X-Bacano-Preview-Token"] = options.previewToken;
|
|
238
|
+
}
|
|
106
239
|
const params = new URLSearchParams({ websiteSlug: options.websiteSlug });
|
|
107
240
|
let response;
|
|
108
241
|
try {
|
|
109
242
|
response = await fetch(
|
|
110
243
|
`${apiUrl}/api/v1/website/public-config?${params.toString()}`,
|
|
111
244
|
{
|
|
112
|
-
headers,
|
|
245
|
+
headers: Object.keys(headers).length > 0 ? headers : void 0,
|
|
113
246
|
cache: "no-store"
|
|
114
247
|
}
|
|
115
248
|
);
|
|
@@ -133,12 +266,80 @@ async function getWebsitePublicConfig(options) {
|
|
|
133
266
|
require_document: false,
|
|
134
267
|
document_types: []
|
|
135
268
|
};
|
|
269
|
+
const marketing = raw.marketing ?? {
|
|
270
|
+
schema_version: 1,
|
|
271
|
+
enabled: false,
|
|
272
|
+
currency_code: "COP",
|
|
273
|
+
time_zone: "America/Bogota",
|
|
274
|
+
attribution_window_days: 90,
|
|
275
|
+
consent_policy: null,
|
|
276
|
+
destinations: { ga4: null, meta: null, clarity: null }
|
|
277
|
+
};
|
|
278
|
+
const branding = raw.branding ?? {
|
|
279
|
+
schema_version: 1,
|
|
280
|
+
favicon: null
|
|
281
|
+
};
|
|
282
|
+
const legal = raw.legal ?? {
|
|
283
|
+
schema_version: 1,
|
|
284
|
+
version: 0,
|
|
285
|
+
content_hash: null,
|
|
286
|
+
documents: {
|
|
287
|
+
terms: null,
|
|
288
|
+
shipping: null,
|
|
289
|
+
returns: null,
|
|
290
|
+
privacy: null,
|
|
291
|
+
cookies: null
|
|
292
|
+
},
|
|
293
|
+
inquiries_channel: null,
|
|
294
|
+
direct_marketing_consent: null,
|
|
295
|
+
cookie_notice: {
|
|
296
|
+
enabled: false,
|
|
297
|
+
message: "Usamos cookies propias y de terceros para mejorar tu experiencia y medir el funcionamiento del sitio.",
|
|
298
|
+
button_label: "Entendido"
|
|
299
|
+
}
|
|
300
|
+
};
|
|
301
|
+
const mapConstructionImage = (image) => {
|
|
302
|
+
if (!image) return null;
|
|
303
|
+
const urls = buildStorageContentMediaUrls(
|
|
304
|
+
`${apiUrl}/storage/v1`,
|
|
305
|
+
image.file_id,
|
|
306
|
+
image.width
|
|
307
|
+
);
|
|
308
|
+
return {
|
|
309
|
+
fileId: image.file_id,
|
|
310
|
+
name: image.name,
|
|
311
|
+
altText: image.alt_text,
|
|
312
|
+
width: image.width,
|
|
313
|
+
height: image.height,
|
|
314
|
+
originalUrl: urls.originalUrl,
|
|
315
|
+
heroUrl: urls.heroUrl,
|
|
316
|
+
responsiveSources: urls.responsiveSources
|
|
317
|
+
};
|
|
318
|
+
};
|
|
319
|
+
const construction = raw.construction ?? {
|
|
320
|
+
schema_version: 1,
|
|
321
|
+
enabled: false,
|
|
322
|
+
enforced: false,
|
|
323
|
+
preview_access_granted: false,
|
|
324
|
+
title: null,
|
|
325
|
+
message: null,
|
|
326
|
+
desktop_image: null,
|
|
327
|
+
mobile_image: null
|
|
328
|
+
};
|
|
329
|
+
const volumePricing = raw.volume_pricing ?? {
|
|
330
|
+
schema_version: 1,
|
|
331
|
+
enabled: false,
|
|
332
|
+
tiers: []
|
|
333
|
+
};
|
|
136
334
|
return {
|
|
137
335
|
websiteId: raw.website_id,
|
|
138
336
|
companyId: raw.company_id,
|
|
337
|
+
contextSchemaVersion: raw.context_schema_version === 1 ? 1 : null,
|
|
338
|
+
chainId: raw.chain_id ?? null,
|
|
139
339
|
slug: raw.slug,
|
|
140
340
|
name: raw.name,
|
|
141
341
|
domain: raw.domain,
|
|
342
|
+
config: raw.config ?? null,
|
|
142
343
|
buyerAccountsEnabled: raw.buyer_accounts_enabled,
|
|
143
344
|
clerkPublishableKey: raw.clerk_publishable_key,
|
|
144
345
|
customerIdentification: {
|
|
@@ -154,56 +355,89 @@ async function getWebsitePublicConfig(options) {
|
|
|
154
355
|
isDefault: documentType.is_default
|
|
155
356
|
})
|
|
156
357
|
)
|
|
358
|
+
},
|
|
359
|
+
branding: {
|
|
360
|
+
schemaVersion: branding.schema_version,
|
|
361
|
+
favicon: branding.favicon ? {
|
|
362
|
+
fileId: branding.favicon.file_id,
|
|
363
|
+
name: branding.favicon.name,
|
|
364
|
+
altText: branding.favicon.alt_text,
|
|
365
|
+
width: branding.favicon.width,
|
|
366
|
+
height: branding.favicon.height,
|
|
367
|
+
originalUrl: buildStorageFileUrl(
|
|
368
|
+
`${apiUrl}/storage/v1`,
|
|
369
|
+
branding.favicon.file_id
|
|
370
|
+
)
|
|
371
|
+
} : null
|
|
372
|
+
},
|
|
373
|
+
marketing: {
|
|
374
|
+
schemaVersion: marketing.schema_version,
|
|
375
|
+
enabled: marketing.enabled,
|
|
376
|
+
currencyCode: marketing.currency_code,
|
|
377
|
+
timeZone: marketing.time_zone,
|
|
378
|
+
attributionWindowDays: marketing.attribution_window_days,
|
|
379
|
+
consentPolicy: marketing.consent_policy ? {
|
|
380
|
+
id: marketing.consent_policy.id,
|
|
381
|
+
version: marketing.consent_policy.version,
|
|
382
|
+
statement: marketing.consent_policy.statement,
|
|
383
|
+
statementHash: marketing.consent_policy.statement_hash
|
|
384
|
+
} : null,
|
|
385
|
+
destinations: {
|
|
386
|
+
ga4: marketing.destinations.ga4 ? {
|
|
387
|
+
measurementId: marketing.destinations.ga4.measurement_id,
|
|
388
|
+
diagnosticMode: marketing.destinations.ga4.diagnostic_mode ?? false
|
|
389
|
+
} : null,
|
|
390
|
+
meta: marketing.destinations.meta ? {
|
|
391
|
+
pixelId: marketing.destinations.meta.pixel_id,
|
|
392
|
+
diagnosticMode: marketing.destinations.meta.diagnostic_mode ?? false
|
|
393
|
+
} : null,
|
|
394
|
+
clarity: marketing.destinations.clarity ? { projectId: marketing.destinations.clarity.project_id } : null
|
|
395
|
+
}
|
|
396
|
+
},
|
|
397
|
+
legal: {
|
|
398
|
+
schemaVersion: legal.schema_version,
|
|
399
|
+
version: legal.version,
|
|
400
|
+
contentHash: legal.content_hash,
|
|
401
|
+
documents: {
|
|
402
|
+
terms: legal.documents.terms,
|
|
403
|
+
shipping: legal.documents.shipping ?? null,
|
|
404
|
+
returns: legal.documents.returns ?? null,
|
|
405
|
+
privacy: legal.documents.privacy,
|
|
406
|
+
cookies: legal.documents.cookies
|
|
407
|
+
},
|
|
408
|
+
inquiriesChannel: legal.inquiries_channel,
|
|
409
|
+
directMarketingConsent: legal.direct_marketing_consent ? {
|
|
410
|
+
version: legal.direct_marketing_consent.version,
|
|
411
|
+
statement: legal.direct_marketing_consent.statement,
|
|
412
|
+
statementHash: legal.direct_marketing_consent.statement_hash
|
|
413
|
+
} : null,
|
|
414
|
+
cookieNotice: {
|
|
415
|
+
enabled: legal.cookie_notice.enabled,
|
|
416
|
+
message: legal.cookie_notice.message,
|
|
417
|
+
buttonLabel: legal.cookie_notice.button_label
|
|
418
|
+
}
|
|
419
|
+
},
|
|
420
|
+
construction: {
|
|
421
|
+
schemaVersion: construction.schema_version,
|
|
422
|
+
enabled: construction.enabled,
|
|
423
|
+
enforced: construction.enforced,
|
|
424
|
+
previewAccessGranted: construction.preview_access_granted,
|
|
425
|
+
title: construction.title,
|
|
426
|
+
message: construction.message,
|
|
427
|
+
desktopImage: mapConstructionImage(construction.desktop_image),
|
|
428
|
+
mobileImage: mapConstructionImage(construction.mobile_image)
|
|
429
|
+
},
|
|
430
|
+
volumePricing: {
|
|
431
|
+
schemaVersion: volumePricing.schema_version,
|
|
432
|
+
enabled: volumePricing.enabled,
|
|
433
|
+
tiers: volumePricing.tiers.map((tier) => ({
|
|
434
|
+
priceType: tier.price_type,
|
|
435
|
+
minQuantity: tier.min_quantity
|
|
436
|
+
}))
|
|
157
437
|
}
|
|
158
438
|
};
|
|
159
439
|
}
|
|
160
440
|
|
|
161
|
-
// libs/sdk/src/transport/graphql.ts
|
|
162
|
-
async function executeGraphQL(url, query, variables, headers) {
|
|
163
|
-
const resolvedHeaders = typeof headers === "function" ? await headers() : headers ?? {};
|
|
164
|
-
let res;
|
|
165
|
-
try {
|
|
166
|
-
res = await fetch(url, {
|
|
167
|
-
method: "POST",
|
|
168
|
-
headers: {
|
|
169
|
-
"Content-Type": "application/json",
|
|
170
|
-
...resolvedHeaders
|
|
171
|
-
},
|
|
172
|
-
body: JSON.stringify({ query, variables })
|
|
173
|
-
});
|
|
174
|
-
} catch (err) {
|
|
175
|
-
throw new WebsiteError(
|
|
176
|
-
`Network error connecting to ${url}: ${err instanceof Error ? err.message : String(err)}`,
|
|
177
|
-
"NETWORK_ERROR"
|
|
178
|
-
);
|
|
179
|
-
}
|
|
180
|
-
if (res.status === 429) {
|
|
181
|
-
throw new WebsiteError(
|
|
182
|
-
"Rate limit exceeded. Please try again later.",
|
|
183
|
-
"RATE_LIMITED"
|
|
184
|
-
);
|
|
185
|
-
}
|
|
186
|
-
let body;
|
|
187
|
-
try {
|
|
188
|
-
body = await res.json();
|
|
189
|
-
} catch {
|
|
190
|
-
throw new WebsiteError(
|
|
191
|
-
`Non-JSON response from ${url} (status ${res.status})`,
|
|
192
|
-
"INVALID_RESPONSE"
|
|
193
|
-
);
|
|
194
|
-
}
|
|
195
|
-
if (body.errors && body.errors.length > 0) {
|
|
196
|
-
throw new GraphQLRequestError(body.errors);
|
|
197
|
-
}
|
|
198
|
-
if (!body.data) {
|
|
199
|
-
throw new WebsiteError(
|
|
200
|
-
"GraphQL response contains no data",
|
|
201
|
-
"EMPTY_RESPONSE"
|
|
202
|
-
);
|
|
203
|
-
}
|
|
204
|
-
return body.data;
|
|
205
|
-
}
|
|
206
|
-
|
|
207
441
|
// libs/sdk/src/transport/documents.ts
|
|
208
442
|
var RESOLVE_WEBSITE_BY_SLUG = (
|
|
209
443
|
/* GraphQL */
|
|
@@ -347,7 +581,6 @@ var GET_PRODUCTS = (
|
|
|
347
581
|
) {
|
|
348
582
|
id
|
|
349
583
|
related_product_id
|
|
350
|
-
display_label
|
|
351
584
|
sort_order
|
|
352
585
|
related_product {
|
|
353
586
|
id
|
|
@@ -369,6 +602,52 @@ var GET_PRODUCTS = (
|
|
|
369
602
|
}
|
|
370
603
|
}
|
|
371
604
|
}
|
|
605
|
+
product_option_group_members(limit: 1) {
|
|
606
|
+
group {
|
|
607
|
+
id
|
|
608
|
+
display_title
|
|
609
|
+
members(
|
|
610
|
+
where: {
|
|
611
|
+
product: {
|
|
612
|
+
product_website_publications: {
|
|
613
|
+
website_id: { _eq: $websiteId }
|
|
614
|
+
is_visible: { _eq: true }
|
|
615
|
+
}
|
|
616
|
+
chain_products: {
|
|
617
|
+
chain_id: { _eq: $chainId }
|
|
618
|
+
is_enabled: { _eq: true }
|
|
619
|
+
status: { _eq: active }
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
order_by: { sort_order: asc }
|
|
624
|
+
) {
|
|
625
|
+
id
|
|
626
|
+
product_id
|
|
627
|
+
display_label
|
|
628
|
+
sort_order
|
|
629
|
+
product {
|
|
630
|
+
id
|
|
631
|
+
slug
|
|
632
|
+
name
|
|
633
|
+
sku
|
|
634
|
+
product_media(
|
|
635
|
+
where: { file: { mimeType: { _ilike: "image/%" } } }
|
|
636
|
+
order_by: { order: asc }
|
|
637
|
+
limit: 1
|
|
638
|
+
) {
|
|
639
|
+
id
|
|
640
|
+
file_id
|
|
641
|
+
order
|
|
642
|
+
file {
|
|
643
|
+
mimeType
|
|
644
|
+
name
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
}
|
|
372
651
|
product_attributes {
|
|
373
652
|
id
|
|
374
653
|
value
|
|
@@ -453,6 +732,52 @@ var GET_PRODUCT_BY_PK = (
|
|
|
453
732
|
limit: 1
|
|
454
733
|
) {
|
|
455
734
|
id
|
|
735
|
+
product_option_group_members(limit: 1) {
|
|
736
|
+
group {
|
|
737
|
+
id
|
|
738
|
+
display_title
|
|
739
|
+
members(
|
|
740
|
+
where: {
|
|
741
|
+
product: {
|
|
742
|
+
product_website_publications: {
|
|
743
|
+
website_id: { _eq: $websiteId }
|
|
744
|
+
is_visible: { _eq: true }
|
|
745
|
+
}
|
|
746
|
+
chain_products: {
|
|
747
|
+
chain_id: { _eq: $chainId }
|
|
748
|
+
is_enabled: { _eq: true }
|
|
749
|
+
status: { _eq: active }
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
order_by: { sort_order: asc }
|
|
754
|
+
) {
|
|
755
|
+
id
|
|
756
|
+
product_id
|
|
757
|
+
display_label
|
|
758
|
+
sort_order
|
|
759
|
+
product {
|
|
760
|
+
id
|
|
761
|
+
slug
|
|
762
|
+
name
|
|
763
|
+
sku
|
|
764
|
+
product_media(
|
|
765
|
+
where: { file: { mimeType: { _ilike: "image/%" } } }
|
|
766
|
+
order_by: { order: asc }
|
|
767
|
+
limit: 1
|
|
768
|
+
) {
|
|
769
|
+
id
|
|
770
|
+
file_id
|
|
771
|
+
order
|
|
772
|
+
file {
|
|
773
|
+
mimeType
|
|
774
|
+
name
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
}
|
|
456
781
|
name
|
|
457
782
|
description
|
|
458
783
|
long_description
|
|
@@ -539,7 +864,6 @@ var GET_PRODUCT_BY_PK = (
|
|
|
539
864
|
) {
|
|
540
865
|
id
|
|
541
866
|
related_product_id
|
|
542
|
-
display_label
|
|
543
867
|
sort_order
|
|
544
868
|
related_product {
|
|
545
869
|
id
|
|
@@ -913,6 +1237,24 @@ var WEBSITE_GET_PRODUCT_RECOMMENDATIONS = (
|
|
|
913
1237
|
}
|
|
914
1238
|
`
|
|
915
1239
|
);
|
|
1240
|
+
var WEBSITE_GET_PRODUCT_SPECIFICATIONS = (
|
|
1241
|
+
/* GraphQL */
|
|
1242
|
+
`
|
|
1243
|
+
mutation WebsiteGetProductSpecifications(
|
|
1244
|
+
$websiteId: uuid!
|
|
1245
|
+
$productIds: [uuid!]!
|
|
1246
|
+
) {
|
|
1247
|
+
result: websiteGetProductSpecifications(
|
|
1248
|
+
website_id: $websiteId
|
|
1249
|
+
product_ids: $productIds
|
|
1250
|
+
) {
|
|
1251
|
+
success
|
|
1252
|
+
specifications
|
|
1253
|
+
errors
|
|
1254
|
+
}
|
|
1255
|
+
}
|
|
1256
|
+
`
|
|
1257
|
+
);
|
|
916
1258
|
var GET_BRANDS = (
|
|
917
1259
|
/* GraphQL */
|
|
918
1260
|
`
|
|
@@ -1021,14 +1363,76 @@ var WEBSITE_GET_PRODUCT_CONTENT = (
|
|
|
1021
1363
|
var CHECK_AVAILABILITY = (
|
|
1022
1364
|
/* GraphQL */
|
|
1023
1365
|
`
|
|
1024
|
-
query CheckAvailability(
|
|
1366
|
+
query CheckAvailability(
|
|
1367
|
+
$variantIds: [uuid!]!
|
|
1368
|
+
$chainId: uuid!
|
|
1369
|
+
$websiteId: uuid
|
|
1370
|
+
) {
|
|
1371
|
+
websiteProductAvailability(
|
|
1372
|
+
product_variant_ids: $variantIds
|
|
1373
|
+
chain_id: $chainId
|
|
1374
|
+
website_id: $websiteId
|
|
1375
|
+
) {
|
|
1376
|
+
product_variant_id
|
|
1377
|
+
in_stock
|
|
1378
|
+
label
|
|
1379
|
+
max_purchasable_quantity
|
|
1380
|
+
}
|
|
1381
|
+
}
|
|
1382
|
+
`
|
|
1383
|
+
);
|
|
1384
|
+
var GET_PRODUCT_LIVE_STATE = (
|
|
1385
|
+
/* GraphQL */
|
|
1386
|
+
`
|
|
1387
|
+
query GetProductLiveState(
|
|
1388
|
+
$variantIds: [uuid!]!
|
|
1389
|
+
$companyId: uuid!
|
|
1390
|
+
$chainId: uuid!
|
|
1391
|
+
$websiteId: uuid!
|
|
1392
|
+
) {
|
|
1393
|
+
product_variants(
|
|
1394
|
+
where: {
|
|
1395
|
+
id: { _in: $variantIds }
|
|
1396
|
+
is_disabled: { _eq: false }
|
|
1397
|
+
product: {
|
|
1398
|
+
company_id: { _eq: $companyId }
|
|
1399
|
+
product_website_publications: {
|
|
1400
|
+
website_id: { _eq: $websiteId }
|
|
1401
|
+
is_visible: { _eq: true }
|
|
1402
|
+
}
|
|
1403
|
+
chain_products: {
|
|
1404
|
+
chain_id: { _eq: $chainId }
|
|
1405
|
+
is_enabled: { _eq: true }
|
|
1406
|
+
status: { _eq: active }
|
|
1407
|
+
}
|
|
1408
|
+
}
|
|
1409
|
+
}
|
|
1410
|
+
) {
|
|
1411
|
+
id
|
|
1412
|
+
product_variant_prices(
|
|
1413
|
+
where: {
|
|
1414
|
+
price_type: { _in: [CLIENT, SALE, WHOLESALE, WHOLESALE_2] }
|
|
1415
|
+
_or: [
|
|
1416
|
+
{ chain_id: { _eq: $chainId } }
|
|
1417
|
+
{ chain_id: { _is_null: true } }
|
|
1418
|
+
]
|
|
1419
|
+
}
|
|
1420
|
+
) {
|
|
1421
|
+
id
|
|
1422
|
+
price
|
|
1423
|
+
price_type
|
|
1424
|
+
chain_id
|
|
1425
|
+
}
|
|
1426
|
+
}
|
|
1025
1427
|
websiteProductAvailability(
|
|
1026
1428
|
product_variant_ids: $variantIds
|
|
1027
1429
|
chain_id: $chainId
|
|
1430
|
+
website_id: $websiteId
|
|
1028
1431
|
) {
|
|
1029
1432
|
product_variant_id
|
|
1030
1433
|
in_stock
|
|
1031
1434
|
label
|
|
1435
|
+
max_purchasable_quantity
|
|
1032
1436
|
}
|
|
1033
1437
|
}
|
|
1034
1438
|
`
|
|
@@ -1061,10 +1465,28 @@ var CREATE_CART = (
|
|
|
1061
1465
|
total_with_discount
|
|
1062
1466
|
created_at
|
|
1063
1467
|
updated_at
|
|
1468
|
+
volume_pricing {
|
|
1469
|
+
enabled
|
|
1470
|
+
eligibleQuantity
|
|
1471
|
+
appliedPriceType
|
|
1472
|
+
appliedMinQuantity
|
|
1473
|
+
nextPriceType
|
|
1474
|
+
nextMinQuantity
|
|
1475
|
+
remainingQuantity
|
|
1476
|
+
totalSavings
|
|
1477
|
+
}
|
|
1064
1478
|
items {
|
|
1065
1479
|
id
|
|
1066
1480
|
product_variant_id
|
|
1067
1481
|
quantity
|
|
1482
|
+
unit_price
|
|
1483
|
+
line_total
|
|
1484
|
+
base_unit_price
|
|
1485
|
+
unit_savings
|
|
1486
|
+
line_savings
|
|
1487
|
+
volume_pricing_eligible
|
|
1488
|
+
resolved_price_type
|
|
1489
|
+
volume_pricing_min_quantity
|
|
1068
1490
|
product_variant {
|
|
1069
1491
|
id
|
|
1070
1492
|
sku
|
|
@@ -1104,10 +1526,28 @@ var GET_CART = (
|
|
|
1104
1526
|
total_with_discount
|
|
1105
1527
|
created_at
|
|
1106
1528
|
updated_at
|
|
1529
|
+
volume_pricing {
|
|
1530
|
+
enabled
|
|
1531
|
+
eligibleQuantity
|
|
1532
|
+
appliedPriceType
|
|
1533
|
+
appliedMinQuantity
|
|
1534
|
+
nextPriceType
|
|
1535
|
+
nextMinQuantity
|
|
1536
|
+
remainingQuantity
|
|
1537
|
+
totalSavings
|
|
1538
|
+
}
|
|
1107
1539
|
items {
|
|
1108
1540
|
id
|
|
1109
1541
|
product_variant_id
|
|
1110
1542
|
quantity
|
|
1543
|
+
unit_price
|
|
1544
|
+
line_total
|
|
1545
|
+
base_unit_price
|
|
1546
|
+
unit_savings
|
|
1547
|
+
line_savings
|
|
1548
|
+
volume_pricing_eligible
|
|
1549
|
+
resolved_price_type
|
|
1550
|
+
volume_pricing_min_quantity
|
|
1111
1551
|
product_variant {
|
|
1112
1552
|
id
|
|
1113
1553
|
sku
|
|
@@ -1138,6 +1578,7 @@ var START_GUEST_CHECKOUT = (
|
|
|
1138
1578
|
$contact: WebsiteGuestContactInput!
|
|
1139
1579
|
$items: [WebsiteCheckoutItemInput!]!
|
|
1140
1580
|
$fulfillmentFamily: String
|
|
1581
|
+
$marketingAttribution: WebsiteMarketingAttributionInput
|
|
1141
1582
|
) {
|
|
1142
1583
|
websiteStartGuestCheckout(
|
|
1143
1584
|
website_id: $websiteId
|
|
@@ -1145,6 +1586,7 @@ var START_GUEST_CHECKOUT = (
|
|
|
1145
1586
|
contact: $contact
|
|
1146
1587
|
items: $items
|
|
1147
1588
|
fulfillment_family: $fulfillmentFamily
|
|
1589
|
+
marketing_attribution: $marketingAttribution
|
|
1148
1590
|
) {
|
|
1149
1591
|
success
|
|
1150
1592
|
order_id
|
|
@@ -1159,10 +1601,28 @@ var START_GUEST_CHECKOUT = (
|
|
|
1159
1601
|
total_with_discount
|
|
1160
1602
|
created_at
|
|
1161
1603
|
updated_at
|
|
1604
|
+
volume_pricing {
|
|
1605
|
+
enabled
|
|
1606
|
+
eligibleQuantity
|
|
1607
|
+
appliedPriceType
|
|
1608
|
+
appliedMinQuantity
|
|
1609
|
+
nextPriceType
|
|
1610
|
+
nextMinQuantity
|
|
1611
|
+
remainingQuantity
|
|
1612
|
+
totalSavings
|
|
1613
|
+
}
|
|
1162
1614
|
items {
|
|
1163
1615
|
id
|
|
1164
1616
|
product_variant_id
|
|
1165
1617
|
quantity
|
|
1618
|
+
unit_price
|
|
1619
|
+
line_total
|
|
1620
|
+
base_unit_price
|
|
1621
|
+
unit_savings
|
|
1622
|
+
line_savings
|
|
1623
|
+
volume_pricing_eligible
|
|
1624
|
+
resolved_price_type
|
|
1625
|
+
volume_pricing_min_quantity
|
|
1166
1626
|
product_variant {
|
|
1167
1627
|
id
|
|
1168
1628
|
sku
|
|
@@ -1212,10 +1672,28 @@ var SET_CART_ITEM = (
|
|
|
1212
1672
|
total_with_discount
|
|
1213
1673
|
created_at
|
|
1214
1674
|
updated_at
|
|
1675
|
+
volume_pricing {
|
|
1676
|
+
enabled
|
|
1677
|
+
eligibleQuantity
|
|
1678
|
+
appliedPriceType
|
|
1679
|
+
appliedMinQuantity
|
|
1680
|
+
nextPriceType
|
|
1681
|
+
nextMinQuantity
|
|
1682
|
+
remainingQuantity
|
|
1683
|
+
totalSavings
|
|
1684
|
+
}
|
|
1215
1685
|
items {
|
|
1216
1686
|
id
|
|
1217
1687
|
product_variant_id
|
|
1218
1688
|
quantity
|
|
1689
|
+
unit_price
|
|
1690
|
+
line_total
|
|
1691
|
+
base_unit_price
|
|
1692
|
+
unit_savings
|
|
1693
|
+
line_savings
|
|
1694
|
+
volume_pricing_eligible
|
|
1695
|
+
resolved_price_type
|
|
1696
|
+
volume_pricing_min_quantity
|
|
1219
1697
|
product_variant {
|
|
1220
1698
|
id
|
|
1221
1699
|
sku
|
|
@@ -1263,10 +1741,28 @@ var REMOVE_CART_ITEM = (
|
|
|
1263
1741
|
total_with_discount
|
|
1264
1742
|
created_at
|
|
1265
1743
|
updated_at
|
|
1744
|
+
volume_pricing {
|
|
1745
|
+
enabled
|
|
1746
|
+
eligibleQuantity
|
|
1747
|
+
appliedPriceType
|
|
1748
|
+
appliedMinQuantity
|
|
1749
|
+
nextPriceType
|
|
1750
|
+
nextMinQuantity
|
|
1751
|
+
remainingQuantity
|
|
1752
|
+
totalSavings
|
|
1753
|
+
}
|
|
1266
1754
|
items {
|
|
1267
1755
|
id
|
|
1268
1756
|
product_variant_id
|
|
1269
1757
|
quantity
|
|
1758
|
+
unit_price
|
|
1759
|
+
line_total
|
|
1760
|
+
base_unit_price
|
|
1761
|
+
unit_savings
|
|
1762
|
+
line_savings
|
|
1763
|
+
volume_pricing_eligible
|
|
1764
|
+
resolved_price_type
|
|
1765
|
+
volume_pricing_min_quantity
|
|
1270
1766
|
product_variant {
|
|
1271
1767
|
id
|
|
1272
1768
|
sku
|
|
@@ -1288,6 +1784,19 @@ var REMOVE_CART_ITEM = (
|
|
|
1288
1784
|
}
|
|
1289
1785
|
`
|
|
1290
1786
|
);
|
|
1787
|
+
var DISCARD_CART = (
|
|
1788
|
+
/* GraphQL */
|
|
1789
|
+
`
|
|
1790
|
+
mutation DiscardCart($orderId: uuid!, $cartToken: String!) {
|
|
1791
|
+
websiteDiscardCart(order_id: $orderId, cart_token: $cartToken) {
|
|
1792
|
+
success
|
|
1793
|
+
order_id
|
|
1794
|
+
website_cart_status
|
|
1795
|
+
errors
|
|
1796
|
+
}
|
|
1797
|
+
}
|
|
1798
|
+
`
|
|
1799
|
+
);
|
|
1291
1800
|
var CHECKOUT = (
|
|
1292
1801
|
/* GraphQL */
|
|
1293
1802
|
`
|
|
@@ -1302,6 +1811,8 @@ var CHECKOUT = (
|
|
|
1302
1811
|
$paymentMethodId: uuid
|
|
1303
1812
|
$buyerAccountOptIn: Boolean
|
|
1304
1813
|
$initialPayment: WebsiteInitialPaymentInput
|
|
1814
|
+
$marketingAttribution: WebsiteMarketingAttributionInput
|
|
1815
|
+
$advertisingConsent: WebsiteAdvertisingConsentInput
|
|
1305
1816
|
) {
|
|
1306
1817
|
websiteCheckout(
|
|
1307
1818
|
order_id: $orderId
|
|
@@ -1314,6 +1825,8 @@ var CHECKOUT = (
|
|
|
1314
1825
|
payment_method_id: $paymentMethodId
|
|
1315
1826
|
buyer_account_opt_in: $buyerAccountOptIn
|
|
1316
1827
|
initial_payment: $initialPayment
|
|
1828
|
+
marketing_attribution: $marketingAttribution
|
|
1829
|
+
advertising_consent: $advertisingConsent
|
|
1317
1830
|
) {
|
|
1318
1831
|
success
|
|
1319
1832
|
order_id
|
|
@@ -2240,45 +2753,157 @@ var GET_SEARCH_SUGGESTIONS = (
|
|
|
2240
2753
|
`
|
|
2241
2754
|
);
|
|
2242
2755
|
|
|
2243
|
-
// libs/sdk/src/
|
|
2244
|
-
var
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2756
|
+
// libs/sdk/src/transport/graphql.ts
|
|
2757
|
+
var DEFAULT_MAX_ATTEMPTS = 4;
|
|
2758
|
+
var DEFAULT_MAX_TOTAL_WAIT_MS = 3 * 60 * 1e3;
|
|
2759
|
+
var DEFAULT_JITTER_RATIO = 0.1;
|
|
2760
|
+
function parseRetryAfterSeconds(res, body) {
|
|
2761
|
+
const header = res.headers?.get?.("Retry-After")?.trim();
|
|
2762
|
+
if (header) {
|
|
2763
|
+
const seconds = Number(header);
|
|
2764
|
+
if (Number.isFinite(seconds) && seconds >= 0) return Math.ceil(seconds);
|
|
2765
|
+
const retryAt = Date.parse(header);
|
|
2766
|
+
if (Number.isFinite(retryAt)) {
|
|
2767
|
+
return Math.max(Math.ceil((retryAt - Date.now()) / 1e3), 0);
|
|
2768
|
+
}
|
|
2769
|
+
}
|
|
2770
|
+
if (body && typeof body === "object") {
|
|
2771
|
+
const seconds = Number(
|
|
2772
|
+
body.retry_after_seconds
|
|
2773
|
+
);
|
|
2774
|
+
if (Number.isFinite(seconds) && seconds >= 0) return Math.ceil(seconds);
|
|
2775
|
+
}
|
|
2776
|
+
return 1;
|
|
2251
2777
|
}
|
|
2252
|
-
function
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2778
|
+
async function createRateLimitError(res) {
|
|
2779
|
+
let body;
|
|
2780
|
+
try {
|
|
2781
|
+
body = await res.json();
|
|
2782
|
+
} catch {
|
|
2783
|
+
body = null;
|
|
2256
2784
|
}
|
|
2257
|
-
|
|
2785
|
+
const message = body && typeof body === "object" && typeof body.message === "string" ? body.message : "Rate limit exceeded. Please try again later.";
|
|
2786
|
+
return new RateLimitError(message, parseRetryAfterSeconds(res, body));
|
|
2258
2787
|
}
|
|
2259
|
-
function
|
|
2260
|
-
|
|
2788
|
+
function getRetryDelayMs(error, options) {
|
|
2789
|
+
const retryAfterMs = (error.retryAfterSeconds ?? 1) * 1e3;
|
|
2790
|
+
const jitterRatio = Math.max(
|
|
2791
|
+
0,
|
|
2792
|
+
Math.min(options.jitterRatio ?? DEFAULT_JITTER_RATIO, 1)
|
|
2793
|
+
);
|
|
2794
|
+
return Math.ceil(retryAfterMs * (1 + Math.random() * jitterRatio));
|
|
2261
2795
|
}
|
|
2262
|
-
function
|
|
2263
|
-
return
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2796
|
+
function wait(delayMs) {
|
|
2797
|
+
return new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
2798
|
+
}
|
|
2799
|
+
async function executeGraphQL(url, query, variables, headers, options) {
|
|
2800
|
+
const retry = options?.retry;
|
|
2801
|
+
const maxAttempts = Math.max(
|
|
2802
|
+
1,
|
|
2803
|
+
Math.floor(retry?.maxAttempts ?? DEFAULT_MAX_ATTEMPTS)
|
|
2804
|
+
);
|
|
2805
|
+
const maxTotalWaitMs = Math.max(
|
|
2806
|
+
0,
|
|
2807
|
+
retry?.maxTotalWaitMs ?? DEFAULT_MAX_TOTAL_WAIT_MS
|
|
2808
|
+
);
|
|
2809
|
+
let totalWaitMs = 0;
|
|
2810
|
+
let res = null;
|
|
2811
|
+
for (let attempt = 1; attempt <= (retry ? maxAttempts : 1); attempt += 1) {
|
|
2812
|
+
const resolvedHeaders = typeof headers === "function" ? await headers() : headers ?? {};
|
|
2813
|
+
try {
|
|
2814
|
+
res = await fetch(url, {
|
|
2815
|
+
method: "POST",
|
|
2816
|
+
headers: {
|
|
2817
|
+
"Content-Type": "application/json",
|
|
2818
|
+
...resolvedHeaders
|
|
2819
|
+
},
|
|
2820
|
+
body: JSON.stringify({ query, variables })
|
|
2821
|
+
});
|
|
2822
|
+
} catch (err) {
|
|
2823
|
+
throw new WebsiteError(
|
|
2824
|
+
`Network error connecting to ${url}: ${err instanceof Error ? err.message : String(err)}`,
|
|
2825
|
+
"NETWORK_ERROR"
|
|
2826
|
+
);
|
|
2827
|
+
}
|
|
2828
|
+
if (res.status !== 429) break;
|
|
2829
|
+
const rateLimitError = await createRateLimitError(res);
|
|
2830
|
+
const delayMs = getRetryDelayMs(rateLimitError, retry ?? {});
|
|
2831
|
+
const canRetry = Boolean(retry) && attempt < maxAttempts && totalWaitMs + delayMs <= maxTotalWaitMs;
|
|
2832
|
+
if (!canRetry) throw rateLimitError;
|
|
2833
|
+
totalWaitMs += delayMs;
|
|
2834
|
+
await wait(delayMs);
|
|
2835
|
+
}
|
|
2836
|
+
if (!res) {
|
|
2837
|
+
throw new WebsiteError(
|
|
2838
|
+
"GraphQL request did not produce a response",
|
|
2839
|
+
"EMPTY_RESPONSE"
|
|
2840
|
+
);
|
|
2841
|
+
}
|
|
2842
|
+
let body;
|
|
2843
|
+
try {
|
|
2844
|
+
body = await res.json();
|
|
2845
|
+
} catch {
|
|
2846
|
+
throw new WebsiteError(
|
|
2847
|
+
`Non-JSON response from ${url} (status ${res.status})`,
|
|
2848
|
+
"INVALID_RESPONSE"
|
|
2849
|
+
);
|
|
2850
|
+
}
|
|
2851
|
+
if (body.errors && body.errors.length > 0) {
|
|
2852
|
+
throw new GraphQLRequestError(body.errors);
|
|
2853
|
+
}
|
|
2854
|
+
if (!body.data) {
|
|
2855
|
+
throw new WebsiteError(
|
|
2856
|
+
"GraphQL response contains no data",
|
|
2857
|
+
"EMPTY_RESPONSE"
|
|
2858
|
+
);
|
|
2859
|
+
}
|
|
2860
|
+
return body.data;
|
|
2861
|
+
}
|
|
2862
|
+
|
|
2863
|
+
// libs/sdk/src/modules/availability.ts
|
|
2864
|
+
var MAX_AVAILABILITY_VARIANT_IDS = 100;
|
|
2865
|
+
function mapAvailability(raw) {
|
|
2866
|
+
return {
|
|
2867
|
+
productVariantId: raw.product_variant_id,
|
|
2868
|
+
inStock: raw.in_stock,
|
|
2869
|
+
label: raw.label,
|
|
2870
|
+
maxPurchasableQuantity: raw.max_purchasable_quantity
|
|
2871
|
+
};
|
|
2872
|
+
}
|
|
2873
|
+
function chunkArray(items, chunkSize) {
|
|
2874
|
+
const chunks = [];
|
|
2875
|
+
for (let index = 0; index < items.length; index += chunkSize) {
|
|
2876
|
+
chunks.push(items.slice(index, index + chunkSize));
|
|
2877
|
+
}
|
|
2878
|
+
return chunks;
|
|
2879
|
+
}
|
|
2880
|
+
function uniqueValues(values) {
|
|
2881
|
+
return Array.from(new Set(values));
|
|
2882
|
+
}
|
|
2883
|
+
function createAvailabilityModule(graphqlUrl, getHeaders, getContext) {
|
|
2884
|
+
return {
|
|
2885
|
+
async checkAvailability(productVariantIds) {
|
|
2886
|
+
const ctx = getContext();
|
|
2887
|
+
if (!ctx.chainId) {
|
|
2888
|
+
throw new WebsiteError(
|
|
2889
|
+
"This website has no chain configured. Stock availability requires a chain_id.",
|
|
2890
|
+
"NO_CHAIN"
|
|
2891
|
+
);
|
|
2271
2892
|
}
|
|
2272
2893
|
if (productVariantIds.length === 0) return [];
|
|
2273
2894
|
const variantIds = uniqueValues(productVariantIds);
|
|
2274
2895
|
const results = [];
|
|
2275
|
-
for (const chunk of chunkArray(
|
|
2896
|
+
for (const chunk of chunkArray(
|
|
2897
|
+
variantIds,
|
|
2898
|
+
MAX_AVAILABILITY_VARIANT_IDS
|
|
2899
|
+
)) {
|
|
2276
2900
|
const data = await executeGraphQL(
|
|
2277
2901
|
graphqlUrl,
|
|
2278
2902
|
CHECK_AVAILABILITY,
|
|
2279
2903
|
{
|
|
2280
2904
|
variantIds: chunk,
|
|
2281
|
-
chainId: ctx.chainId
|
|
2905
|
+
chainId: ctx.chainId,
|
|
2906
|
+
websiteId: ctx.websiteId
|
|
2282
2907
|
},
|
|
2283
2908
|
getHeaders
|
|
2284
2909
|
);
|
|
@@ -2291,12 +2916,297 @@ function createAvailabilityModule(graphqlUrl, getHeaders, getContext) {
|
|
|
2291
2916
|
};
|
|
2292
2917
|
}
|
|
2293
2918
|
|
|
2919
|
+
// libs/sdk/src/marketing/sanitize.ts
|
|
2920
|
+
var SENSITIVE_PARAMETER_PATTERNS = [
|
|
2921
|
+
/token/i,
|
|
2922
|
+
/secret/i,
|
|
2923
|
+
/password/i,
|
|
2924
|
+
/authorization/i,
|
|
2925
|
+
/session/i,
|
|
2926
|
+
/ticket/i,
|
|
2927
|
+
/code_verifier/i,
|
|
2928
|
+
/client_secret/i,
|
|
2929
|
+
/payment/i,
|
|
2930
|
+
/order[_-]?(?:id|token)/i,
|
|
2931
|
+
/public[_-]?order/i,
|
|
2932
|
+
/clerk/i,
|
|
2933
|
+
/oauth/i,
|
|
2934
|
+
/email/i,
|
|
2935
|
+
/phone/i,
|
|
2936
|
+
/document/i
|
|
2937
|
+
];
|
|
2938
|
+
var ALLOWED_MARKETING_PARAMETERS = /* @__PURE__ */ new Set([
|
|
2939
|
+
"utm_source",
|
|
2940
|
+
"utm_medium",
|
|
2941
|
+
"utm_campaign",
|
|
2942
|
+
"utm_term",
|
|
2943
|
+
"utm_content",
|
|
2944
|
+
"utm_source_platform",
|
|
2945
|
+
"utm_id",
|
|
2946
|
+
"bacano_origin",
|
|
2947
|
+
"gclid",
|
|
2948
|
+
"gbraid",
|
|
2949
|
+
"wbraid",
|
|
2950
|
+
"fbclid",
|
|
2951
|
+
"ttclid",
|
|
2952
|
+
"msclkid"
|
|
2953
|
+
]);
|
|
2954
|
+
function sanitizeMarketingUrl(input, baseUrl) {
|
|
2955
|
+
try {
|
|
2956
|
+
const url = new URL(input, baseUrl);
|
|
2957
|
+
const safeParameters = new URLSearchParams();
|
|
2958
|
+
for (const [key, value] of url.searchParams.entries()) {
|
|
2959
|
+
if (ALLOWED_MARKETING_PARAMETERS.has(key.toLowerCase()) && !SENSITIVE_PARAMETER_PATTERNS.some((pattern) => pattern.test(key))) {
|
|
2960
|
+
safeParameters.set(key.toLowerCase(), value.slice(0, 500));
|
|
2961
|
+
}
|
|
2962
|
+
}
|
|
2963
|
+
url.search = safeParameters.toString();
|
|
2964
|
+
url.hash = "";
|
|
2965
|
+
return url.toString();
|
|
2966
|
+
} catch {
|
|
2967
|
+
return "";
|
|
2968
|
+
}
|
|
2969
|
+
}
|
|
2970
|
+
function getAllowedMarketingParameters(input, baseUrl) {
|
|
2971
|
+
const sanitized = sanitizeMarketingUrl(input, baseUrl);
|
|
2972
|
+
if (!sanitized) return {};
|
|
2973
|
+
const url = new URL(sanitized);
|
|
2974
|
+
return Object.fromEntries(url.searchParams.entries());
|
|
2975
|
+
}
|
|
2976
|
+
|
|
2977
|
+
// libs/sdk/src/marketing/attribution.ts
|
|
2978
|
+
var STORAGE_PREFIX = "bacano:marketing:attribution:v1";
|
|
2979
|
+
var GA4_CLIENT_ID_PATTERN = /^\d+\.\d+$/;
|
|
2980
|
+
var META_BROWSER_ID_PATTERN = /^fb\.\d+\.\d+\.[A-Za-z0-9_-]+$/;
|
|
2981
|
+
var SALES_ORIGIN_KEY_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
2982
|
+
function storageKey(websiteId) {
|
|
2983
|
+
return `${STORAGE_PREFIX}:${websiteId}`;
|
|
2984
|
+
}
|
|
2985
|
+
function readStored(websiteId) {
|
|
2986
|
+
if (typeof window === "undefined") return null;
|
|
2987
|
+
try {
|
|
2988
|
+
const value = window.localStorage.getItem(storageKey(websiteId));
|
|
2989
|
+
if (!value) return null;
|
|
2990
|
+
const parsed = JSON.parse(value);
|
|
2991
|
+
if (new Date(parsed.expiresAt).getTime() <= Date.now()) {
|
|
2992
|
+
window.localStorage.removeItem(storageKey(websiteId));
|
|
2993
|
+
return null;
|
|
2994
|
+
}
|
|
2995
|
+
return parsed;
|
|
2996
|
+
} catch {
|
|
2997
|
+
return null;
|
|
2998
|
+
}
|
|
2999
|
+
}
|
|
3000
|
+
function writeStored(websiteId, value) {
|
|
3001
|
+
if (typeof window === "undefined") return;
|
|
3002
|
+
try {
|
|
3003
|
+
window.localStorage.setItem(storageKey(websiteId), JSON.stringify(value));
|
|
3004
|
+
} catch {
|
|
3005
|
+
}
|
|
3006
|
+
}
|
|
3007
|
+
function readCookie(name) {
|
|
3008
|
+
if (typeof document === "undefined") return void 0;
|
|
3009
|
+
try {
|
|
3010
|
+
const prefix = `${name}=`;
|
|
3011
|
+
return document.cookie.split(";").map((part) => part.trim()).find((part) => part.startsWith(prefix))?.slice(prefix.length);
|
|
3012
|
+
} catch {
|
|
3013
|
+
return void 0;
|
|
3014
|
+
}
|
|
3015
|
+
}
|
|
3016
|
+
function validMetaBrowserId(value) {
|
|
3017
|
+
const normalized = value?.trim();
|
|
3018
|
+
return normalized && normalized.length <= 512 && META_BROWSER_ID_PATTERN.test(normalized) ? normalized : void 0;
|
|
3019
|
+
}
|
|
3020
|
+
function optional(value) {
|
|
3021
|
+
const normalized = value?.trim();
|
|
3022
|
+
return normalized ? normalized.slice(0, 500) : void 0;
|
|
3023
|
+
}
|
|
3024
|
+
function validSalesOriginKey(value) {
|
|
3025
|
+
const normalized = value?.trim().toLowerCase();
|
|
3026
|
+
return normalized && normalized.length <= 100 && SALES_ORIGIN_KEY_PATTERN.test(normalized) ? normalized : void 0;
|
|
3027
|
+
}
|
|
3028
|
+
function buildTouchpoint() {
|
|
3029
|
+
if (typeof window === "undefined") return null;
|
|
3030
|
+
const params = getAllowedMarketingParameters(window.location.href);
|
|
3031
|
+
const referrerHost = (() => {
|
|
3032
|
+
if (!document.referrer) return void 0;
|
|
3033
|
+
try {
|
|
3034
|
+
const hostname = new URL(document.referrer).hostname;
|
|
3035
|
+
return hostname === window.location.hostname ? void 0 : hostname.slice(0, 255);
|
|
3036
|
+
} catch {
|
|
3037
|
+
return void 0;
|
|
3038
|
+
}
|
|
3039
|
+
})();
|
|
3040
|
+
return {
|
|
3041
|
+
capturedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3042
|
+
landingUrl: sanitizeMarketingUrl(window.location.href),
|
|
3043
|
+
referrerHost,
|
|
3044
|
+
source: optional(params.utm_source),
|
|
3045
|
+
medium: optional(params.utm_medium),
|
|
3046
|
+
campaign: optional(params.utm_campaign),
|
|
3047
|
+
term: optional(params.utm_term),
|
|
3048
|
+
content: optional(params.utm_content),
|
|
3049
|
+
sourcePlatform: optional(params.utm_source_platform),
|
|
3050
|
+
campaignId: optional(params.utm_id),
|
|
3051
|
+
salesOriginKey: validSalesOriginKey(params.bacano_origin),
|
|
3052
|
+
gclid: optional(params.gclid),
|
|
3053
|
+
gbraid: optional(params.gbraid),
|
|
3054
|
+
wbraid: optional(params.wbraid),
|
|
3055
|
+
fbclid: optional(params.fbclid),
|
|
3056
|
+
ttclid: optional(params.ttclid),
|
|
3057
|
+
msclkid: optional(params.msclkid)
|
|
3058
|
+
};
|
|
3059
|
+
}
|
|
3060
|
+
function isNonDirect(touchpoint) {
|
|
3061
|
+
return Boolean(
|
|
3062
|
+
touchpoint.salesOriginKey || touchpoint.source || touchpoint.medium || touchpoint.campaign || touchpoint.gclid || touchpoint.gbraid || touchpoint.wbraid || touchpoint.fbclid || touchpoint.ttclid || touchpoint.msclkid || touchpoint.referrerHost
|
|
3063
|
+
);
|
|
3064
|
+
}
|
|
3065
|
+
function toPublic(stored) {
|
|
3066
|
+
return {
|
|
3067
|
+
schemaVersion: 1,
|
|
3068
|
+
firstTouch: stored.firstTouch,
|
|
3069
|
+
lastNonDirectTouch: stored.lastNonDirectTouch,
|
|
3070
|
+
ga4ClientId: stored.ga4ClientId,
|
|
3071
|
+
ga4SessionId: stored.ga4SessionId,
|
|
3072
|
+
metaFbp: stored.metaFbp,
|
|
3073
|
+
metaFbc: stored.metaFbc,
|
|
3074
|
+
clientUserAgent: stored.clientUserAgent,
|
|
3075
|
+
capturedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3076
|
+
expiresAt: stored.expiresAt
|
|
3077
|
+
};
|
|
3078
|
+
}
|
|
3079
|
+
function refreshWebsiteMarketingMetaContext(websiteId) {
|
|
3080
|
+
try {
|
|
3081
|
+
const stored = readStored(websiteId);
|
|
3082
|
+
if (!stored || typeof window === "undefined") return false;
|
|
3083
|
+
const fbp = validMetaBrowserId(readCookie("_fbp"));
|
|
3084
|
+
const clickTouchpoint = stored.lastNonDirectTouch ?? stored.firstTouch;
|
|
3085
|
+
const derivedFbc = clickTouchpoint.fbclid ? `fb.1.${new Date(clickTouchpoint.capturedAt).getTime()}.${clickTouchpoint.fbclid}` : void 0;
|
|
3086
|
+
const fbc = validMetaBrowserId(readCookie("_fbc") ?? derivedFbc);
|
|
3087
|
+
const clientUserAgent = window.navigator?.userAgent.trim().slice(0, 1024);
|
|
3088
|
+
writeStored(websiteId, {
|
|
3089
|
+
...stored,
|
|
3090
|
+
metaFbp: fbp ?? stored.metaFbp,
|
|
3091
|
+
metaFbc: fbc ?? stored.metaFbc,
|
|
3092
|
+
clientUserAgent: clientUserAgent || stored.clientUserAgent
|
|
3093
|
+
});
|
|
3094
|
+
return Boolean(fbp || fbc || clientUserAgent);
|
|
3095
|
+
} catch {
|
|
3096
|
+
return false;
|
|
3097
|
+
}
|
|
3098
|
+
}
|
|
3099
|
+
function storeWebsiteMarketingGa4ClientId(websiteId, clientId) {
|
|
3100
|
+
if (typeof clientId !== "string") return false;
|
|
3101
|
+
const normalized = clientId.trim();
|
|
3102
|
+
if (!GA4_CLIENT_ID_PATTERN.test(normalized)) return false;
|
|
3103
|
+
const stored = readStored(websiteId);
|
|
3104
|
+
if (!stored) return false;
|
|
3105
|
+
writeStored(websiteId, { ...stored, ga4ClientId: normalized });
|
|
3106
|
+
return true;
|
|
3107
|
+
}
|
|
3108
|
+
function storeWebsiteMarketingGa4SessionId(websiteId, sessionId) {
|
|
3109
|
+
const normalized = typeof sessionId === "number" && Number.isSafeInteger(sessionId) ? String(sessionId) : typeof sessionId === "string" ? sessionId.trim() : "";
|
|
3110
|
+
const numericValue = Number(normalized);
|
|
3111
|
+
if (!/^\d{1,16}$/.test(normalized) || !Number.isSafeInteger(numericValue) || numericValue <= 0) {
|
|
3112
|
+
return false;
|
|
3113
|
+
}
|
|
3114
|
+
const stored = readStored(websiteId);
|
|
3115
|
+
if (!stored) return false;
|
|
3116
|
+
writeStored(websiteId, { ...stored, ga4SessionId: normalized });
|
|
3117
|
+
return true;
|
|
3118
|
+
}
|
|
3119
|
+
function captureWebsiteMarketingAttribution(websiteId, windowDays) {
|
|
3120
|
+
const current = buildTouchpoint();
|
|
3121
|
+
if (!current) return null;
|
|
3122
|
+
const stored = readStored(websiteId);
|
|
3123
|
+
const expiresAt = stored?.expiresAt ?? new Date(Date.now() + Math.max(1, windowDays) * 864e5).toISOString();
|
|
3124
|
+
const next = {
|
|
3125
|
+
firstTouch: stored?.firstTouch ?? current,
|
|
3126
|
+
lastNonDirectTouch: isNonDirect(current) ? current : stored?.lastNonDirectTouch ?? null,
|
|
3127
|
+
ga4ClientId: stored?.ga4ClientId,
|
|
3128
|
+
ga4SessionId: stored?.ga4SessionId,
|
|
3129
|
+
metaFbp: stored?.metaFbp,
|
|
3130
|
+
metaFbc: stored?.metaFbc,
|
|
3131
|
+
clientUserAgent: stored?.clientUserAgent,
|
|
3132
|
+
expiresAt
|
|
3133
|
+
};
|
|
3134
|
+
writeStored(websiteId, next);
|
|
3135
|
+
return toPublic(next);
|
|
3136
|
+
}
|
|
3137
|
+
function getWebsiteMarketingAttribution(websiteId) {
|
|
3138
|
+
const stored = readStored(websiteId);
|
|
3139
|
+
return stored ? toPublic(stored) : null;
|
|
3140
|
+
}
|
|
3141
|
+
function serializeTouchpoint(touchpoint) {
|
|
3142
|
+
return {
|
|
3143
|
+
captured_at: touchpoint.capturedAt,
|
|
3144
|
+
landing_url: touchpoint.landingUrl,
|
|
3145
|
+
referrer_host: touchpoint.referrerHost,
|
|
3146
|
+
source: touchpoint.source,
|
|
3147
|
+
medium: touchpoint.medium,
|
|
3148
|
+
campaign: touchpoint.campaign,
|
|
3149
|
+
term: touchpoint.term,
|
|
3150
|
+
content: touchpoint.content,
|
|
3151
|
+
source_platform: touchpoint.sourcePlatform,
|
|
3152
|
+
campaign_id: touchpoint.campaignId,
|
|
3153
|
+
sales_origin_key: touchpoint.salesOriginKey,
|
|
3154
|
+
gclid: touchpoint.gclid,
|
|
3155
|
+
gbraid: touchpoint.gbraid,
|
|
3156
|
+
wbraid: touchpoint.wbraid,
|
|
3157
|
+
fbclid: touchpoint.fbclid,
|
|
3158
|
+
ttclid: touchpoint.ttclid,
|
|
3159
|
+
msclkid: touchpoint.msclkid
|
|
3160
|
+
};
|
|
3161
|
+
}
|
|
3162
|
+
function serializeWebsiteMarketingAttribution(attribution) {
|
|
3163
|
+
return {
|
|
3164
|
+
schema_version: attribution.schemaVersion,
|
|
3165
|
+
first_touch: serializeTouchpoint(attribution.firstTouch),
|
|
3166
|
+
last_non_direct_touch: attribution.lastNonDirectTouch ? serializeTouchpoint(attribution.lastNonDirectTouch) : null,
|
|
3167
|
+
ga4_client_id: attribution.ga4ClientId,
|
|
3168
|
+
ga4_session_id: attribution.ga4SessionId,
|
|
3169
|
+
meta_fbp: attribution.metaFbp,
|
|
3170
|
+
meta_fbc: attribution.metaFbc,
|
|
3171
|
+
client_user_agent: attribution.clientUserAgent,
|
|
3172
|
+
captured_at: attribution.capturedAt
|
|
3173
|
+
};
|
|
3174
|
+
}
|
|
3175
|
+
|
|
3176
|
+
// libs/sdk/src/modules/cart-errors.ts
|
|
3177
|
+
var RECOVERABLE_CART_SESSION_ERRORS = /* @__PURE__ */ new Set([
|
|
3178
|
+
"INVALID_CART_TOKEN",
|
|
3179
|
+
"CART_NOT_FOUND"
|
|
3180
|
+
]);
|
|
3181
|
+
function firstStableErrorCode(errors) {
|
|
3182
|
+
const candidate = errors?.[0]?.split(":", 1)[0]?.trim();
|
|
3183
|
+
return candidate && /^[A-Z][A-Z0-9_]+$/.test(candidate) ? candidate : void 0;
|
|
3184
|
+
}
|
|
3185
|
+
function getStableCartActionErrorCode(errors, fallbackCode) {
|
|
3186
|
+
return firstStableErrorCode(errors) ?? fallbackCode;
|
|
3187
|
+
}
|
|
3188
|
+
function hasRecoverableCartSessionError(errors) {
|
|
3189
|
+
return Boolean(
|
|
3190
|
+
errors?.some(
|
|
3191
|
+
(error) => RECOVERABLE_CART_SESSION_ERRORS.has(error.split(":", 1)[0]?.trim())
|
|
3192
|
+
)
|
|
3193
|
+
);
|
|
3194
|
+
}
|
|
3195
|
+
|
|
2294
3196
|
// libs/sdk/src/modules/cart.ts
|
|
2295
3197
|
function mapCartItem(raw) {
|
|
2296
3198
|
return {
|
|
2297
3199
|
id: raw.id,
|
|
2298
3200
|
productVariantId: raw.product_variant_id,
|
|
2299
3201
|
quantity: raw.quantity,
|
|
3202
|
+
unitPrice: raw.unit_price ?? null,
|
|
3203
|
+
lineTotal: raw.line_total ?? null,
|
|
3204
|
+
baseUnitPrice: raw.base_unit_price ?? raw.unit_price ?? null,
|
|
3205
|
+
unitSavings: raw.unit_savings ?? 0,
|
|
3206
|
+
lineSavings: raw.line_savings ?? 0,
|
|
3207
|
+
volumePricingEligible: raw.volume_pricing_eligible ?? false,
|
|
3208
|
+
resolvedPriceType: raw.resolved_price_type ?? null,
|
|
3209
|
+
volumePricingMinQuantity: raw.volume_pricing_min_quantity ?? null,
|
|
2300
3210
|
productVariant: {
|
|
2301
3211
|
sku: raw.product_variant.sku,
|
|
2302
3212
|
product: raw.product_variant.product,
|
|
@@ -2322,13 +3232,29 @@ function mapCart(raw, cartToken) {
|
|
|
2322
3232
|
},
|
|
2323
3233
|
status: raw.status,
|
|
2324
3234
|
createdAt: raw.created_at,
|
|
2325
|
-
updatedAt: raw.updated_at
|
|
3235
|
+
updatedAt: raw.updated_at,
|
|
3236
|
+
volumePricing: raw.volume_pricing ? {
|
|
3237
|
+
...raw.volume_pricing,
|
|
3238
|
+
totalSavings: raw.volume_pricing.totalSavings ?? 0
|
|
3239
|
+
} : {
|
|
3240
|
+
enabled: false,
|
|
3241
|
+
eligibleQuantity: 0,
|
|
3242
|
+
appliedPriceType: null,
|
|
3243
|
+
appliedMinQuantity: null,
|
|
3244
|
+
nextPriceType: null,
|
|
3245
|
+
nextMinQuantity: null,
|
|
3246
|
+
remainingQuantity: null,
|
|
3247
|
+
totalSavings: 0
|
|
3248
|
+
}
|
|
2326
3249
|
};
|
|
2327
3250
|
}
|
|
2328
3251
|
function assertActionSuccess(result, fallbackCode) {
|
|
2329
3252
|
if (!result.success || !result.order_id || !result.cart_token || !result.cart) {
|
|
2330
3253
|
const message = result.errors?.join("; ") || "Cart action failed";
|
|
2331
|
-
throw new WebsiteError(
|
|
3254
|
+
throw new WebsiteError(
|
|
3255
|
+
message,
|
|
3256
|
+
getStableCartActionErrorCode(result.errors, fallbackCode)
|
|
3257
|
+
);
|
|
2332
3258
|
}
|
|
2333
3259
|
}
|
|
2334
3260
|
function requireCartSession(storage) {
|
|
@@ -2346,7 +3272,7 @@ function createCartModule(graphqlUrl, getHeaders, getContext, cartSessionStorage
|
|
|
2346
3272
|
const data = await executeGraphQL(graphqlUrl, GET_CART, { orderId, cartToken }, getHeaders);
|
|
2347
3273
|
const result = data.websiteGetCart;
|
|
2348
3274
|
if (!result.success) {
|
|
2349
|
-
if (result.errors
|
|
3275
|
+
if (hasRecoverableCartSessionError(result.errors)) {
|
|
2350
3276
|
cartSessionStorage.clear();
|
|
2351
3277
|
return null;
|
|
2352
3278
|
}
|
|
@@ -2399,23 +3325,31 @@ function createCartModule(graphqlUrl, getHeaders, getContext, cartSessionStorage
|
|
|
2399
3325
|
"NO_CHAIN"
|
|
2400
3326
|
);
|
|
2401
3327
|
}
|
|
3328
|
+
const requestStartCheckout = async (cartToken) => {
|
|
3329
|
+
const data = await executeGraphQL(
|
|
3330
|
+
graphqlUrl,
|
|
3331
|
+
START_GUEST_CHECKOUT,
|
|
3332
|
+
{
|
|
3333
|
+
websiteId: ctx.websiteId,
|
|
3334
|
+
cartToken,
|
|
3335
|
+
contact: input.contact,
|
|
3336
|
+
fulfillmentFamily: input.fulfillmentFamily,
|
|
3337
|
+
items: input.items.map((item) => ({
|
|
3338
|
+
product_variant_id: item.productVariantId,
|
|
3339
|
+
quantity: item.quantity
|
|
3340
|
+
})),
|
|
3341
|
+
marketingAttribution: input.marketingAttribution ? serializeWebsiteMarketingAttribution(input.marketingAttribution) : void 0
|
|
3342
|
+
},
|
|
3343
|
+
getHeaders
|
|
3344
|
+
);
|
|
3345
|
+
return data.websiteStartGuestCheckout;
|
|
3346
|
+
};
|
|
2402
3347
|
const existing = cartSessionStorage.get();
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
cartToken: existing?.cartToken,
|
|
2409
|
-
contact: input.contact,
|
|
2410
|
-
fulfillmentFamily: input.fulfillmentFamily,
|
|
2411
|
-
items: input.items.map((item) => ({
|
|
2412
|
-
product_variant_id: item.productVariantId,
|
|
2413
|
-
quantity: item.quantity
|
|
2414
|
-
}))
|
|
2415
|
-
},
|
|
2416
|
-
getHeaders
|
|
2417
|
-
);
|
|
2418
|
-
const result = data.websiteStartGuestCheckout;
|
|
3348
|
+
let result = await requestStartCheckout(existing?.cartToken);
|
|
3349
|
+
if (existing && !result.success && hasRecoverableCartSessionError(result.errors)) {
|
|
3350
|
+
cartSessionStorage.clear();
|
|
3351
|
+
result = await requestStartCheckout();
|
|
3352
|
+
}
|
|
2419
3353
|
assertActionSuccess(result, "CHECKOUT_START_ERROR");
|
|
2420
3354
|
cartSessionStorage.set({
|
|
2421
3355
|
orderId: result.order_id,
|
|
@@ -2438,7 +3372,7 @@ function createCartModule(graphqlUrl, getHeaders, getContext, cartSessionStorage
|
|
|
2438
3372
|
const session = cartSessionStorage.get();
|
|
2439
3373
|
if (!session) {
|
|
2440
3374
|
throw new WebsiteError(
|
|
2441
|
-
"
|
|
3375
|
+
"Create or recover a Bacano cart session before syncing cart items",
|
|
2442
3376
|
"CART_SESSION_MISSING"
|
|
2443
3377
|
);
|
|
2444
3378
|
}
|
|
@@ -2506,11 +3440,37 @@ function createCartModule(graphqlUrl, getHeaders, getContext, cartSessionStorage
|
|
|
2506
3440
|
);
|
|
2507
3441
|
const result = data.websiteRemoveCartItem;
|
|
2508
3442
|
assertActionSuccess(result, "CART_ITEM_ERROR");
|
|
3443
|
+
const cart = mapCart(result.cart, result.cart_token);
|
|
3444
|
+
if (cart.items.length === 0) {
|
|
3445
|
+
cartSessionStorage.clear();
|
|
3446
|
+
return cart;
|
|
3447
|
+
}
|
|
2509
3448
|
cartSessionStorage.set({
|
|
2510
3449
|
orderId: result.order_id,
|
|
2511
3450
|
cartToken: result.cart_token
|
|
2512
3451
|
});
|
|
2513
|
-
return
|
|
3452
|
+
return cart;
|
|
3453
|
+
},
|
|
3454
|
+
async clear() {
|
|
3455
|
+
const session = cartSessionStorage.get();
|
|
3456
|
+
if (!session) return;
|
|
3457
|
+
const data = await executeGraphQL(
|
|
3458
|
+
graphqlUrl,
|
|
3459
|
+
DISCARD_CART,
|
|
3460
|
+
{
|
|
3461
|
+
orderId: session.orderId,
|
|
3462
|
+
cartToken: session.cartToken
|
|
3463
|
+
},
|
|
3464
|
+
getHeaders
|
|
3465
|
+
);
|
|
3466
|
+
const result = data.websiteDiscardCart;
|
|
3467
|
+
if (!result.success && !hasRecoverableCartSessionError(result.errors)) {
|
|
3468
|
+
throw new WebsiteError(
|
|
3469
|
+
result.errors?.join("; ") || "Cart could not be discarded",
|
|
3470
|
+
getStableCartActionErrorCode(result.errors, "CART_DISCARD_ERROR")
|
|
3471
|
+
);
|
|
3472
|
+
}
|
|
3473
|
+
cartSessionStorage.clear();
|
|
2514
3474
|
}
|
|
2515
3475
|
};
|
|
2516
3476
|
}
|
|
@@ -2533,7 +3493,9 @@ function createCartSessionStorage(kind, getSlug) {
|
|
|
2533
3493
|
const key = readKey();
|
|
2534
3494
|
const match = document.cookie.split("; ").find((row) => row.startsWith(`${key}=`));
|
|
2535
3495
|
if (!match) return null;
|
|
2536
|
-
return JSON.parse(
|
|
3496
|
+
return JSON.parse(
|
|
3497
|
+
decodeURIComponent(match.split("=").slice(1).join("="))
|
|
3498
|
+
);
|
|
2537
3499
|
} catch {
|
|
2538
3500
|
return null;
|
|
2539
3501
|
}
|
|
@@ -2573,46 +3535,129 @@ function createCartSessionStorage(kind, getSlug) {
|
|
|
2573
3535
|
};
|
|
2574
3536
|
}
|
|
2575
3537
|
|
|
2576
|
-
// libs/sdk/src/
|
|
2577
|
-
var
|
|
2578
|
-
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
|
|
2585
|
-
}
|
|
2586
|
-
|
|
2587
|
-
const
|
|
2588
|
-
const
|
|
2589
|
-
const
|
|
2590
|
-
|
|
2591
|
-
|
|
2592
|
-
|
|
2593
|
-
|
|
2594
|
-
|
|
2595
|
-
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
|
|
2607
|
-
|
|
2608
|
-
|
|
2609
|
-
|
|
3538
|
+
// libs/sdk/src/modules/static-catalog.ts
|
|
3539
|
+
var STATIC_CATALOG_PAGE_SIZE = 100;
|
|
3540
|
+
var STATIC_CATALOG_MAX_PRODUCTS = 1e4;
|
|
3541
|
+
var STATIC_CATALOG_INITIAL_PAGE_SIZE = 24;
|
|
3542
|
+
async function collectStaticCatalog({
|
|
3543
|
+
websiteId,
|
|
3544
|
+
snapshot: options,
|
|
3545
|
+
bootstrap,
|
|
3546
|
+
loadPage
|
|
3547
|
+
}) {
|
|
3548
|
+
const pageSize = options.pageSize ?? STATIC_CATALOG_PAGE_SIZE;
|
|
3549
|
+
const maxProducts = options.maxProducts ?? STATIC_CATALOG_MAX_PRODUCTS;
|
|
3550
|
+
const initialPageSize = bootstrap?.initialPageSize ?? STATIC_CATALOG_INITIAL_PAGE_SIZE;
|
|
3551
|
+
const sortBy = bootstrap?.sortBy ?? "name_asc";
|
|
3552
|
+
if (!Number.isInteger(pageSize) || pageSize < 1 || pageSize > 100) {
|
|
3553
|
+
throw new WebsiteError(
|
|
3554
|
+
"Static catalog pageSize must be an integer between 1 and 100.",
|
|
3555
|
+
"INVALID_STATIC_CATALOG_PAGE_SIZE"
|
|
3556
|
+
);
|
|
3557
|
+
}
|
|
3558
|
+
if (!Number.isInteger(maxProducts) || maxProducts < 1 || maxProducts > STATIC_CATALOG_MAX_PRODUCTS) {
|
|
3559
|
+
throw new WebsiteError(
|
|
3560
|
+
`Static catalog maxProducts must be an integer between 1 and ${STATIC_CATALOG_MAX_PRODUCTS}.`,
|
|
3561
|
+
"INVALID_STATIC_CATALOG_MAX_PRODUCTS"
|
|
3562
|
+
);
|
|
3563
|
+
}
|
|
3564
|
+
if (bootstrap && (!Number.isInteger(initialPageSize) || initialPageSize < 1 || initialPageSize > 100)) {
|
|
3565
|
+
throw new WebsiteError(
|
|
3566
|
+
"Static catalog initialPageSize must be an integer between 1 and 100.",
|
|
3567
|
+
"INVALID_STATIC_CATALOG_INITIAL_PAGE_SIZE"
|
|
3568
|
+
);
|
|
3569
|
+
}
|
|
3570
|
+
const generatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
3571
|
+
const products = [];
|
|
3572
|
+
let pageCount = 0;
|
|
3573
|
+
let expectedTotal = null;
|
|
3574
|
+
let firstPage = null;
|
|
3575
|
+
for (let offset = 0; ; ) {
|
|
3576
|
+
const includeBootstrapData = Boolean(bootstrap && offset === 0);
|
|
3577
|
+
const requestLimit = includeBootstrapData ? Math.max(pageSize, initialPageSize) : pageSize;
|
|
3578
|
+
const page = await loadPage({
|
|
3579
|
+
categoryListKey: options.categoryListKey,
|
|
3580
|
+
attributeListKey: options.attributeListKey,
|
|
3581
|
+
sortBy,
|
|
3582
|
+
limit: requestLimit,
|
|
3583
|
+
offset,
|
|
3584
|
+
include: {
|
|
3585
|
+
products: true,
|
|
3586
|
+
detailVariants: true,
|
|
3587
|
+
categories: includeBootstrapData,
|
|
3588
|
+
attributes: true,
|
|
3589
|
+
filters: includeBootstrapData,
|
|
3590
|
+
brands: includeBootstrapData,
|
|
3591
|
+
debug: false
|
|
3592
|
+
}
|
|
3593
|
+
});
|
|
3594
|
+
if (offset === 0) firstPage = page;
|
|
3595
|
+
pageCount += 1;
|
|
3596
|
+
const pageTotal = page.pagination.total;
|
|
3597
|
+
if (expectedTotal === null) {
|
|
3598
|
+
expectedTotal = pageTotal;
|
|
3599
|
+
} else if (pageTotal !== expectedTotal) {
|
|
3600
|
+
throwCatalogChanged();
|
|
3601
|
+
}
|
|
3602
|
+
if (pageTotal > maxProducts) {
|
|
3603
|
+
throw new WebsiteError(
|
|
3604
|
+
`Static catalog contains ${pageTotal} products and exceeds maxProducts ${maxProducts}.`,
|
|
3605
|
+
"STATIC_CATALOG_LIMIT_EXCEEDED"
|
|
3606
|
+
);
|
|
3607
|
+
}
|
|
3608
|
+
products.push(...page.products);
|
|
3609
|
+
if (!page.pagination.hasMore) break;
|
|
3610
|
+
if (page.products.length === 0) {
|
|
3611
|
+
throw new WebsiteError(
|
|
3612
|
+
"Static catalog pagination did not advance.",
|
|
3613
|
+
"STATIC_CATALOG_PAGINATION_STALLED"
|
|
3614
|
+
);
|
|
3615
|
+
}
|
|
3616
|
+
offset += page.products.length;
|
|
3617
|
+
}
|
|
3618
|
+
const total = expectedTotal ?? 0;
|
|
3619
|
+
const uniqueProductIds = new Set(products.map((product) => product.id));
|
|
3620
|
+
if (products.length !== total || uniqueProductIds.size !== total) {
|
|
3621
|
+
throwCatalogChanged();
|
|
3622
|
+
}
|
|
3623
|
+
const snapshot = {
|
|
3624
|
+
schemaVersion: 1,
|
|
3625
|
+
snapshotId: `${websiteId}:${generatedAt}`,
|
|
3626
|
+
generatedAt,
|
|
3627
|
+
websiteId,
|
|
3628
|
+
products,
|
|
3629
|
+
total,
|
|
3630
|
+
pageSize,
|
|
3631
|
+
pageCount
|
|
2610
3632
|
};
|
|
3633
|
+
const initialPage = bootstrap && firstPage ? {
|
|
3634
|
+
...firstPage,
|
|
3635
|
+
products: firstPage.products.slice(0, initialPageSize),
|
|
3636
|
+
pagination: {
|
|
3637
|
+
...firstPage.pagination,
|
|
3638
|
+
limit: initialPageSize,
|
|
3639
|
+
offset: 0,
|
|
3640
|
+
hasMore: total > initialPageSize
|
|
3641
|
+
}
|
|
3642
|
+
} : null;
|
|
3643
|
+
return { snapshot, initialPage };
|
|
3644
|
+
}
|
|
3645
|
+
function throwCatalogChanged() {
|
|
3646
|
+
throw new WebsiteError(
|
|
3647
|
+
"Static catalog changed while it was being collected. Retry the build to get a consistent snapshot.",
|
|
3648
|
+
"STATIC_CATALOG_CHANGED_DURING_BUILD"
|
|
3649
|
+
);
|
|
2611
3650
|
}
|
|
2612
3651
|
|
|
2613
3652
|
// libs/sdk/src/modules/catalog.ts
|
|
2614
3653
|
var MAX_AVAILABILITY_VARIANT_IDS2 = 100;
|
|
2615
3654
|
var DEFAULT_FILTER_ATTRIBUTE_LIST_KEY = "atributos";
|
|
3655
|
+
var MAX_LIVE_STATE_VARIANT_IDS = 100;
|
|
3656
|
+
var STATIC_CATALOG_RETRY = {
|
|
3657
|
+
maxAttempts: 4,
|
|
3658
|
+
maxTotalWaitMs: 3 * 60 * 1e3,
|
|
3659
|
+
jitterRatio: 0.1
|
|
3660
|
+
};
|
|
2616
3661
|
function mapPrice(raw) {
|
|
2617
3662
|
return {
|
|
2618
3663
|
id: raw.id,
|
|
@@ -2645,6 +3690,9 @@ function getProductImages(media) {
|
|
|
2645
3690
|
function getProductVideos(media) {
|
|
2646
3691
|
return getProductMediaByKind(media, "video");
|
|
2647
3692
|
}
|
|
3693
|
+
function getProductDetailVariants(product) {
|
|
3694
|
+
return product.detailVariants ?? product.variants;
|
|
3695
|
+
}
|
|
2648
3696
|
function getPrimaryProductImage(media) {
|
|
2649
3697
|
return getProductImages(media)[0] ?? null;
|
|
2650
3698
|
}
|
|
@@ -2794,7 +3842,6 @@ function mapRelatedProduct(raw, storageUrl) {
|
|
|
2794
3842
|
slug: raw.related_product.slug,
|
|
2795
3843
|
name: raw.related_product.name,
|
|
2796
3844
|
sku: raw.related_product.sku,
|
|
2797
|
-
displayLabel: raw.display_label,
|
|
2798
3845
|
sortOrder: raw.sort_order ?? 0,
|
|
2799
3846
|
media: raw.related_product.product_media.map(
|
|
2800
3847
|
(m) => mapMedia(m, storageUrl)
|
|
@@ -2802,6 +3849,26 @@ function mapRelatedProduct(raw, storageUrl) {
|
|
|
2802
3849
|
};
|
|
2803
3850
|
}
|
|
2804
3851
|
function mapProduct(raw, storageUrl, labels, chainId) {
|
|
3852
|
+
const detailVariants = raw.product_variants.map((variant) => mapVariant(variant, labels, chainId)).filter((variant) => variant.pricing.currentPrice != null);
|
|
3853
|
+
const listingVariantIds = raw.listing_variant_ids ? new Set(raw.listing_variant_ids) : null;
|
|
3854
|
+
const variants = listingVariantIds ? detailVariants.filter((variant) => listingVariantIds.has(variant.id)) : detailVariants;
|
|
3855
|
+
const rawOptionGroup = raw.product_option_group_members?.[0]?.group;
|
|
3856
|
+
const optionGroup = rawOptionGroup && rawOptionGroup.members.length >= 2 ? {
|
|
3857
|
+
id: rawOptionGroup.id,
|
|
3858
|
+
title: rawOptionGroup.display_title,
|
|
3859
|
+
members: rawOptionGroup.members.map((member) => ({
|
|
3860
|
+
id: member.id,
|
|
3861
|
+
productId: member.product_id,
|
|
3862
|
+
slug: member.product.slug,
|
|
3863
|
+
name: member.product.name,
|
|
3864
|
+
sku: member.product.sku,
|
|
3865
|
+
displayLabel: member.display_label,
|
|
3866
|
+
sortOrder: member.sort_order ?? 0,
|
|
3867
|
+
media: member.product.product_media.map(
|
|
3868
|
+
(media) => mapMedia(media, storageUrl)
|
|
3869
|
+
)
|
|
3870
|
+
}))
|
|
3871
|
+
} : null;
|
|
2805
3872
|
return {
|
|
2806
3873
|
id: raw.id,
|
|
2807
3874
|
name: raw.name,
|
|
@@ -2818,16 +3885,17 @@ function mapProduct(raw, storageUrl, labels, chainId) {
|
|
|
2818
3885
|
measurementUnit: raw.measurement_unit,
|
|
2819
3886
|
createdAt: raw.created_at,
|
|
2820
3887
|
updatedAt: raw.updated_at ?? raw.created_at,
|
|
2821
|
-
variants
|
|
2822
|
-
|
|
2823
|
-
),
|
|
3888
|
+
variants,
|
|
3889
|
+
detailVariants,
|
|
2824
3890
|
media: raw.product_media.map((m) => mapMedia(m, storageUrl)),
|
|
2825
3891
|
attributes: raw.product_attributes.map(
|
|
2826
3892
|
(attribute) => mapProductAttribute(attribute, labels)
|
|
2827
3893
|
),
|
|
2828
3894
|
relatedProducts: (raw.product_related_products ?? []).map(
|
|
2829
3895
|
(relation) => mapRelatedProduct(relation, storageUrl)
|
|
2830
|
-
)
|
|
3896
|
+
),
|
|
3897
|
+
optionGroup,
|
|
3898
|
+
specification: raw.product_specification ?? null
|
|
2831
3899
|
};
|
|
2832
3900
|
}
|
|
2833
3901
|
function mapCategory(raw) {
|
|
@@ -3158,7 +4226,7 @@ function hasMeaningfulCategoryListFilters(filters) {
|
|
|
3158
4226
|
)
|
|
3159
4227
|
);
|
|
3160
4228
|
}
|
|
3161
|
-
async function getWebsiteAvailabilityByVariantId(graphqlUrl, getHeaders, variantIds, chainId) {
|
|
4229
|
+
async function getWebsiteAvailabilityByVariantId(graphqlUrl, getHeaders, variantIds, chainId, websiteId) {
|
|
3162
4230
|
const uniqueVariantIds = uniqueValues2(variantIds);
|
|
3163
4231
|
const availabilityByVariantId = /* @__PURE__ */ new Map();
|
|
3164
4232
|
for (const chunk of chunkArray2(
|
|
@@ -3168,7 +4236,7 @@ async function getWebsiteAvailabilityByVariantId(graphqlUrl, getHeaders, variant
|
|
|
3168
4236
|
const data = await executeGraphQL(
|
|
3169
4237
|
graphqlUrl,
|
|
3170
4238
|
CHECK_AVAILABILITY,
|
|
3171
|
-
{ variantIds: chunk, chainId },
|
|
4239
|
+
{ variantIds: chunk, chainId, websiteId },
|
|
3172
4240
|
getHeaders
|
|
3173
4241
|
);
|
|
3174
4242
|
for (const item of data.websiteProductAvailability ?? []) {
|
|
@@ -3179,7 +4247,7 @@ async function getWebsiteAvailabilityByVariantId(graphqlUrl, getHeaders, variant
|
|
|
3179
4247
|
}
|
|
3180
4248
|
return availabilityByVariantId;
|
|
3181
4249
|
}
|
|
3182
|
-
async function filterProductsForWebsiteStock(graphqlUrl, getHeaders, products, chainId) {
|
|
4250
|
+
async function filterProductsForWebsiteStock(graphqlUrl, getHeaders, products, chainId, websiteId) {
|
|
3183
4251
|
const variantIds = products.flatMap(
|
|
3184
4252
|
(product) => product.variants.map((variant) => variant.id)
|
|
3185
4253
|
);
|
|
@@ -3190,7 +4258,8 @@ async function filterProductsForWebsiteStock(graphqlUrl, getHeaders, products, c
|
|
|
3190
4258
|
graphqlUrl,
|
|
3191
4259
|
getHeaders,
|
|
3192
4260
|
variantIds,
|
|
3193
|
-
chainId
|
|
4261
|
+
chainId,
|
|
4262
|
+
websiteId
|
|
3194
4263
|
)).entries()
|
|
3195
4264
|
).filter(([, item]) => item.inStock).map(([variantId]) => variantId)
|
|
3196
4265
|
);
|
|
@@ -3208,7 +4277,7 @@ async function filterProductsForWebsiteStock(graphqlUrl, getHeaders, products, c
|
|
|
3208
4277
|
)
|
|
3209
4278
|
})).filter((product) => product.variants.length > 0);
|
|
3210
4279
|
}
|
|
3211
|
-
async function filterSeoProductsForWebsiteStock(graphqlUrl, getHeaders, products, chainId) {
|
|
4280
|
+
async function filterSeoProductsForWebsiteStock(graphqlUrl, getHeaders, products, chainId, websiteId) {
|
|
3212
4281
|
const variantIds = products.flatMap(
|
|
3213
4282
|
(product) => product.product_variants.map((variant) => variant.id)
|
|
3214
4283
|
);
|
|
@@ -3219,7 +4288,8 @@ async function filterSeoProductsForWebsiteStock(graphqlUrl, getHeaders, products
|
|
|
3219
4288
|
graphqlUrl,
|
|
3220
4289
|
getHeaders,
|
|
3221
4290
|
variantIds,
|
|
3222
|
-
chainId
|
|
4291
|
+
chainId,
|
|
4292
|
+
websiteId
|
|
3223
4293
|
)).entries()
|
|
3224
4294
|
).filter(([, item]) => item.inStock).map(([variantId]) => variantId)
|
|
3225
4295
|
);
|
|
@@ -3250,6 +4320,32 @@ function mapProductSeoEntry(raw, storageUrl) {
|
|
|
3250
4320
|
};
|
|
3251
4321
|
}
|
|
3252
4322
|
function createCatalogModule(graphqlUrl, getHeaders, getContext, storageUrl) {
|
|
4323
|
+
const loadProductSpecifications = async (productIds) => {
|
|
4324
|
+
const ctx = getContext();
|
|
4325
|
+
const uniqueProductIds = [...new Set(productIds)];
|
|
4326
|
+
const specifications = {};
|
|
4327
|
+
for (let offset = 0; offset < uniqueProductIds.length; offset += 100) {
|
|
4328
|
+
const batch = uniqueProductIds.slice(offset, offset + 100);
|
|
4329
|
+
try {
|
|
4330
|
+
const data = await executeGraphQL(
|
|
4331
|
+
graphqlUrl,
|
|
4332
|
+
WEBSITE_GET_PRODUCT_SPECIFICATIONS,
|
|
4333
|
+
{ websiteId: ctx.websiteId, productIds: batch },
|
|
4334
|
+
getHeaders
|
|
4335
|
+
);
|
|
4336
|
+
if (!data.result?.success) continue;
|
|
4337
|
+
Object.assign(
|
|
4338
|
+
specifications,
|
|
4339
|
+
parseActionJson(
|
|
4340
|
+
data.result.specifications,
|
|
4341
|
+
{}
|
|
4342
|
+
)
|
|
4343
|
+
);
|
|
4344
|
+
} catch {
|
|
4345
|
+
}
|
|
4346
|
+
}
|
|
4347
|
+
return specifications;
|
|
4348
|
+
};
|
|
3253
4349
|
const mapSingleProductResult = async (data) => {
|
|
3254
4350
|
const ctx = getContext();
|
|
3255
4351
|
if (!ctx.chainId) {
|
|
@@ -3263,15 +4359,187 @@ function createCatalogModule(graphqlUrl, getHeaders, getContext, storageUrl) {
|
|
|
3263
4359
|
const publicAttributeLabels = buildPublicAttributeLabels(
|
|
3264
4360
|
data.catalog_attribute_lists
|
|
3265
4361
|
);
|
|
4362
|
+
const mappedProduct = mapProduct(
|
|
4363
|
+
product,
|
|
4364
|
+
storageUrl,
|
|
4365
|
+
publicAttributeLabels,
|
|
4366
|
+
ctx.chainId
|
|
4367
|
+
);
|
|
3266
4368
|
const filteredProducts = await filterProductsForWebsiteStock(
|
|
3267
4369
|
graphqlUrl,
|
|
3268
4370
|
getHeaders,
|
|
3269
|
-
[
|
|
3270
|
-
ctx.chainId
|
|
4371
|
+
[mappedProduct],
|
|
4372
|
+
ctx.chainId,
|
|
4373
|
+
ctx.websiteId
|
|
3271
4374
|
);
|
|
3272
|
-
|
|
4375
|
+
if (!filteredProducts[0]) return null;
|
|
4376
|
+
const specifications = await loadProductSpecifications([product.id]);
|
|
4377
|
+
return {
|
|
4378
|
+
...mappedProduct,
|
|
4379
|
+
variants: getProductDetailVariants(mappedProduct),
|
|
4380
|
+
specification: specifications[product.id] ?? null
|
|
4381
|
+
};
|
|
4382
|
+
};
|
|
4383
|
+
const loadCatalogPage = async (opts, retryRateLimits = false) => {
|
|
4384
|
+
const ctx = getContext();
|
|
4385
|
+
if (!ctx.chainId) {
|
|
4386
|
+
throw new WebsiteError(
|
|
4387
|
+
"This website has no chain configured. Catalog requires a chain_id.",
|
|
4388
|
+
"NO_CHAIN"
|
|
4389
|
+
);
|
|
4390
|
+
}
|
|
4391
|
+
const chainId = ctx.chainId;
|
|
4392
|
+
const filters = {
|
|
4393
|
+
category_slug: opts.categorySlug,
|
|
4394
|
+
subcategory_slug: opts.subcategorySlug,
|
|
4395
|
+
tags: opts.tags,
|
|
4396
|
+
search: opts.search,
|
|
4397
|
+
brand_id: opts.brandId,
|
|
4398
|
+
min_price: opts.minPrice,
|
|
4399
|
+
max_price: opts.maxPrice,
|
|
4400
|
+
attributes_by_slug: opts.attributesBySlug,
|
|
4401
|
+
technical_by_field: opts.technicalByField ? Object.fromEntries(
|
|
4402
|
+
Object.entries(opts.technicalByField).map(([fieldId, filter]) => [
|
|
4403
|
+
fieldId,
|
|
4404
|
+
{
|
|
4405
|
+
option_ids: filter.optionIds,
|
|
4406
|
+
boolean_values: filter.booleanValues,
|
|
4407
|
+
min: filter.min,
|
|
4408
|
+
max: filter.max,
|
|
4409
|
+
date_from: filter.dateFrom,
|
|
4410
|
+
date_to: filter.dateTo
|
|
4411
|
+
}
|
|
4412
|
+
])
|
|
4413
|
+
) : void 0,
|
|
4414
|
+
sort_by: opts.sortBy,
|
|
4415
|
+
limit: opts.limit,
|
|
4416
|
+
offset: opts.offset
|
|
4417
|
+
};
|
|
4418
|
+
const data = await executeGraphQL(
|
|
4419
|
+
graphqlUrl,
|
|
4420
|
+
WEBSITE_GET_CATALOG_PAGE,
|
|
4421
|
+
{
|
|
4422
|
+
websiteId: ctx.websiteId,
|
|
4423
|
+
categoryListKey: opts.categoryListKey,
|
|
4424
|
+
attributeListKey: opts.attributeListKey,
|
|
4425
|
+
filters,
|
|
4426
|
+
include: opts.include
|
|
4427
|
+
},
|
|
4428
|
+
getHeaders,
|
|
4429
|
+
retryRateLimits ? { retry: STATIC_CATALOG_RETRY } : void 0
|
|
4430
|
+
);
|
|
4431
|
+
if (!data.result.success) {
|
|
4432
|
+
throw new WebsiteError(
|
|
4433
|
+
data.result.errors.join("; ") || "Unable to resolve catalog page",
|
|
4434
|
+
"CATALOG_PAGE_ERROR"
|
|
4435
|
+
);
|
|
4436
|
+
}
|
|
4437
|
+
const products = parseActionJson(data.result.products, []);
|
|
4438
|
+
const categoryList = parseActionJson(
|
|
4439
|
+
data.result.category_list,
|
|
4440
|
+
null
|
|
4441
|
+
);
|
|
4442
|
+
const attributeList = parseActionJson(
|
|
4443
|
+
data.result.attribute_list,
|
|
4444
|
+
null
|
|
4445
|
+
);
|
|
4446
|
+
const filtersResult = parseActionJson(
|
|
4447
|
+
data.result.filters,
|
|
4448
|
+
null
|
|
4449
|
+
);
|
|
4450
|
+
const brands = parseActionJson(data.result.brands, []);
|
|
4451
|
+
const pagination = parseActionJson(
|
|
4452
|
+
data.result.pagination,
|
|
4453
|
+
{
|
|
4454
|
+
limit: opts.limit ?? 24,
|
|
4455
|
+
offset: opts.offset ?? 0,
|
|
4456
|
+
total: 0,
|
|
4457
|
+
hasMore: false
|
|
4458
|
+
}
|
|
4459
|
+
);
|
|
4460
|
+
const appliedFilters = parseActionJson(
|
|
4461
|
+
data.result.applied_filters,
|
|
4462
|
+
{}
|
|
4463
|
+
);
|
|
4464
|
+
const debug = parseActionJson(
|
|
4465
|
+
data.result.debug,
|
|
4466
|
+
null
|
|
4467
|
+
);
|
|
4468
|
+
const labels = buildPublicAttributeLabels(
|
|
4469
|
+
attributeList ? [attributeList] : []
|
|
4470
|
+
);
|
|
4471
|
+
return {
|
|
4472
|
+
products: products.map(
|
|
4473
|
+
(product) => mapProduct(product, storageUrl, labels, chainId)
|
|
4474
|
+
),
|
|
4475
|
+
categories: categoryList ? mapCategoryList(categoryList) : null,
|
|
4476
|
+
attributes: attributeList ? mapAttributeList(attributeList) : null,
|
|
4477
|
+
filters: filtersResult,
|
|
4478
|
+
brands,
|
|
4479
|
+
pagination,
|
|
4480
|
+
appliedFilters,
|
|
4481
|
+
debug
|
|
4482
|
+
};
|
|
3273
4483
|
};
|
|
3274
4484
|
return {
|
|
4485
|
+
async getProductLiveState(productVariantIds) {
|
|
4486
|
+
const ctx = getContext();
|
|
4487
|
+
if (!ctx.chainId) {
|
|
4488
|
+
throw new WebsiteError(
|
|
4489
|
+
"This website has no chain configured. Product live state requires a chain_id.",
|
|
4490
|
+
"NO_CHAIN"
|
|
4491
|
+
);
|
|
4492
|
+
}
|
|
4493
|
+
const chainId = ctx.chainId;
|
|
4494
|
+
const variantIds = [...new Set(productVariantIds)];
|
|
4495
|
+
if (variantIds.length === 0) return [];
|
|
4496
|
+
if (variantIds.length > MAX_LIVE_STATE_VARIANT_IDS) {
|
|
4497
|
+
throw new WebsiteError(
|
|
4498
|
+
`Product live state accepts at most ${MAX_LIVE_STATE_VARIANT_IDS} variants per request.`,
|
|
4499
|
+
"PRODUCT_LIVE_STATE_LIMIT_EXCEEDED"
|
|
4500
|
+
);
|
|
4501
|
+
}
|
|
4502
|
+
const data = await executeGraphQL(
|
|
4503
|
+
graphqlUrl,
|
|
4504
|
+
GET_PRODUCT_LIVE_STATE,
|
|
4505
|
+
{
|
|
4506
|
+
variantIds,
|
|
4507
|
+
companyId: ctx.companyId,
|
|
4508
|
+
chainId,
|
|
4509
|
+
websiteId: ctx.websiteId
|
|
4510
|
+
},
|
|
4511
|
+
getHeaders
|
|
4512
|
+
);
|
|
4513
|
+
const variantsById = new Map(
|
|
4514
|
+
(data.product_variants ?? []).map((variant) => [variant.id, variant])
|
|
4515
|
+
);
|
|
4516
|
+
const availabilityById = new Map(
|
|
4517
|
+
(data.websiteProductAvailability ?? []).map((availability) => [
|
|
4518
|
+
availability.product_variant_id,
|
|
4519
|
+
availability
|
|
4520
|
+
])
|
|
4521
|
+
);
|
|
4522
|
+
return variantIds.flatMap((variantId) => {
|
|
4523
|
+
const variant = variantsById.get(variantId);
|
|
4524
|
+
if (!variant) return [];
|
|
4525
|
+
const availability = availabilityById.get(variantId);
|
|
4526
|
+
const pricing = resolveProductPricing(
|
|
4527
|
+
selectPublicPrices(
|
|
4528
|
+
variant.product_variant_prices.map(mapPrice),
|
|
4529
|
+
chainId
|
|
4530
|
+
)
|
|
4531
|
+
);
|
|
4532
|
+
return [
|
|
4533
|
+
{
|
|
4534
|
+
productVariantId: variantId,
|
|
4535
|
+
inStock: availability?.in_stock ?? false,
|
|
4536
|
+
availabilityLabel: availability?.label ?? "Agotado",
|
|
4537
|
+
maxPurchasableQuantity: availability?.max_purchasable_quantity ?? 0,
|
|
4538
|
+
pricing
|
|
4539
|
+
}
|
|
4540
|
+
];
|
|
4541
|
+
});
|
|
4542
|
+
},
|
|
3275
4543
|
async getProductRecommendations(opts) {
|
|
3276
4544
|
const ctx = getContext();
|
|
3277
4545
|
const limit = opts.limit ?? 16;
|
|
@@ -3334,7 +4602,8 @@ function createCatalogModule(graphqlUrl, getHeaders, getContext, storageUrl) {
|
|
|
3334
4602
|
graphqlUrl,
|
|
3335
4603
|
getHeaders,
|
|
3336
4604
|
data.products ?? [],
|
|
3337
|
-
ctx.chainId
|
|
4605
|
+
ctx.chainId,
|
|
4606
|
+
ctx.websiteId
|
|
3338
4607
|
);
|
|
3339
4608
|
const entries = availableProducts.slice(0, limit).map((product) => mapProductSeoEntry(product, storageUrl)).filter((entry) => Boolean(entry));
|
|
3340
4609
|
return {
|
|
@@ -3346,86 +4615,36 @@ function createCatalogModule(graphqlUrl, getHeaders, getContext, storageUrl) {
|
|
|
3346
4615
|
}
|
|
3347
4616
|
};
|
|
3348
4617
|
},
|
|
3349
|
-
|
|
4618
|
+
getCatalogPage: loadCatalogPage,
|
|
4619
|
+
async getStaticCatalogSnapshot(opts) {
|
|
3350
4620
|
const ctx = getContext();
|
|
3351
|
-
|
|
3352
|
-
|
|
3353
|
-
|
|
3354
|
-
|
|
3355
|
-
|
|
3356
|
-
|
|
3357
|
-
|
|
3358
|
-
const
|
|
3359
|
-
|
|
3360
|
-
|
|
3361
|
-
|
|
3362
|
-
|
|
3363
|
-
|
|
3364
|
-
|
|
3365
|
-
max_price: opts.maxPrice,
|
|
3366
|
-
attributes_by_slug: opts.attributesBySlug,
|
|
3367
|
-
sort_by: opts.sortBy,
|
|
3368
|
-
limit: opts.limit,
|
|
3369
|
-
offset: opts.offset
|
|
3370
|
-
};
|
|
3371
|
-
const data = await executeGraphQL(
|
|
3372
|
-
graphqlUrl,
|
|
3373
|
-
WEBSITE_GET_CATALOG_PAGE,
|
|
3374
|
-
{
|
|
3375
|
-
websiteId: ctx.websiteId,
|
|
3376
|
-
categoryListKey: opts.categoryListKey,
|
|
3377
|
-
attributeListKey: opts.attributeListKey,
|
|
3378
|
-
filters,
|
|
3379
|
-
include: opts.include
|
|
4621
|
+
return (await collectStaticCatalog({
|
|
4622
|
+
websiteId: ctx.websiteId,
|
|
4623
|
+
snapshot: opts,
|
|
4624
|
+
loadPage: (options) => loadCatalogPage(options, true)
|
|
4625
|
+
})).snapshot;
|
|
4626
|
+
},
|
|
4627
|
+
async getStaticCatalogBootstrap(opts) {
|
|
4628
|
+
const ctx = getContext();
|
|
4629
|
+
const { snapshot, initialPage } = await collectStaticCatalog({
|
|
4630
|
+
websiteId: ctx.websiteId,
|
|
4631
|
+
snapshot: opts,
|
|
4632
|
+
bootstrap: {
|
|
4633
|
+
initialPageSize: opts.initialPageSize,
|
|
4634
|
+
sortBy: opts.sortBy
|
|
3380
4635
|
},
|
|
3381
|
-
|
|
3382
|
-
);
|
|
3383
|
-
if (!
|
|
4636
|
+
loadPage: (options) => loadCatalogPage(options, true)
|
|
4637
|
+
});
|
|
4638
|
+
if (!initialPage) {
|
|
3384
4639
|
throw new WebsiteError(
|
|
3385
|
-
|
|
3386
|
-
"
|
|
4640
|
+
"Static catalog bootstrap could not resolve its initial page.",
|
|
4641
|
+
"STATIC_CATALOG_BOOTSTRAP_MISSING_INITIAL_PAGE"
|
|
3387
4642
|
);
|
|
3388
4643
|
}
|
|
3389
|
-
const products = parseActionJson(data.result.products, []);
|
|
3390
|
-
const categoryList = parseActionJson(
|
|
3391
|
-
data.result.category_list,
|
|
3392
|
-
null
|
|
3393
|
-
);
|
|
3394
|
-
const attributeList = parseActionJson(
|
|
3395
|
-
data.result.attribute_list,
|
|
3396
|
-
null
|
|
3397
|
-
);
|
|
3398
|
-
const filtersResult = parseActionJson(
|
|
3399
|
-
data.result.filters,
|
|
3400
|
-
null
|
|
3401
|
-
);
|
|
3402
|
-
const brands = parseActionJson(data.result.brands, []);
|
|
3403
|
-
const pagination = parseActionJson(
|
|
3404
|
-
data.result.pagination,
|
|
3405
|
-
{ limit: opts.limit ?? 24, offset: opts.offset ?? 0, total: 0, hasMore: false }
|
|
3406
|
-
);
|
|
3407
|
-
const appliedFilters = parseActionJson(
|
|
3408
|
-
data.result.applied_filters,
|
|
3409
|
-
{}
|
|
3410
|
-
);
|
|
3411
|
-
const debug = parseActionJson(
|
|
3412
|
-
data.result.debug,
|
|
3413
|
-
null
|
|
3414
|
-
);
|
|
3415
|
-
const labels = buildPublicAttributeLabels(
|
|
3416
|
-
attributeList ? [attributeList] : []
|
|
3417
|
-
);
|
|
3418
4644
|
return {
|
|
3419
|
-
|
|
3420
|
-
|
|
3421
|
-
|
|
3422
|
-
categories: categoryList ? mapCategoryList(categoryList) : null,
|
|
3423
|
-
attributes: attributeList ? mapAttributeList(attributeList) : null,
|
|
3424
|
-
filters: filtersResult,
|
|
3425
|
-
brands,
|
|
3426
|
-
pagination,
|
|
3427
|
-
appliedFilters,
|
|
3428
|
-
debug
|
|
4645
|
+
...snapshot,
|
|
4646
|
+
bootstrapSchemaVersion: 1,
|
|
4647
|
+
initialPage
|
|
3429
4648
|
};
|
|
3430
4649
|
},
|
|
3431
4650
|
async getProducts(opts) {
|
|
@@ -3460,8 +4679,16 @@ function createCatalogModule(graphqlUrl, getHeaders, getContext, storageUrl) {
|
|
|
3460
4679
|
(data.products ?? []).map(
|
|
3461
4680
|
(p) => mapProduct(p, storageUrl, publicAttributeLabels, chainId)
|
|
3462
4681
|
),
|
|
3463
|
-
chainId
|
|
4682
|
+
chainId,
|
|
4683
|
+
ctx.websiteId
|
|
3464
4684
|
);
|
|
4685
|
+
const specifications = await loadProductSpecifications(
|
|
4686
|
+
products.map((product) => product.id)
|
|
4687
|
+
);
|
|
4688
|
+
products = products.map((product) => ({
|
|
4689
|
+
...product,
|
|
4690
|
+
specification: specifications[product.id] ?? null
|
|
4691
|
+
}));
|
|
3465
4692
|
if (opts?.minPrice !== void 0 || opts?.maxPrice !== void 0) {
|
|
3466
4693
|
products = products.filter(
|
|
3467
4694
|
(product) => product.variants.some((variant) => {
|
|
@@ -3572,7 +4799,8 @@ function createCatalogModule(graphqlUrl, getHeaders, getContext, storageUrl) {
|
|
|
3572
4799
|
graphqlUrl,
|
|
3573
4800
|
getHeaders,
|
|
3574
4801
|
variantIds,
|
|
3575
|
-
chainId
|
|
4802
|
+
chainId,
|
|
4803
|
+
ctx.websiteId
|
|
3576
4804
|
);
|
|
3577
4805
|
const applicableCategories = /* @__PURE__ */ new Set();
|
|
3578
4806
|
const applicableSubcategoryIds = /* @__PURE__ */ new Set();
|
|
@@ -3690,7 +4918,8 @@ function createCatalogModule(graphqlUrl, getHeaders, getContext, storageUrl) {
|
|
|
3690
4918
|
graphqlUrl,
|
|
3691
4919
|
getHeaders,
|
|
3692
4920
|
variantIds,
|
|
3693
|
-
chainId
|
|
4921
|
+
chainId,
|
|
4922
|
+
ctx.websiteId
|
|
3694
4923
|
);
|
|
3695
4924
|
const publicAttributeLabels = buildPublicAttributeLabels(
|
|
3696
4925
|
data.catalog_attribute_lists
|
|
@@ -3795,7 +5024,8 @@ function createCatalogModule(graphqlUrl, getHeaders, getContext, storageUrl) {
|
|
|
3795
5024
|
min: minPrice,
|
|
3796
5025
|
max: maxPrice
|
|
3797
5026
|
},
|
|
3798
|
-
attributes
|
|
5027
|
+
attributes,
|
|
5028
|
+
technical: []
|
|
3799
5029
|
};
|
|
3800
5030
|
},
|
|
3801
5031
|
async getSearchSuggestions(opts) {
|
|
@@ -3849,6 +5079,14 @@ function mapCartItem2(raw) {
|
|
|
3849
5079
|
id: raw.id,
|
|
3850
5080
|
productVariantId: raw.product_variant_id,
|
|
3851
5081
|
quantity: raw.quantity,
|
|
5082
|
+
unitPrice: raw.unit_price ?? null,
|
|
5083
|
+
lineTotal: raw.line_total ?? null,
|
|
5084
|
+
baseUnitPrice: raw.base_unit_price ?? raw.unit_price ?? null,
|
|
5085
|
+
unitSavings: raw.unit_savings ?? 0,
|
|
5086
|
+
lineSavings: raw.line_savings ?? 0,
|
|
5087
|
+
volumePricingEligible: raw.volume_pricing_eligible ?? false,
|
|
5088
|
+
resolvedPriceType: raw.resolved_price_type ?? null,
|
|
5089
|
+
volumePricingMinQuantity: raw.volume_pricing_min_quantity ?? null,
|
|
3852
5090
|
productVariant: {
|
|
3853
5091
|
sku: raw.product_variant.sku,
|
|
3854
5092
|
product: raw.product_variant.product,
|
|
@@ -3873,7 +5111,20 @@ function mapCart2(raw) {
|
|
|
3873
5111
|
},
|
|
3874
5112
|
status: raw.status,
|
|
3875
5113
|
createdAt: raw.created_at,
|
|
3876
|
-
updatedAt: raw.updated_at
|
|
5114
|
+
updatedAt: raw.updated_at,
|
|
5115
|
+
volumePricing: raw.volume_pricing ? {
|
|
5116
|
+
...raw.volume_pricing,
|
|
5117
|
+
totalSavings: raw.volume_pricing.totalSavings ?? 0
|
|
5118
|
+
} : {
|
|
5119
|
+
enabled: false,
|
|
5120
|
+
eligibleQuantity: 0,
|
|
5121
|
+
appliedPriceType: null,
|
|
5122
|
+
appliedMinQuantity: null,
|
|
5123
|
+
nextPriceType: null,
|
|
5124
|
+
nextMinQuantity: null,
|
|
5125
|
+
remainingQuantity: null,
|
|
5126
|
+
totalSavings: 0
|
|
5127
|
+
}
|
|
3877
5128
|
};
|
|
3878
5129
|
}
|
|
3879
5130
|
function mapCouponResult(raw) {
|
|
@@ -3892,6 +5143,7 @@ function mapCouponResult(raw) {
|
|
|
3892
5143
|
};
|
|
3893
5144
|
}
|
|
3894
5145
|
function createCheckoutModule(graphqlUrl, getHeaders, getContext, cartSessionStorage) {
|
|
5146
|
+
let shippingPolicyCache = null;
|
|
3895
5147
|
return {
|
|
3896
5148
|
async getLocationOptions() {
|
|
3897
5149
|
const context = getContext();
|
|
@@ -3989,22 +5241,39 @@ function createCheckoutModule(graphqlUrl, getHeaders, getContext, cartSessionSto
|
|
|
3989
5241
|
}));
|
|
3990
5242
|
},
|
|
3991
5243
|
async getShippingPolicy() {
|
|
5244
|
+
const now = Date.now();
|
|
5245
|
+
if (shippingPolicyCache && shippingPolicyCache.expiresAt > now) {
|
|
5246
|
+
return shippingPolicyCache.request;
|
|
5247
|
+
}
|
|
3992
5248
|
const context = getContext();
|
|
3993
|
-
const
|
|
5249
|
+
const request = executeGraphQL(
|
|
3994
5250
|
graphqlUrl,
|
|
3995
5251
|
GET_SHIPPING_POLICY,
|
|
3996
5252
|
{ websiteId: context.websiteId },
|
|
3997
5253
|
getHeaders
|
|
3998
|
-
)
|
|
3999
|
-
|
|
4000
|
-
|
|
4001
|
-
|
|
4002
|
-
|
|
4003
|
-
|
|
4004
|
-
|
|
4005
|
-
|
|
4006
|
-
|
|
5254
|
+
).then((data) => {
|
|
5255
|
+
const result = data.websiteGetShippingPolicy;
|
|
5256
|
+
if (!result.success || !result.policy) {
|
|
5257
|
+
throw new CheckoutError(result.errors ?? ["SHIPPING_POLICY_FAILED"]);
|
|
5258
|
+
}
|
|
5259
|
+
return {
|
|
5260
|
+
freeShippingEnabled: result.policy.free_shipping_enabled,
|
|
5261
|
+
freeShippingThreshold: result.policy.free_shipping_threshold,
|
|
5262
|
+
currency: result.policy.currency
|
|
5263
|
+
};
|
|
5264
|
+
});
|
|
5265
|
+
shippingPolicyCache = {
|
|
5266
|
+
expiresAt: now + 5 * 60 * 1e3,
|
|
5267
|
+
request
|
|
4007
5268
|
};
|
|
5269
|
+
try {
|
|
5270
|
+
return await request;
|
|
5271
|
+
} catch (error) {
|
|
5272
|
+
if (shippingPolicyCache?.request === request) {
|
|
5273
|
+
shippingPolicyCache = null;
|
|
5274
|
+
}
|
|
5275
|
+
throw error;
|
|
5276
|
+
}
|
|
4008
5277
|
},
|
|
4009
5278
|
async getFreeShippingProgress(input) {
|
|
4010
5279
|
if (input.items.length === 0) {
|
|
@@ -4170,6 +5439,13 @@ function createCheckoutModule(graphqlUrl, getHeaders, getContext, cartSessionSto
|
|
|
4170
5439
|
initialPayment: input.initialPayment ? {
|
|
4171
5440
|
amount: input.initialPayment.amount,
|
|
4172
5441
|
payment_method_id: input.initialPayment.paymentMethodId
|
|
5442
|
+
} : void 0,
|
|
5443
|
+
marketingAttribution: input.marketingAttribution ? serializeWebsiteMarketingAttribution(input.marketingAttribution) : void 0,
|
|
5444
|
+
advertisingConsent: input.advertisingConsent ? {
|
|
5445
|
+
policy_id: input.advertisingConsent.policyId,
|
|
5446
|
+
policy_version: input.advertisingConsent.policyVersion,
|
|
5447
|
+
statement_hash: input.advertisingConsent.statementHash,
|
|
5448
|
+
granted: input.advertisingConsent.granted
|
|
4173
5449
|
} : void 0
|
|
4174
5450
|
},
|
|
4175
5451
|
getHeaders
|
|
@@ -4305,18 +5581,23 @@ function readOptionalText(value) {
|
|
|
4305
5581
|
function enrichMediaValue(value, storageUrl) {
|
|
4306
5582
|
const fileId = typeof value.fileId === "string" && value.fileId.trim() ? value.fileId.trim() : "";
|
|
4307
5583
|
if (!fileId) return value;
|
|
4308
|
-
const
|
|
5584
|
+
const isProductMedia = typeof value.id === "string" && typeof value.order === "number";
|
|
5585
|
+
const isEditorialImage = value.mediaKind === "image" && !isProductMedia;
|
|
5586
|
+
const originalWidth = typeof value.originalWidth === "number" ? value.originalWidth : null;
|
|
5587
|
+
const urls = isEditorialImage ? buildStorageContentMediaUrls(storageUrl, fileId, originalWidth) : buildStorageMediaUrls(storageUrl, fileId);
|
|
4309
5588
|
return {
|
|
4310
5589
|
...value,
|
|
4311
5590
|
...urls,
|
|
4312
|
-
url: value.url ?? urls.originalUrl
|
|
5591
|
+
url: value.url ?? urls.originalUrl,
|
|
5592
|
+
src: value.src ?? value.url ?? urls.originalUrl
|
|
4313
5593
|
};
|
|
4314
5594
|
}
|
|
4315
5595
|
function normalizeRichTextNode(value, storageUrl) {
|
|
4316
5596
|
const type = value.type;
|
|
4317
5597
|
const attrs = isRecord(value.attrs) ? value.attrs : null;
|
|
4318
5598
|
if (attrs && (type === "bacanoImage" || type === "bacanoVideo") && typeof attrs.fileId === "string") {
|
|
4319
|
-
const
|
|
5599
|
+
const originalWidth = typeof attrs.originalWidth === "number" ? attrs.originalWidth : null;
|
|
5600
|
+
const urls = type === "bacanoImage" ? buildStorageContentMediaUrls(storageUrl, attrs.fileId, originalWidth) : buildStorageMediaUrls(storageUrl, attrs.fileId);
|
|
4320
5601
|
const altText = readOptionalText(attrs.altText) ?? readOptionalText(attrs.alt);
|
|
4321
5602
|
const caption = readOptionalText(attrs.caption);
|
|
4322
5603
|
const showCaption = Boolean(caption) && attrs.showCaption !== false;
|
|
@@ -4448,6 +5729,452 @@ function createContentModule(graphqlUrl, getHeaders, getContext, storageUrl) {
|
|
|
4448
5729
|
};
|
|
4449
5730
|
}
|
|
4450
5731
|
|
|
5732
|
+
// libs/sdk/src/marketing/clarity.ts
|
|
5733
|
+
var initializedDestinations = /* @__PURE__ */ new Set();
|
|
5734
|
+
function clarityWindow() {
|
|
5735
|
+
return typeof window === "undefined" ? null : window;
|
|
5736
|
+
}
|
|
5737
|
+
function prepareClarity(projectId) {
|
|
5738
|
+
const target = clarityWindow();
|
|
5739
|
+
if (!target || initializedDestinations.has(projectId)) return;
|
|
5740
|
+
if (!target.clarity) {
|
|
5741
|
+
const clarity = function() {
|
|
5742
|
+
clarity.q ??= [];
|
|
5743
|
+
clarity.q.push(arguments);
|
|
5744
|
+
};
|
|
5745
|
+
target.clarity = clarity;
|
|
5746
|
+
}
|
|
5747
|
+
initializedDestinations.add(projectId);
|
|
5748
|
+
}
|
|
5749
|
+
function loadClarityScript(projectId) {
|
|
5750
|
+
if (typeof document === "undefined") return;
|
|
5751
|
+
const id = `bacano-clarity-${projectId}`;
|
|
5752
|
+
if (document.getElementById(id)) return;
|
|
5753
|
+
const script = document.createElement("script");
|
|
5754
|
+
script.id = id;
|
|
5755
|
+
script.async = true;
|
|
5756
|
+
script.src = `https://www.clarity.ms/tag/${encodeURIComponent(projectId)}`;
|
|
5757
|
+
script.referrerPolicy = "strict-origin-when-cross-origin";
|
|
5758
|
+
document.head.appendChild(script);
|
|
5759
|
+
}
|
|
5760
|
+
function sendClarityEvent(eventName) {
|
|
5761
|
+
clarityWindow()?.clarity?.("event", eventName);
|
|
5762
|
+
}
|
|
5763
|
+
|
|
5764
|
+
// libs/sdk/src/marketing/domain.ts
|
|
5765
|
+
function matchesCanonicalMarketingHost(canonicalDomain, currentHostname) {
|
|
5766
|
+
if (canonicalDomain === void 0) return true;
|
|
5767
|
+
if (!canonicalDomain?.trim()) return false;
|
|
5768
|
+
try {
|
|
5769
|
+
const configuredHost = new URL(
|
|
5770
|
+
canonicalDomain.includes("://") ? canonicalDomain : `https://${canonicalDomain}`
|
|
5771
|
+
).hostname.toLowerCase();
|
|
5772
|
+
return currentHostname.toLowerCase() === configuredHost;
|
|
5773
|
+
} catch {
|
|
5774
|
+
return false;
|
|
5775
|
+
}
|
|
5776
|
+
}
|
|
5777
|
+
|
|
5778
|
+
// libs/sdk/src/marketing/ga4.ts
|
|
5779
|
+
function mapGa4Items(items) {
|
|
5780
|
+
return items?.map((item) => ({
|
|
5781
|
+
item_id: item.itemId,
|
|
5782
|
+
item_name: item.itemName,
|
|
5783
|
+
item_variant: item.itemVariant,
|
|
5784
|
+
item_brand: item.itemBrand,
|
|
5785
|
+
item_category: item.itemCategory,
|
|
5786
|
+
price: item.price,
|
|
5787
|
+
quantity: item.quantity,
|
|
5788
|
+
index: item.index,
|
|
5789
|
+
sku: item.sku
|
|
5790
|
+
}));
|
|
5791
|
+
}
|
|
5792
|
+
|
|
5793
|
+
// libs/sdk/src/marketing/types.ts
|
|
5794
|
+
var MARKETING_SCHEMA_VERSION = 1;
|
|
5795
|
+
var WEBSITE_MARKETING_CONTACT_PLACEMENTS = [
|
|
5796
|
+
"product_detail",
|
|
5797
|
+
"floating",
|
|
5798
|
+
"header",
|
|
5799
|
+
"footer",
|
|
5800
|
+
"contact_page"
|
|
5801
|
+
];
|
|
5802
|
+
|
|
5803
|
+
// libs/sdk/src/marketing/runtime.ts
|
|
5804
|
+
var initializedGa4Destinations = /* @__PURE__ */ new Set();
|
|
5805
|
+
var initializedMetaDestinations = /* @__PURE__ */ new Set();
|
|
5806
|
+
function marketingWindow() {
|
|
5807
|
+
return typeof window === "undefined" ? null : window;
|
|
5808
|
+
}
|
|
5809
|
+
function loadScriptOnce(id, source) {
|
|
5810
|
+
if (typeof document === "undefined" || document.getElementById(id)) return;
|
|
5811
|
+
const script = document.createElement("script");
|
|
5812
|
+
script.id = id;
|
|
5813
|
+
script.async = true;
|
|
5814
|
+
script.src = source;
|
|
5815
|
+
script.referrerPolicy = "strict-origin-when-cross-origin";
|
|
5816
|
+
document.head.appendChild(script);
|
|
5817
|
+
}
|
|
5818
|
+
function prepareGa4(measurementId) {
|
|
5819
|
+
const target = marketingWindow();
|
|
5820
|
+
if (!target) return;
|
|
5821
|
+
if (initializedGa4Destinations.has(measurementId)) return;
|
|
5822
|
+
target.dataLayer ??= [];
|
|
5823
|
+
target.gtag ??= function() {
|
|
5824
|
+
target.dataLayer?.push(arguments);
|
|
5825
|
+
};
|
|
5826
|
+
target.gtag("js", /* @__PURE__ */ new Date());
|
|
5827
|
+
target.gtag("config", measurementId, { send_page_view: false });
|
|
5828
|
+
initializedGa4Destinations.add(measurementId);
|
|
5829
|
+
}
|
|
5830
|
+
function loadGa4Script(measurementId) {
|
|
5831
|
+
loadScriptOnce(
|
|
5832
|
+
`bacano-ga4-${measurementId}`,
|
|
5833
|
+
`https://www.googletagmanager.com/gtag/js?id=${encodeURIComponent(measurementId)}`
|
|
5834
|
+
);
|
|
5835
|
+
}
|
|
5836
|
+
function captureGa4ClientId(measurementId, websiteId, diagnostic) {
|
|
5837
|
+
const target = marketingWindow();
|
|
5838
|
+
target?.gtag?.("get", measurementId, "client_id", (clientId) => {
|
|
5839
|
+
if (storeWebsiteMarketingGa4ClientId(websiteId, clientId)) return;
|
|
5840
|
+
diagnostic?.({
|
|
5841
|
+
level: "warning",
|
|
5842
|
+
code: "GA4_CLIENT_ID_UNAVAILABLE",
|
|
5843
|
+
message: "GA4 did not provide a valid browser client identifier"
|
|
5844
|
+
});
|
|
5845
|
+
});
|
|
5846
|
+
}
|
|
5847
|
+
function captureGa4SessionId(measurementId, websiteId, diagnostic) {
|
|
5848
|
+
const target = marketingWindow();
|
|
5849
|
+
target?.gtag?.("get", measurementId, "session_id", (sessionId) => {
|
|
5850
|
+
if (storeWebsiteMarketingGa4SessionId(websiteId, sessionId)) return;
|
|
5851
|
+
diagnostic?.({
|
|
5852
|
+
level: "warning",
|
|
5853
|
+
code: "GA4_SESSION_ID_UNAVAILABLE",
|
|
5854
|
+
message: "GA4 did not provide a valid browser session identifier"
|
|
5855
|
+
});
|
|
5856
|
+
});
|
|
5857
|
+
}
|
|
5858
|
+
function prepareMeta(pixelId) {
|
|
5859
|
+
const target = marketingWindow();
|
|
5860
|
+
if (!target) return;
|
|
5861
|
+
if (initializedMetaDestinations.has(pixelId)) return;
|
|
5862
|
+
if (!target.fbq) {
|
|
5863
|
+
const queue = [];
|
|
5864
|
+
const fbq = ((...args) => {
|
|
5865
|
+
if (fbq.callMethod) {
|
|
5866
|
+
fbq.callMethod(...args);
|
|
5867
|
+
return;
|
|
5868
|
+
}
|
|
5869
|
+
queue.push(args);
|
|
5870
|
+
});
|
|
5871
|
+
fbq.push = fbq;
|
|
5872
|
+
fbq.loaded = true;
|
|
5873
|
+
fbq.version = "2.0";
|
|
5874
|
+
fbq.queue = queue;
|
|
5875
|
+
target.fbq = fbq;
|
|
5876
|
+
target._fbq = fbq;
|
|
5877
|
+
}
|
|
5878
|
+
target.fbq("init", pixelId);
|
|
5879
|
+
initializedMetaDestinations.add(pixelId);
|
|
5880
|
+
}
|
|
5881
|
+
function loadMetaScript() {
|
|
5882
|
+
loadScriptOnce(
|
|
5883
|
+
"bacano-meta-pixel",
|
|
5884
|
+
"https://connect.facebook.net/en_US/fbevents.js"
|
|
5885
|
+
);
|
|
5886
|
+
}
|
|
5887
|
+
function scheduleScriptLoad(load, strategy) {
|
|
5888
|
+
const target = marketingWindow();
|
|
5889
|
+
if (!target || strategy !== "idle") {
|
|
5890
|
+
load();
|
|
5891
|
+
return () => void 0;
|
|
5892
|
+
}
|
|
5893
|
+
let completed = false;
|
|
5894
|
+
let idleHandle = null;
|
|
5895
|
+
let timeoutHandle = null;
|
|
5896
|
+
const interactionEvents = ["pointerdown", "keydown", "touchstart"];
|
|
5897
|
+
const cleanup = () => {
|
|
5898
|
+
for (const eventName of interactionEvents) {
|
|
5899
|
+
target.removeEventListener(eventName, finish, true);
|
|
5900
|
+
}
|
|
5901
|
+
if (idleHandle !== null) target.cancelIdleCallback?.(idleHandle);
|
|
5902
|
+
if (timeoutHandle !== null) target.clearTimeout(timeoutHandle);
|
|
5903
|
+
};
|
|
5904
|
+
const finish = () => {
|
|
5905
|
+
if (completed) return;
|
|
5906
|
+
completed = true;
|
|
5907
|
+
cleanup();
|
|
5908
|
+
load();
|
|
5909
|
+
};
|
|
5910
|
+
for (const eventName of interactionEvents) {
|
|
5911
|
+
target.addEventListener(eventName, finish, {
|
|
5912
|
+
capture: true,
|
|
5913
|
+
once: true,
|
|
5914
|
+
passive: true
|
|
5915
|
+
});
|
|
5916
|
+
}
|
|
5917
|
+
if (target.requestIdleCallback) {
|
|
5918
|
+
idleHandle = target.requestIdleCallback(finish, { timeout: 2e3 });
|
|
5919
|
+
} else {
|
|
5920
|
+
timeoutHandle = target.setTimeout(finish, 1500);
|
|
5921
|
+
}
|
|
5922
|
+
return cleanup;
|
|
5923
|
+
}
|
|
5924
|
+
function metaEventName(eventName) {
|
|
5925
|
+
const standard = {
|
|
5926
|
+
page_view: "PageView",
|
|
5927
|
+
search: "Search",
|
|
5928
|
+
view_item: "ViewContent",
|
|
5929
|
+
add_to_cart: "AddToCart",
|
|
5930
|
+
begin_checkout: "InitiateCheckout",
|
|
5931
|
+
add_payment_info: "AddPaymentInfo",
|
|
5932
|
+
contact: "Contact"
|
|
5933
|
+
};
|
|
5934
|
+
return standard[eventName] ?? eventName;
|
|
5935
|
+
}
|
|
5936
|
+
function isWebsiteMarketingContactPlacement(value) {
|
|
5937
|
+
return WEBSITE_MARKETING_CONTACT_PLACEMENTS.some(
|
|
5938
|
+
(candidate) => candidate === value
|
|
5939
|
+
);
|
|
5940
|
+
}
|
|
5941
|
+
function contactPlacementFor(event) {
|
|
5942
|
+
const placement = event.contactPlacement;
|
|
5943
|
+
if (event.eventName !== "contact" || !isWebsiteMarketingContactPlacement(placement)) {
|
|
5944
|
+
return void 0;
|
|
5945
|
+
}
|
|
5946
|
+
return placement;
|
|
5947
|
+
}
|
|
5948
|
+
function sendGa4(event, diagnosticMode) {
|
|
5949
|
+
const target = marketingWindow();
|
|
5950
|
+
const contactPlacement = contactPlacementFor(event);
|
|
5951
|
+
target?.gtag?.("event", event.eventName, {
|
|
5952
|
+
currency: event.currency,
|
|
5953
|
+
value: event.value,
|
|
5954
|
+
search_term: event.searchTerm,
|
|
5955
|
+
item_list_id: event.itemListId,
|
|
5956
|
+
item_list_name: event.itemListName,
|
|
5957
|
+
shipping_tier: event.shippingTier,
|
|
5958
|
+
payment_type: event.paymentType,
|
|
5959
|
+
method: event.contactMethod,
|
|
5960
|
+
...contactPlacement ? { contact_placement: contactPlacement } : void 0,
|
|
5961
|
+
page_location: event.pageLocation,
|
|
5962
|
+
page_title: event.pageTitle,
|
|
5963
|
+
items: mapGa4Items(event.items),
|
|
5964
|
+
event_id: event.eventId,
|
|
5965
|
+
debug_mode: diagnosticMode ? true : void 0
|
|
5966
|
+
});
|
|
5967
|
+
}
|
|
5968
|
+
function sendMeta(event) {
|
|
5969
|
+
const target = marketingWindow();
|
|
5970
|
+
const eventName = metaEventName(event.eventName);
|
|
5971
|
+
const isStandard = eventName !== event.eventName;
|
|
5972
|
+
const contactPlacement = contactPlacementFor(event);
|
|
5973
|
+
target?.fbq?.(
|
|
5974
|
+
isStandard ? "track" : "trackCustom",
|
|
5975
|
+
eventName,
|
|
5976
|
+
{
|
|
5977
|
+
currency: event.currency,
|
|
5978
|
+
value: event.value,
|
|
5979
|
+
search_string: event.searchTerm,
|
|
5980
|
+
content_ids: event.items?.map((item) => item.itemId),
|
|
5981
|
+
contents: event.items?.map((item) => ({
|
|
5982
|
+
id: item.itemId,
|
|
5983
|
+
quantity: item.quantity ?? 1,
|
|
5984
|
+
item_price: item.price
|
|
5985
|
+
})),
|
|
5986
|
+
content_type: event.items?.length ? "product" : void 0,
|
|
5987
|
+
contact_method: event.contactMethod,
|
|
5988
|
+
...contactPlacement ? { contact_placement: contactPlacement } : void 0
|
|
5989
|
+
},
|
|
5990
|
+
{ eventID: event.eventId }
|
|
5991
|
+
);
|
|
5992
|
+
}
|
|
5993
|
+
function newEventId() {
|
|
5994
|
+
return typeof crypto !== "undefined" && "randomUUID" in crypto ? crypto.randomUUID() : `evt_${Date.now()}_${Math.random().toString(16).slice(2)}`;
|
|
5995
|
+
}
|
|
5996
|
+
function createWebsiteMarketingRuntime(options) {
|
|
5997
|
+
let destroyed = false;
|
|
5998
|
+
let cancelScheduledLoad = () => void 0;
|
|
5999
|
+
const target = marketingWindow();
|
|
6000
|
+
const enabled = options.config.enabled && Boolean(
|
|
6001
|
+
target && matchesCanonicalMarketingHost(
|
|
6002
|
+
options.canonicalDomain,
|
|
6003
|
+
target.location.hostname
|
|
6004
|
+
)
|
|
6005
|
+
) && Boolean(
|
|
6006
|
+
options.config.destinations.ga4 || options.config.destinations.meta || options.config.destinations.clarity
|
|
6007
|
+
);
|
|
6008
|
+
if (enabled) {
|
|
6009
|
+
try {
|
|
6010
|
+
if (options.config.destinations.ga4) {
|
|
6011
|
+
prepareGa4(options.config.destinations.ga4.measurementId);
|
|
6012
|
+
}
|
|
6013
|
+
if (options.config.destinations.meta) {
|
|
6014
|
+
prepareMeta(options.config.destinations.meta.pixelId);
|
|
6015
|
+
}
|
|
6016
|
+
if (options.config.destinations.clarity) {
|
|
6017
|
+
prepareClarity(options.config.destinations.clarity.projectId);
|
|
6018
|
+
}
|
|
6019
|
+
cancelScheduledLoad = scheduleScriptLoad(() => {
|
|
6020
|
+
if (destroyed) return;
|
|
6021
|
+
if (options.config.destinations.ga4) {
|
|
6022
|
+
loadGa4Script(options.config.destinations.ga4.measurementId);
|
|
6023
|
+
}
|
|
6024
|
+
if (options.config.destinations.meta) loadMetaScript();
|
|
6025
|
+
if (options.config.destinations.clarity) {
|
|
6026
|
+
loadClarityScript(options.config.destinations.clarity.projectId);
|
|
6027
|
+
}
|
|
6028
|
+
}, options.scriptLoadStrategy);
|
|
6029
|
+
captureWebsiteMarketingAttribution(
|
|
6030
|
+
options.websiteId,
|
|
6031
|
+
options.config.attributionWindowDays
|
|
6032
|
+
);
|
|
6033
|
+
if (options.config.destinations.ga4) {
|
|
6034
|
+
captureGa4ClientId(
|
|
6035
|
+
options.config.destinations.ga4.measurementId,
|
|
6036
|
+
options.websiteId,
|
|
6037
|
+
options.diagnostic
|
|
6038
|
+
);
|
|
6039
|
+
captureGa4SessionId(
|
|
6040
|
+
options.config.destinations.ga4.measurementId,
|
|
6041
|
+
options.websiteId,
|
|
6042
|
+
options.diagnostic
|
|
6043
|
+
);
|
|
6044
|
+
}
|
|
6045
|
+
if (options.config.destinations.meta) {
|
|
6046
|
+
refreshWebsiteMarketingMetaContext(options.websiteId);
|
|
6047
|
+
}
|
|
6048
|
+
} catch (error) {
|
|
6049
|
+
options.diagnostic?.({
|
|
6050
|
+
level: "warning",
|
|
6051
|
+
code: "RUNTIME_INITIALIZATION_FAILED",
|
|
6052
|
+
message: error instanceof Error ? error.message : "Initialization failed"
|
|
6053
|
+
});
|
|
6054
|
+
}
|
|
6055
|
+
}
|
|
6056
|
+
return {
|
|
6057
|
+
enabled,
|
|
6058
|
+
track(input) {
|
|
6059
|
+
if (!enabled || destroyed) return;
|
|
6060
|
+
const event = {
|
|
6061
|
+
...input,
|
|
6062
|
+
schemaVersion: 1,
|
|
6063
|
+
eventId: input.eventId ?? newEventId(),
|
|
6064
|
+
occurredAt: input.occurredAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
6065
|
+
currency: input.currency ?? options.config.currencyCode,
|
|
6066
|
+
pageLocation: sanitizeMarketingUrl(
|
|
6067
|
+
input.pageLocation ?? (typeof window === "undefined" ? "" : window.location.href)
|
|
6068
|
+
)
|
|
6069
|
+
};
|
|
6070
|
+
try {
|
|
6071
|
+
if (options.config.destinations.ga4) {
|
|
6072
|
+
sendGa4(event, options.config.destinations.ga4.diagnosticMode);
|
|
6073
|
+
}
|
|
6074
|
+
if (options.config.destinations.meta) sendMeta(event);
|
|
6075
|
+
if (options.config.destinations.clarity) {
|
|
6076
|
+
sendClarityEvent(event.eventName);
|
|
6077
|
+
}
|
|
6078
|
+
options.diagnostic?.({
|
|
6079
|
+
level: "debug",
|
|
6080
|
+
code: "EVENT_DISPATCHED",
|
|
6081
|
+
message: "Marketing event dispatched",
|
|
6082
|
+
eventName: event.eventName
|
|
6083
|
+
});
|
|
6084
|
+
} catch (error) {
|
|
6085
|
+
options.diagnostic?.({
|
|
6086
|
+
level: "warning",
|
|
6087
|
+
code: "EVENT_DISPATCH_FAILED",
|
|
6088
|
+
message: error instanceof Error ? error.message : "Dispatch failed",
|
|
6089
|
+
eventName: event.eventName
|
|
6090
|
+
});
|
|
6091
|
+
}
|
|
6092
|
+
},
|
|
6093
|
+
captureAttribution() {
|
|
6094
|
+
if (!enabled) return null;
|
|
6095
|
+
captureWebsiteMarketingAttribution(
|
|
6096
|
+
options.websiteId,
|
|
6097
|
+
options.config.attributionWindowDays
|
|
6098
|
+
);
|
|
6099
|
+
if (options.config.destinations.meta) {
|
|
6100
|
+
refreshWebsiteMarketingMetaContext(options.websiteId);
|
|
6101
|
+
}
|
|
6102
|
+
return getWebsiteMarketingAttribution(options.websiteId);
|
|
6103
|
+
},
|
|
6104
|
+
getAttribution() {
|
|
6105
|
+
if (!enabled) return null;
|
|
6106
|
+
if (options.config.destinations.meta) {
|
|
6107
|
+
refreshWebsiteMarketingMetaContext(options.websiteId);
|
|
6108
|
+
}
|
|
6109
|
+
return getWebsiteMarketingAttribution(options.websiteId);
|
|
6110
|
+
},
|
|
6111
|
+
destroy() {
|
|
6112
|
+
destroyed = true;
|
|
6113
|
+
cancelScheduledLoad();
|
|
6114
|
+
}
|
|
6115
|
+
};
|
|
6116
|
+
}
|
|
6117
|
+
|
|
6118
|
+
// libs/sdk/src/modules/marketing.ts
|
|
6119
|
+
function readRetryAfter(response) {
|
|
6120
|
+
const value = Number(response.headers.get("Retry-After") ?? "1");
|
|
6121
|
+
return Number.isFinite(value) && value >= 0 ? Math.ceil(value) : 1;
|
|
6122
|
+
}
|
|
6123
|
+
function createMarketingModule(apiUrl, websiteSlug, getContext) {
|
|
6124
|
+
return {
|
|
6125
|
+
async captureLead(input) {
|
|
6126
|
+
const context = getContext();
|
|
6127
|
+
const attribution = getWebsiteMarketingAttribution(context.websiteId);
|
|
6128
|
+
let response;
|
|
6129
|
+
try {
|
|
6130
|
+
response = await fetch(
|
|
6131
|
+
`${apiUrl.replace(/\/+$/, "")}/api/v1/website/campaign-captures`,
|
|
6132
|
+
{
|
|
6133
|
+
method: "POST",
|
|
6134
|
+
headers: { "Content-Type": "application/json" },
|
|
6135
|
+
body: JSON.stringify({
|
|
6136
|
+
websiteSlug,
|
|
6137
|
+
campaignKey: input.campaignKey,
|
|
6138
|
+
idempotencyKey: input.idempotencyKey,
|
|
6139
|
+
source: input.source ?? "WEBSITE",
|
|
6140
|
+
commercialConsent: input.commercialConsent,
|
|
6141
|
+
contact: input.contact,
|
|
6142
|
+
answers: input.answers ?? {},
|
|
6143
|
+
attribution: attribution ? serializeWebsiteMarketingAttribution(attribution) : null
|
|
6144
|
+
})
|
|
6145
|
+
}
|
|
6146
|
+
);
|
|
6147
|
+
} catch (error) {
|
|
6148
|
+
throw new WebsiteError(
|
|
6149
|
+
`Could not capture the campaign lead: ${error instanceof Error ? error.message : String(error)}`,
|
|
6150
|
+
"NETWORK_ERROR"
|
|
6151
|
+
);
|
|
6152
|
+
}
|
|
6153
|
+
if (response.status === 429) {
|
|
6154
|
+
throw new RateLimitError(
|
|
6155
|
+
"Rate limit exceeded. Please try again later.",
|
|
6156
|
+
readRetryAfter(response)
|
|
6157
|
+
);
|
|
6158
|
+
}
|
|
6159
|
+
if (!response.ok) {
|
|
6160
|
+
throw new WebsiteError(
|
|
6161
|
+
`Could not capture the campaign lead (status ${response.status})`,
|
|
6162
|
+
response.status === 403 ? "ORIGIN_NOT_ALLOWED" : "INVALID_RESPONSE",
|
|
6163
|
+
{ status: response.status }
|
|
6164
|
+
);
|
|
6165
|
+
}
|
|
6166
|
+
const result = await response.json();
|
|
6167
|
+
if (typeof result.accepted !== "boolean" || typeof result.message !== "string") {
|
|
6168
|
+
throw new WebsiteError(
|
|
6169
|
+
"Campaign capture returned an invalid response",
|
|
6170
|
+
"INVALID_RESPONSE"
|
|
6171
|
+
);
|
|
6172
|
+
}
|
|
6173
|
+
return { accepted: result.accepted, message: result.message };
|
|
6174
|
+
}
|
|
6175
|
+
};
|
|
6176
|
+
}
|
|
6177
|
+
|
|
4451
6178
|
// libs/sdk/src/modules/orders.ts
|
|
4452
6179
|
function mapOrderItem(raw) {
|
|
4453
6180
|
return {
|
|
@@ -4622,13 +6349,13 @@ function createOrdersModule(graphqlUrl, getHeaders, getContext) {
|
|
|
4622
6349
|
)) {
|
|
4623
6350
|
return order;
|
|
4624
6351
|
}
|
|
4625
|
-
await
|
|
6352
|
+
await wait2(intervalMs);
|
|
4626
6353
|
}
|
|
4627
6354
|
throw new CheckoutError(["PAYMENT_STATUS_TIMEOUT"]);
|
|
4628
6355
|
}
|
|
4629
6356
|
};
|
|
4630
6357
|
}
|
|
4631
|
-
function
|
|
6358
|
+
function wait2(ms) {
|
|
4632
6359
|
return new Promise((resolve) => {
|
|
4633
6360
|
setTimeout(resolve, ms);
|
|
4634
6361
|
});
|
|
@@ -4787,6 +6514,9 @@ function createProfileModule(graphqlUrl, getHeaders) {
|
|
|
4787
6514
|
}
|
|
4788
6515
|
|
|
4789
6516
|
// libs/sdk/src/modules/website.ts
|
|
6517
|
+
var WEBSITE_CONTEXT_CACHE_TTL_MS = 5 * 60 * 1e3;
|
|
6518
|
+
var WEBSITE_CONTEXT_CACHE_MAX_ENTRIES = 100;
|
|
6519
|
+
var websiteContextCache = /* @__PURE__ */ new Map();
|
|
4790
6520
|
function mapWebsite(raw) {
|
|
4791
6521
|
return {
|
|
4792
6522
|
websiteId: raw.id,
|
|
@@ -4800,10 +6530,7 @@ function mapWebsite(raw) {
|
|
|
4800
6530
|
}
|
|
4801
6531
|
async function resolveWebsite(graphqlUrl, slug, headers) {
|
|
4802
6532
|
if (!slug.trim()) {
|
|
4803
|
-
throw new WebsiteError(
|
|
4804
|
-
"websiteSlug is required",
|
|
4805
|
-
"INVALID_CONFIG"
|
|
4806
|
-
);
|
|
6533
|
+
throw new WebsiteError("websiteSlug is required", "INVALID_CONFIG");
|
|
4807
6534
|
}
|
|
4808
6535
|
const data = await executeGraphQL(
|
|
4809
6536
|
graphqlUrl,
|
|
@@ -4819,6 +6546,31 @@ async function resolveWebsite(graphqlUrl, slug, headers) {
|
|
|
4819
6546
|
}
|
|
4820
6547
|
return mapWebsite(data.websites[0]);
|
|
4821
6548
|
}
|
|
6549
|
+
function resolveWebsiteCached(graphqlUrl, slug, headers) {
|
|
6550
|
+
const normalizedSlug = slug.trim();
|
|
6551
|
+
const cacheKey = `${graphqlUrl}::${normalizedSlug}`;
|
|
6552
|
+
const now = Date.now();
|
|
6553
|
+
const cached = websiteContextCache.get(cacheKey);
|
|
6554
|
+
if (cached && cached.expiresAt > now) return cached.request;
|
|
6555
|
+
if (cached) websiteContextCache.delete(cacheKey);
|
|
6556
|
+
const request = resolveWebsite(graphqlUrl, normalizedSlug, headers).catch(
|
|
6557
|
+
(error) => {
|
|
6558
|
+
if (websiteContextCache.get(cacheKey)?.request === request) {
|
|
6559
|
+
websiteContextCache.delete(cacheKey);
|
|
6560
|
+
}
|
|
6561
|
+
throw error;
|
|
6562
|
+
}
|
|
6563
|
+
);
|
|
6564
|
+
if (websiteContextCache.size >= WEBSITE_CONTEXT_CACHE_MAX_ENTRIES) {
|
|
6565
|
+
const oldestKey = websiteContextCache.keys().next().value;
|
|
6566
|
+
if (oldestKey) websiteContextCache.delete(oldestKey);
|
|
6567
|
+
}
|
|
6568
|
+
websiteContextCache.set(cacheKey, {
|
|
6569
|
+
expiresAt: now + WEBSITE_CONTEXT_CACHE_TTL_MS,
|
|
6570
|
+
request
|
|
6571
|
+
});
|
|
6572
|
+
return request;
|
|
6573
|
+
}
|
|
4822
6574
|
|
|
4823
6575
|
// libs/sdk/src/client.ts
|
|
4824
6576
|
function normalizeApiUrl(apiUrl) {
|
|
@@ -4829,10 +6581,17 @@ function createBacanoClient(config) {
|
|
|
4829
6581
|
throw new WebsiteError("apiUrl is required", "INVALID_CONFIG");
|
|
4830
6582
|
if (!config.websiteSlug?.trim())
|
|
4831
6583
|
throw new WebsiteError("websiteSlug is required", "INVALID_CONFIG");
|
|
6584
|
+
if (config.initialContext && config.initialContext.slug.trim() !== config.websiteSlug.trim()) {
|
|
6585
|
+
throw new WebsiteError(
|
|
6586
|
+
"initialContext slug must match websiteSlug",
|
|
6587
|
+
"INVALID_CONFIG"
|
|
6588
|
+
);
|
|
6589
|
+
}
|
|
4832
6590
|
const apiUrl = normalizeApiUrl(config.apiUrl);
|
|
4833
6591
|
const graphqlUrl = `${apiUrl}/api/v1/website/graphql`;
|
|
4834
6592
|
const storageUrl = `${apiUrl}/storage/v1`;
|
|
4835
|
-
let context = null;
|
|
6593
|
+
let context = config.initialContext ? { ...config.initialContext } : null;
|
|
6594
|
+
let initialization = null;
|
|
4836
6595
|
function getContext() {
|
|
4837
6596
|
if (!context) throw new NotInitializedError();
|
|
4838
6597
|
return context;
|
|
@@ -4877,13 +6636,27 @@ function createBacanoClient(config) {
|
|
|
4877
6636
|
getContext,
|
|
4878
6637
|
storageUrl
|
|
4879
6638
|
);
|
|
6639
|
+
const marketingModule = createMarketingModule(
|
|
6640
|
+
apiUrl,
|
|
6641
|
+
config.websiteSlug,
|
|
6642
|
+
getContext
|
|
6643
|
+
);
|
|
4880
6644
|
return {
|
|
4881
6645
|
async init() {
|
|
4882
|
-
context
|
|
4883
|
-
|
|
4884
|
-
|
|
4885
|
-
|
|
4886
|
-
|
|
6646
|
+
if (context) return;
|
|
6647
|
+
if (!initialization) {
|
|
6648
|
+
initialization = resolveWebsiteCached(
|
|
6649
|
+
graphqlUrl,
|
|
6650
|
+
config.websiteSlug,
|
|
6651
|
+
getWebsiteHeaders
|
|
6652
|
+
).then((resolvedContext) => {
|
|
6653
|
+
context = resolvedContext;
|
|
6654
|
+
}).catch((error) => {
|
|
6655
|
+
initialization = null;
|
|
6656
|
+
throw error;
|
|
6657
|
+
});
|
|
6658
|
+
}
|
|
6659
|
+
await initialization;
|
|
4887
6660
|
},
|
|
4888
6661
|
getContext,
|
|
4889
6662
|
auth: {
|
|
@@ -4898,8 +6671,11 @@ function createBacanoClient(config) {
|
|
|
4898
6671
|
},
|
|
4899
6672
|
catalog: {
|
|
4900
6673
|
getCatalogPage: catalogModule.getCatalogPage.bind(catalogModule),
|
|
6674
|
+
getStaticCatalogSnapshot: catalogModule.getStaticCatalogSnapshot.bind(catalogModule),
|
|
6675
|
+
getStaticCatalogBootstrap: catalogModule.getStaticCatalogBootstrap.bind(catalogModule),
|
|
4901
6676
|
getProductSeoEntries: catalogModule.getProductSeoEntries.bind(catalogModule),
|
|
4902
6677
|
getProductRecommendations: catalogModule.getProductRecommendations.bind(catalogModule),
|
|
6678
|
+
getProductLiveState: catalogModule.getProductLiveState.bind(catalogModule),
|
|
4903
6679
|
getProducts: catalogModule.getProducts.bind(catalogModule),
|
|
4904
6680
|
getProduct: catalogModule.getProduct.bind(catalogModule),
|
|
4905
6681
|
getProductBySlug: catalogModule.getProductBySlug.bind(catalogModule),
|
|
@@ -4924,7 +6700,8 @@ function createBacanoClient(config) {
|
|
|
4924
6700
|
setItem: cartModule.setItem.bind(cartModule),
|
|
4925
6701
|
addItem: cartModule.addItem.bind(cartModule),
|
|
4926
6702
|
updateItem: cartModule.updateItem.bind(cartModule),
|
|
4927
|
-
removeItem: cartModule.removeItem.bind(cartModule)
|
|
6703
|
+
removeItem: cartModule.removeItem.bind(cartModule),
|
|
6704
|
+
clear: cartModule.clear.bind(cartModule)
|
|
4928
6705
|
},
|
|
4929
6706
|
checkout: {
|
|
4930
6707
|
getLocationOptions: checkoutModule.getLocationOptions.bind(checkoutModule),
|
|
@@ -4951,6 +6728,9 @@ function createBacanoClient(config) {
|
|
|
4951
6728
|
get: profileModule.get.bind(profileModule),
|
|
4952
6729
|
getAddresses: profileModule.getAddresses.bind(profileModule),
|
|
4953
6730
|
update: profileModule.update.bind(profileModule)
|
|
6731
|
+
},
|
|
6732
|
+
marketing: {
|
|
6733
|
+
captureLead: marketingModule.captureLead.bind(marketingModule)
|
|
4954
6734
|
}
|
|
4955
6735
|
};
|
|
4956
6736
|
}
|