hydrogen-sfdgspsdmq-test1 0.0.1-security → 2024.1.9

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of hydrogen-sfdgspsdmq-test1 might be problematic. Click here for more details.

@@ -0,0 +1,3491 @@
1
+ import { createStorefrontClient as createStorefrontClient$1, SHOPIFY_STOREFRONT_ID_HEADER, getShopifyCookies, SHOPIFY_Y, SHOPIFY_STOREFRONT_Y_HEADER, SHOPIFY_S, SHOPIFY_STOREFRONT_S_HEADER, flattenConnection, ShopPayButton as ShopPayButton$1 } from '@shopify/hydrogen-react';
2
+ export { AnalyticsEventName, AnalyticsPageType, ExternalVideo, IMAGE_FRAGMENT, Image, MediaFile, ModelViewer, Money, ShopifySalesChannel, Video, customerAccountApiCustomScalars, flattenConnection, getClientBrowserParameters, getShopifyCookies, parseGid, parseMetafield, sendShopifyAnalytics, storefrontApiCustomScalars, useLoadScript, useMoney, useShopifyCookies } from '@shopify/hydrogen-react';
3
+ import { lazy, createContext, forwardRef, useMemo, createElement, Suspense, Fragment, useRef, useEffect, useContext } from 'react';
4
+ import { useMatches, useLocation, useNavigation, Link, useNavigate, useFetcher, useFetchers } from '@remix-run/react';
5
+ import { jsx, jsxs, Fragment as Fragment$1 } from 'react/jsx-runtime';
6
+ import cspBuilder from 'content-security-policy-builder';
7
+
8
+ // src/storefront.ts
9
+
10
+ // src/utils/hash.ts
11
+ function hashKey(queryKey) {
12
+ const rawKeys = Array.isArray(queryKey) ? queryKey : [queryKey];
13
+ let hash = "";
14
+ for (const key of rawKeys) {
15
+ if (key != null) {
16
+ if (typeof key === "object") {
17
+ hash += JSON.stringify(key);
18
+ } else {
19
+ hash += key.toString();
20
+ }
21
+ }
22
+ }
23
+ return encodeURIComponent(hash);
24
+ }
25
+
26
+ // src/cache/strategies.ts
27
+ var PUBLIC = "public";
28
+ var PRIVATE = "private";
29
+ var NO_STORE = "no-store";
30
+ var optionMapping = {
31
+ maxAge: "max-age",
32
+ staleWhileRevalidate: "stale-while-revalidate",
33
+ sMaxAge: "s-maxage",
34
+ staleIfError: "stale-if-error"
35
+ };
36
+ function generateCacheControlHeader(cacheOptions) {
37
+ const cacheControl = [];
38
+ Object.keys(cacheOptions).forEach((key) => {
39
+ if (key === "mode") {
40
+ cacheControl.push(cacheOptions[key]);
41
+ } else if (optionMapping[key]) {
42
+ cacheControl.push(
43
+ `${optionMapping[key]}=${cacheOptions[key]}`
44
+ );
45
+ }
46
+ });
47
+ return cacheControl.join(", ");
48
+ }
49
+ function CacheNone() {
50
+ return {
51
+ mode: NO_STORE
52
+ };
53
+ }
54
+ function guardExpirableModeType(overrideOptions) {
55
+ if (overrideOptions?.mode && overrideOptions?.mode !== PUBLIC && overrideOptions?.mode !== PRIVATE) {
56
+ throw Error("'mode' must be either 'public' or 'private'");
57
+ }
58
+ }
59
+ function CacheShort(overrideOptions) {
60
+ guardExpirableModeType(overrideOptions);
61
+ return {
62
+ mode: PUBLIC,
63
+ maxAge: 1,
64
+ staleWhileRevalidate: 9,
65
+ ...overrideOptions
66
+ };
67
+ }
68
+ function CacheLong(overrideOptions) {
69
+ guardExpirableModeType(overrideOptions);
70
+ return {
71
+ mode: PUBLIC,
72
+ maxAge: 3600,
73
+ // 1 hour
74
+ staleWhileRevalidate: 82800,
75
+ // 23 Hours
76
+ ...overrideOptions
77
+ };
78
+ }
79
+ function CacheDefault(overrideOptions) {
80
+ guardExpirableModeType(overrideOptions);
81
+ return {
82
+ mode: PUBLIC,
83
+ maxAge: 1,
84
+ staleWhileRevalidate: 86399,
85
+ // 1 second less than 24 hours
86
+ ...overrideOptions
87
+ };
88
+ }
89
+ function CacheCustom(overrideOptions) {
90
+ return overrideOptions;
91
+ }
92
+
93
+ // src/utils/parse-json.ts
94
+ function parseJSON(json) {
95
+ if (String(json).includes("__proto__"))
96
+ return JSON.parse(json, noproto);
97
+ return JSON.parse(json);
98
+ }
99
+ function noproto(k, v) {
100
+ if (k !== "__proto__")
101
+ return v;
102
+ }
103
+ function getCacheControlSetting(userCacheOptions, options) {
104
+ if (userCacheOptions && options) {
105
+ return {
106
+ ...userCacheOptions,
107
+ ...options
108
+ };
109
+ } else {
110
+ return userCacheOptions || CacheDefault();
111
+ }
112
+ }
113
+ function generateDefaultCacheControlHeader(userCacheOptions) {
114
+ return generateCacheControlHeader(getCacheControlSetting(userCacheOptions));
115
+ }
116
+ async function getItem(cache, request) {
117
+ if (!cache)
118
+ return;
119
+ const response = await cache.match(request);
120
+ if (!response) {
121
+ return;
122
+ }
123
+ return response;
124
+ }
125
+ async function setItem(cache, request, response, userCacheOptions) {
126
+ if (!cache)
127
+ return;
128
+ const cacheControl = getCacheControlSetting(userCacheOptions);
129
+ const paddedCacheControlString = generateDefaultCacheControlHeader(
130
+ getCacheControlSetting(cacheControl, {
131
+ maxAge: (cacheControl.maxAge || 0) + (cacheControl.staleWhileRevalidate || 0)
132
+ })
133
+ );
134
+ const cacheControlString = generateDefaultCacheControlHeader(
135
+ getCacheControlSetting(cacheControl)
136
+ );
137
+ response.headers.set("cache-control", paddedCacheControlString);
138
+ response.headers.set("real-cache-control", cacheControlString);
139
+ response.headers.set("cache-put-date", (/* @__PURE__ */ new Date()).toUTCString());
140
+ await cache.put(request, response);
141
+ }
142
+ async function deleteItem(cache, request) {
143
+ if (!cache)
144
+ return;
145
+ await cache.delete(request);
146
+ }
147
+ function calculateAge(response, responseDate) {
148
+ const cacheControl = response.headers.get("real-cache-control");
149
+ let responseMaxAge = 0;
150
+ if (cacheControl) {
151
+ const maxAgeMatch = cacheControl.match(/max-age=(\d*)/);
152
+ if (maxAgeMatch && maxAgeMatch.length > 1) {
153
+ responseMaxAge = parseFloat(maxAgeMatch[1]);
154
+ }
155
+ }
156
+ const ageInMs = (/* @__PURE__ */ new Date()).valueOf() - new Date(responseDate).valueOf();
157
+ return [ageInMs / 1e3, responseMaxAge];
158
+ }
159
+ function isStale(request, response) {
160
+ const responseDate = response.headers.get("cache-put-date");
161
+ if (!responseDate) {
162
+ return false;
163
+ }
164
+ const [age, responseMaxAge] = calculateAge(response, responseDate);
165
+ const result = age > responseMaxAge;
166
+ return result;
167
+ }
168
+ var CacheAPI = {
169
+ get: getItem,
170
+ set: setItem,
171
+ delete: deleteItem,
172
+ generateDefaultCacheControlHeader,
173
+ isStale
174
+ };
175
+
176
+ // src/cache/sub-request.ts
177
+ function getKeyUrl(key) {
178
+ return `https://shopify.dev/?${key}`;
179
+ }
180
+ function getCacheOption(userCacheOptions) {
181
+ return userCacheOptions || CacheDefault();
182
+ }
183
+ async function getItemFromCache(cache, key) {
184
+ if (!cache)
185
+ return;
186
+ const url = getKeyUrl(key);
187
+ const request = new Request(url);
188
+ const response = await CacheAPI.get(cache, request);
189
+ if (!response) {
190
+ return;
191
+ }
192
+ const text = await response.text();
193
+ try {
194
+ return [parseJSON(text), response];
195
+ } catch {
196
+ return [text, response];
197
+ }
198
+ }
199
+ async function setItemInCache(cache, key, value, userCacheOptions) {
200
+ if (!cache)
201
+ return;
202
+ const url = getKeyUrl(key);
203
+ const request = new Request(url);
204
+ const response = new Response(JSON.stringify(value));
205
+ await CacheAPI.set(
206
+ cache,
207
+ request,
208
+ response,
209
+ getCacheOption(userCacheOptions)
210
+ );
211
+ }
212
+ function isStale2(key, response) {
213
+ return CacheAPI.isStale(new Request(getKeyUrl(key)), response);
214
+ }
215
+
216
+ // src/cache/fetch.ts
217
+ function toSerializableResponse(body, response) {
218
+ return [
219
+ body,
220
+ {
221
+ status: response.status,
222
+ statusText: response.statusText,
223
+ headers: Array.from(response.headers.entries())
224
+ }
225
+ ];
226
+ }
227
+ function fromSerializableResponse([body, init]) {
228
+ return [body, new Response(body, init)];
229
+ }
230
+ var checkGraphQLErrors = (body, response) => !body?.errors && response.status < 400;
231
+ var swrLock = /* @__PURE__ */ new Set();
232
+ async function runWithCache(cacheKey, actionFn, {
233
+ strategy = CacheShort(),
234
+ cacheInstance,
235
+ shouldCacheResult = () => true,
236
+ waitUntil,
237
+ debugInfo
238
+ }) {
239
+ const startTime = Date.now();
240
+ const key = hashKey([
241
+ // '__HYDROGEN_CACHE_ID__', // TODO purgeQueryCacheOnBuild
242
+ ...typeof cacheKey === "string" ? [cacheKey] : cacheKey
243
+ ]);
244
+ let debugData;
245
+ const addDebugData = (info) => {
246
+ debugData = {
247
+ displayName: info.displayName,
248
+ url: info.response?.url,
249
+ responseInit: {
250
+ status: info.response?.status || 0,
251
+ statusText: info.response?.statusText || "",
252
+ headers: Array.from(info.response?.headers.entries() || [])
253
+ }
254
+ };
255
+ };
256
+ const logSubRequestEvent2 = ({
257
+ result: result2,
258
+ cacheStatus,
259
+ overrideStartTime
260
+ }) => {
261
+ globalThis.__H2O_LOG_EVENT?.({
262
+ eventType: "subrequest",
263
+ startTime: overrideStartTime || startTime,
264
+ cacheStatus,
265
+ responsePayload: result2 && result2[0] || result2,
266
+ responseInit: result2 && result2[1] || debugData?.responseInit,
267
+ cache: {
268
+ status: cacheStatus,
269
+ strategy: generateCacheControlHeader(strategy || {}),
270
+ key
271
+ },
272
+ waitUntil,
273
+ ...debugInfo,
274
+ url: debugData?.url || debugInfo?.url || getKeyUrl(key),
275
+ displayName: debugInfo?.displayName || debugData?.displayName
276
+ });
277
+ } ;
278
+ if (!cacheInstance || !strategy || strategy.mode === NO_STORE) {
279
+ const result2 = await actionFn({ addDebugData });
280
+ logSubRequestEvent2?.({ result: result2 });
281
+ return result2;
282
+ }
283
+ const cachedItem = await getItemFromCache(cacheInstance, key);
284
+ if (cachedItem) {
285
+ const [cachedResult, cacheInfo] = cachedItem;
286
+ const cacheStatus = isStale2(key, cacheInfo) ? "STALE" : "HIT";
287
+ if (!swrLock.has(key) && cacheStatus === "STALE") {
288
+ swrLock.add(key);
289
+ const revalidatingPromise = Promise.resolve().then(async () => {
290
+ const revalidateStartTime = Date.now();
291
+ try {
292
+ const result2 = await actionFn({ addDebugData });
293
+ if (shouldCacheResult(result2)) {
294
+ await setItemInCache(cacheInstance, key, result2, strategy);
295
+ logSubRequestEvent2?.({
296
+ result: result2,
297
+ cacheStatus: "PUT",
298
+ overrideStartTime: revalidateStartTime
299
+ });
300
+ }
301
+ } catch (error) {
302
+ if (error.message) {
303
+ error.message = "SWR in sub-request failed: " + error.message;
304
+ }
305
+ console.error(error);
306
+ } finally {
307
+ swrLock.delete(key);
308
+ }
309
+ });
310
+ waitUntil?.(revalidatingPromise);
311
+ }
312
+ logSubRequestEvent2?.({
313
+ result: cachedResult,
314
+ cacheStatus
315
+ });
316
+ return cachedResult;
317
+ }
318
+ const result = await actionFn({ addDebugData });
319
+ logSubRequestEvent2?.({
320
+ result,
321
+ cacheStatus: "MISS"
322
+ });
323
+ if (shouldCacheResult(result)) {
324
+ const setItemInCachePromise = Promise.resolve().then(async () => {
325
+ const putStartTime = Date.now();
326
+ await setItemInCache(cacheInstance, key, result, strategy);
327
+ logSubRequestEvent2?.({
328
+ result,
329
+ cacheStatus: "PUT",
330
+ overrideStartTime: putStartTime
331
+ });
332
+ });
333
+ waitUntil?.(setItemInCachePromise);
334
+ }
335
+ return result;
336
+ }
337
+ async function fetchWithServerCache(url, requestInit, {
338
+ cacheInstance,
339
+ cache: cacheOptions,
340
+ cacheKey = [url, requestInit],
341
+ shouldCacheResponse = () => true,
342
+ waitUntil,
343
+ returnType = "json",
344
+ debugInfo
345
+ } = {}) {
346
+ if (!cacheOptions && (!requestInit.method || requestInit.method === "GET")) {
347
+ cacheOptions = CacheShort();
348
+ }
349
+ return runWithCache(
350
+ cacheKey,
351
+ async () => {
352
+ const response = await fetch(url, requestInit);
353
+ let data;
354
+ try {
355
+ data = await response[returnType]();
356
+ } catch {
357
+ try {
358
+ data = await response.text();
359
+ } catch {
360
+ return toSerializableResponse("", response);
361
+ }
362
+ }
363
+ return toSerializableResponse(data, response);
364
+ },
365
+ {
366
+ cacheInstance,
367
+ waitUntil,
368
+ strategy: cacheOptions ?? null,
369
+ debugInfo,
370
+ shouldCacheResult: (result) => shouldCacheResponse(...fromSerializableResponse(result))
371
+ }
372
+ ).then(fromSerializableResponse);
373
+ }
374
+
375
+ // src/constants.ts
376
+ var STOREFRONT_REQUEST_GROUP_ID_HEADER = "Custom-Storefront-Request-Group-ID";
377
+ var STOREFRONT_ACCESS_TOKEN_HEADER = "X-Shopify-Storefront-Access-Token";
378
+ var SDK_VARIANT_HEADER = "X-SDK-Variant";
379
+ var SDK_VARIANT_SOURCE_HEADER = "X-SDK-Variant-Source";
380
+ var SDK_VERSION_HEADER = "X-SDK-Version";
381
+
382
+ // src/utils/uuid.ts
383
+ function generateUUID() {
384
+ if (typeof crypto !== "undefined" && !!crypto.randomUUID) {
385
+ return crypto.randomUUID();
386
+ } else {
387
+ return `weak-${Math.random().toString(16).substring(2)}`;
388
+ }
389
+ }
390
+
391
+ // src/utils/warning.ts
392
+ var warnings = /* @__PURE__ */ new Set();
393
+ var warnOnce = (string) => {
394
+ if (!warnings.has(string)) {
395
+ console.warn(string);
396
+ warnings.add(string);
397
+ }
398
+ };
399
+
400
+ // src/version.ts
401
+ var LIB_VERSION = "2024.1.6";
402
+
403
+ // src/utils/graphql.ts
404
+ function minifyQuery(string) {
405
+ return string.replace(/\s*#.*$/gm, "").replace(/\s+/gm, " ").trim();
406
+ }
407
+ var IS_QUERY_RE = /(^|}\s)query[\s({]/im;
408
+ var IS_MUTATION_RE = /(^|}\s)mutation[\s({]/im;
409
+ function assertQuery(query, callerName) {
410
+ if (!IS_QUERY_RE.test(query)) {
411
+ throw new Error(`[h2:error:${callerName}] Can only execute queries`);
412
+ }
413
+ }
414
+ function assertMutation(query, callerName) {
415
+ if (!IS_MUTATION_RE.test(query)) {
416
+ throw new Error(`[h2:error:${callerName}] Can only execute mutations`);
417
+ }
418
+ }
419
+ var GraphQLError = class extends Error {
420
+ /**
421
+ * If an error can be associated to a particular point in the requested
422
+ * GraphQL document, it should contain a list of locations.
423
+ */
424
+ locations;
425
+ /**
426
+ * If an error can be associated to a particular field in the GraphQL result,
427
+ * it _must_ contain an entry with the key `path` that details the path of
428
+ * the response field which experienced the error. This allows clients to
429
+ * identify whether a null result is intentional or caused by a runtime error.
430
+ */
431
+ path;
432
+ /**
433
+ * Reserved for implementors to extend the protocol however they see fit,
434
+ * and hence there are no additional restrictions on its contents.
435
+ */
436
+ extensions;
437
+ constructor(message, options = {}) {
438
+ const h2Prefix = options.clientOperation ? `[h2:error:${options.clientOperation}] ` : "";
439
+ const enhancedMessage = h2Prefix + message + (options.requestId ? ` - Request ID: ${options.requestId}` : "");
440
+ super(enhancedMessage);
441
+ this.name = "GraphQLError";
442
+ this.extensions = options.extensions;
443
+ this.locations = options.locations;
444
+ this.path = options.path;
445
+ this.stack = options.stack || void 0;
446
+ try {
447
+ this.cause = JSON.stringify({
448
+ ...typeof options.cause === "object" ? options.cause : {},
449
+ requestId: options.requestId,
450
+ ...{
451
+ path: options.path,
452
+ extensions: options.extensions,
453
+ graphql: h2Prefix && options.query && {
454
+ query: options.query,
455
+ variables: JSON.stringify(options.queryVariables)
456
+ }
457
+ }
458
+ });
459
+ } catch {
460
+ if (options.cause)
461
+ this.cause = options.cause;
462
+ }
463
+ }
464
+ get [Symbol.toStringTag]() {
465
+ return this.name;
466
+ }
467
+ /**
468
+ * Note: `toString()` is internally used by `console.log(...)` / `console.error(...)`
469
+ * when ingesting logs in Oxygen production. Therefore, we want to make sure that
470
+ * the error message is as informative as possible instead of `[object Object]`.
471
+ */
472
+ toString() {
473
+ let result = `${this.name}: ${this.message}`;
474
+ if (this.path) {
475
+ try {
476
+ result += ` | path: ${JSON.stringify(this.path)}`;
477
+ } catch {
478
+ }
479
+ }
480
+ if (this.extensions) {
481
+ try {
482
+ result += ` | extensions: ${JSON.stringify(this.extensions)}`;
483
+ } catch {
484
+ }
485
+ }
486
+ result += "\n";
487
+ if (this.stack) {
488
+ result += `${this.stack.slice(this.stack.indexOf("\n") + 1)}
489
+ `;
490
+ }
491
+ return result;
492
+ }
493
+ /**
494
+ * Note: toJSON` is internally used by `JSON.stringify(...)`.
495
+ * The most common scenario when this error instance is going to be stringified is
496
+ * when it's passed to Remix' `json` and `defer` functions: e.g. `defer({promise: storefront.query(...)})`.
497
+ * In this situation, we don't want to expose private error information to the browser so we only
498
+ * do it in development.
499
+ */
500
+ toJSON() {
501
+ const formatted = { name: "Error", message: "" };
502
+ {
503
+ formatted.name = this.name;
504
+ formatted.message = "Development: " + this.message;
505
+ if (this.path)
506
+ formatted.path = this.path;
507
+ if (this.locations)
508
+ formatted.locations = this.locations;
509
+ if (this.extensions)
510
+ formatted.extensions = this.extensions;
511
+ }
512
+ return formatted;
513
+ }
514
+ };
515
+ function throwErrorWithGqlLink({
516
+ url,
517
+ response,
518
+ errors,
519
+ type,
520
+ query,
521
+ queryVariables,
522
+ ErrorConstructor = Error,
523
+ client = "storefront"
524
+ }) {
525
+ const errorMessage = (typeof errors === "string" ? errors : errors?.map?.((error) => error.message).join("\n")) || `URL: ${url}
526
+ API response error: ${response.status}`;
527
+ const gqlError = new GraphQLError(errorMessage, {
528
+ query,
529
+ queryVariables,
530
+ cause: { errors },
531
+ clientOperation: `${client}.${type}`,
532
+ requestId: response.headers.get("x-request-id")
533
+ });
534
+ throw new ErrorConstructor(gqlError.message, { cause: gqlError.cause });
535
+ }
536
+
537
+ // src/utils/callsites.ts
538
+ function withSyncStack(promise, options = {}) {
539
+ const syncError = new Error();
540
+ const getSyncStack = (message, name = "Error") => {
541
+ const syncStack = (syncError.stack ?? "").split("\n").slice(3 + (options.stackOffset ?? 0)).join("\n").replace(/ at loader(\d+) \(/, (all, m1) => all.replace(m1, ""));
542
+ return `${name}: ${message}
543
+ ` + syncStack;
544
+ };
545
+ return promise.then((result) => {
546
+ if (result?.errors && Array.isArray(result.errors)) {
547
+ const logErrors = typeof options.logErrors === "function" ? options.logErrors : () => options.logErrors ?? false;
548
+ result.errors.forEach((error) => {
549
+ if (error) {
550
+ error.stack = getSyncStack(error.message, error.name);
551
+ if (logErrors(error))
552
+ console.error(error);
553
+ }
554
+ });
555
+ }
556
+ return result;
557
+ }).catch((error) => {
558
+ if (error)
559
+ error.stack = getSyncStack(error.message, error.name);
560
+ throw error;
561
+ });
562
+ }
563
+ var getCallerStackLine = (stackOffset = 0) => {
564
+ let stackInfo = void 0;
565
+ const original = Error.prepareStackTrace;
566
+ Error.prepareStackTrace = (_, callsites) => {
567
+ const cs = callsites[2 + stackOffset];
568
+ stackInfo = cs && {
569
+ file: cs.getFileName() ?? void 0,
570
+ func: cs.getFunctionName() ?? void 0,
571
+ line: cs.getLineNumber() ?? void 0,
572
+ column: cs.getColumnNumber() ?? void 0
573
+ };
574
+ return "";
575
+ };
576
+ const err = { stack: "" };
577
+ Error.captureStackTrace(err);
578
+ err.stack;
579
+ Error.prepareStackTrace = original;
580
+ return stackInfo;
581
+ } ;
582
+
583
+ // src/storefront.ts
584
+ var defaultI18n = { language: "EN", country: "US" };
585
+ function createStorefrontClient(options) {
586
+ const {
587
+ storefrontHeaders,
588
+ cache,
589
+ waitUntil,
590
+ i18n,
591
+ storefrontId,
592
+ logErrors = true,
593
+ ...clientOptions
594
+ } = options;
595
+ const H2_PREFIX_WARN = "[h2:warn:createStorefrontClient] ";
596
+ if (!cache) {
597
+ warnOnce(
598
+ H2_PREFIX_WARN + "Storefront API client created without a cache instance. This may slow down your sub-requests."
599
+ );
600
+ }
601
+ const {
602
+ getPublicTokenHeaders,
603
+ getPrivateTokenHeaders,
604
+ getStorefrontApiUrl,
605
+ getShopifyDomain
606
+ } = createStorefrontClient$1(clientOptions);
607
+ const getHeaders = clientOptions.privateStorefrontToken ? getPrivateTokenHeaders : getPublicTokenHeaders;
608
+ const defaultHeaders = getHeaders({
609
+ contentType: "json",
610
+ buyerIp: storefrontHeaders?.buyerIp || ""
611
+ });
612
+ defaultHeaders[STOREFRONT_REQUEST_GROUP_ID_HEADER] = storefrontHeaders?.requestGroupId || generateUUID();
613
+ if (storefrontId)
614
+ defaultHeaders[SHOPIFY_STOREFRONT_ID_HEADER] = storefrontId;
615
+ defaultHeaders["user-agent"] = `Hydrogen ${LIB_VERSION}`;
616
+ if (storefrontHeaders && storefrontHeaders.cookie) {
617
+ const cookies = getShopifyCookies(storefrontHeaders.cookie ?? "");
618
+ if (cookies[SHOPIFY_Y])
619
+ defaultHeaders[SHOPIFY_STOREFRONT_Y_HEADER] = cookies[SHOPIFY_Y];
620
+ if (cookies[SHOPIFY_S])
621
+ defaultHeaders[SHOPIFY_STOREFRONT_S_HEADER] = cookies[SHOPIFY_S];
622
+ }
623
+ const cacheKeyHeader = JSON.stringify({
624
+ "content-type": defaultHeaders["content-type"],
625
+ "user-agent": defaultHeaders["user-agent"],
626
+ [SDK_VARIANT_HEADER]: defaultHeaders[SDK_VARIANT_HEADER],
627
+ [SDK_VARIANT_SOURCE_HEADER]: defaultHeaders[SDK_VARIANT_SOURCE_HEADER],
628
+ [SDK_VERSION_HEADER]: defaultHeaders[SDK_VERSION_HEADER],
629
+ [STOREFRONT_ACCESS_TOKEN_HEADER]: defaultHeaders[STOREFRONT_ACCESS_TOKEN_HEADER]
630
+ });
631
+ async function fetchStorefrontApi({
632
+ query,
633
+ mutation,
634
+ variables,
635
+ cache: cacheOptions,
636
+ headers = [],
637
+ storefrontApiVersion,
638
+ displayName,
639
+ stackInfo
640
+ }) {
641
+ const userHeaders = headers instanceof Headers ? Object.fromEntries(headers.entries()) : Array.isArray(headers) ? Object.fromEntries(headers) : headers;
642
+ query = query ?? mutation;
643
+ const queryVariables = { ...variables };
644
+ if (i18n) {
645
+ if (!variables?.country && /\$country/.test(query)) {
646
+ queryVariables.country = i18n.country;
647
+ }
648
+ if (!variables?.language && /\$language/.test(query)) {
649
+ queryVariables.language = i18n.language;
650
+ }
651
+ }
652
+ const url = getStorefrontApiUrl({ storefrontApiVersion });
653
+ const graphqlData = JSON.stringify({ query, variables: queryVariables });
654
+ const requestInit = {
655
+ method: "POST",
656
+ headers: { ...defaultHeaders, ...userHeaders },
657
+ body: graphqlData
658
+ };
659
+ const cacheKey = [
660
+ url,
661
+ requestInit.method,
662
+ cacheKeyHeader,
663
+ requestInit.body
664
+ ];
665
+ const [body, response] = await fetchWithServerCache(url, requestInit, {
666
+ cacheInstance: mutation ? void 0 : cache,
667
+ cache: cacheOptions || CacheDefault(),
668
+ cacheKey,
669
+ shouldCacheResponse: checkGraphQLErrors,
670
+ waitUntil,
671
+ debugInfo: {
672
+ url,
673
+ graphql: graphqlData,
674
+ requestId: requestInit.headers[STOREFRONT_REQUEST_GROUP_ID_HEADER],
675
+ purpose: storefrontHeaders?.purpose,
676
+ stackInfo,
677
+ displayName
678
+ }
679
+ });
680
+ const errorOptions = {
681
+ url,
682
+ response,
683
+ type: mutation ? "mutation" : "query",
684
+ query,
685
+ queryVariables,
686
+ errors: void 0
687
+ };
688
+ if (!response.ok) {
689
+ let errors2;
690
+ try {
691
+ errors2 = parseJSON(body);
692
+ } catch (_e) {
693
+ errors2 = [{ message: body }];
694
+ }
695
+ throwErrorWithGqlLink({ ...errorOptions, errors: errors2 });
696
+ }
697
+ const { data, errors } = body;
698
+ const gqlErrors = errors?.map(
699
+ ({ message, ...rest }) => new GraphQLError(message, {
700
+ ...rest,
701
+ clientOperation: `storefront.${errorOptions.type}`,
702
+ requestId: response.headers.get("x-request-id"),
703
+ queryVariables,
704
+ query
705
+ })
706
+ );
707
+ return formatAPIResult(data, gqlErrors);
708
+ }
709
+ return {
710
+ storefront: {
711
+ /**
712
+ * Sends a GraphQL query to the Storefront API.
713
+ *
714
+ * Example:
715
+ *
716
+ * ```js
717
+ * async function loader ({context: {storefront}}) {
718
+ * const data = await storefront.query('query { ... }', {
719
+ * variables: {},
720
+ * cache: storefront.CacheLong()
721
+ * });
722
+ * }
723
+ * ```
724
+ */
725
+ query(query, options2) {
726
+ query = minifyQuery(query);
727
+ assertQuery(query, "storefront.query");
728
+ const stackOffset = getStackOffset?.(query);
729
+ return withSyncStack(
730
+ fetchStorefrontApi({
731
+ ...options2,
732
+ query,
733
+ stackInfo: getCallerStackLine?.(stackOffset)
734
+ }),
735
+ { stackOffset, logErrors }
736
+ );
737
+ },
738
+ /**
739
+ * Sends a GraphQL mutation to the Storefront API.
740
+ *
741
+ * Example:
742
+ *
743
+ * ```js
744
+ * async function loader ({context: {storefront}}) {
745
+ * await storefront.mutate('mutation { ... }', {
746
+ * variables: {},
747
+ * });
748
+ * }
749
+ * ```
750
+ */
751
+ mutate(mutation, options2) {
752
+ mutation = minifyQuery(mutation);
753
+ assertMutation(mutation, "storefront.mutate");
754
+ const stackOffset = getStackOffset?.(mutation);
755
+ return withSyncStack(
756
+ fetchStorefrontApi({
757
+ ...options2,
758
+ mutation,
759
+ stackInfo: getCallerStackLine?.(stackOffset)
760
+ }),
761
+ { stackOffset, logErrors }
762
+ );
763
+ },
764
+ cache,
765
+ CacheNone,
766
+ CacheLong,
767
+ CacheShort,
768
+ CacheCustom,
769
+ generateCacheControlHeader,
770
+ getPublicTokenHeaders,
771
+ getPrivateTokenHeaders,
772
+ getShopifyDomain,
773
+ getApiUrl: getStorefrontApiUrl,
774
+ /**
775
+ * @deprecated
776
+ * Wether it's a GraphQL error returned in the Storefront API response.
777
+ *
778
+ * Example:
779
+ *
780
+ * ```js
781
+ * async function loader ({context: {storefront}}) {
782
+ * try {
783
+ * await storefront.query(...);
784
+ * } catch(error) {
785
+ * if (storefront.isApiError(error)) {
786
+ * // ...
787
+ * }
788
+ *
789
+ * throw error;
790
+ * }
791
+ * }
792
+ * ```
793
+ */
794
+ isApiError: (error) => {
795
+ {
796
+ warnOnce(
797
+ "`isApiError` is deprecated. An `errors` object would be returned from the API if there is an error."
798
+ );
799
+ }
800
+ return false;
801
+ },
802
+ i18n: i18n ?? defaultI18n
803
+ }
804
+ };
805
+ }
806
+ var getStackOffset = (query) => {
807
+ let stackOffset = 0;
808
+ if (/fragment CartApi(Query|Mutation) on Cart/.test(query)) {
809
+ stackOffset = 1;
810
+ }
811
+ return stackOffset;
812
+ } ;
813
+ function formatAPIResult(data, errors) {
814
+ return {
815
+ ...data,
816
+ ...errors && { errors }
817
+ };
818
+ }
819
+
820
+ // src/utils/request.ts
821
+ function getHeader(request, key) {
822
+ const value = request.headers?.get?.(key) ?? request.headers?.[key];
823
+ return typeof value === "string" ? value : null;
824
+ }
825
+ function getDebugHeaders(request) {
826
+ return {
827
+ requestId: request ? getHeader(request, "request-id") : void 0,
828
+ purpose: request ? getHeader(request, "purpose") : void 0
829
+ };
830
+ }
831
+
832
+ // src/with-cache.ts
833
+ function createWithCache({
834
+ cache,
835
+ waitUntil,
836
+ request
837
+ }) {
838
+ return function withCache(cacheKey, strategy, actionFn) {
839
+ return runWithCache(cacheKey, actionFn, {
840
+ strategy,
841
+ cacheInstance: cache,
842
+ waitUntil,
843
+ debugInfo: {
844
+ ...getDebugHeaders(request),
845
+ stackInfo: getCallerStackLine?.()
846
+ }
847
+ });
848
+ };
849
+ }
850
+
851
+ // src/cache/in-memory.ts
852
+ var InMemoryCache = class {
853
+ #store;
854
+ constructor() {
855
+ this.#store = /* @__PURE__ */ new Map();
856
+ }
857
+ add(request) {
858
+ throw new Error("Method not implemented. Use `put` instead.");
859
+ }
860
+ addAll(requests) {
861
+ throw new Error("Method not implemented. Use `put` instead.");
862
+ }
863
+ matchAll(request, options) {
864
+ throw new Error("Method not implemented. Use `match` instead.");
865
+ }
866
+ async put(request, response) {
867
+ if (request.method !== "GET") {
868
+ throw new TypeError("Cannot cache response to non-GET request.");
869
+ }
870
+ if (response.status === 206) {
871
+ throw new TypeError(
872
+ "Cannot cache response to a range request (206 Partial Content)."
873
+ );
874
+ }
875
+ if (response.headers.get("vary")?.includes("*")) {
876
+ throw new TypeError("Cannot cache response with 'Vary: *' header.");
877
+ }
878
+ this.#store.set(request.url, {
879
+ body: new Uint8Array(await response.arrayBuffer()),
880
+ status: response.status,
881
+ headers: [...response.headers],
882
+ timestamp: Date.now()
883
+ });
884
+ }
885
+ async match(request) {
886
+ if (request.method !== "GET")
887
+ return;
888
+ const match = this.#store.get(request.url);
889
+ if (!match) {
890
+ return;
891
+ }
892
+ const { body, timestamp, ...metadata } = match;
893
+ const headers = new Headers(metadata.headers);
894
+ const cacheControl = headers.get("cache-control") || headers.get("real-cache-control") || "";
895
+ const maxAge = parseInt(
896
+ cacheControl.match(/max-age=(\d+)/)?.[1] || "0",
897
+ 10
898
+ );
899
+ const swr = parseInt(
900
+ cacheControl.match(/stale-while-revalidate=(\d+)/)?.[1] || "0",
901
+ 10
902
+ );
903
+ const age = (Date.now() - timestamp) / 1e3;
904
+ const isMiss = age > maxAge + swr;
905
+ if (isMiss) {
906
+ this.#store.delete(request.url);
907
+ return;
908
+ }
909
+ const isStale3 = age > maxAge;
910
+ headers.set("cache", isStale3 ? "STALE" : "HIT");
911
+ headers.set("date", new Date(timestamp).toUTCString());
912
+ return new Response(body, {
913
+ status: metadata.status ?? 200,
914
+ headers
915
+ });
916
+ }
917
+ async delete(request) {
918
+ if (this.#store.has(request.url)) {
919
+ this.#store.delete(request.url);
920
+ return true;
921
+ }
922
+ return false;
923
+ }
924
+ keys(request) {
925
+ const cacheKeys = [];
926
+ for (const url of this.#store.keys()) {
927
+ if (!request || request.url === url) {
928
+ cacheKeys.push(new Request(url));
929
+ }
930
+ }
931
+ return Promise.resolve(cacheKeys);
932
+ }
933
+ };
934
+
935
+ // src/utils/get-redirect-url.ts
936
+ function getRedirectUrl(requestUrl) {
937
+ if (!requestUrl)
938
+ return;
939
+ const { pathname, search } = new URL(requestUrl);
940
+ const redirectFrom = pathname + search;
941
+ const searchParams = new URLSearchParams(search);
942
+ const redirectTo = searchParams.get("return_to") || searchParams.get("redirect");
943
+ if (redirectTo) {
944
+ if (isLocalPath(requestUrl, redirectTo)) {
945
+ return redirectTo;
946
+ } else {
947
+ console.warn(
948
+ `Cross-domain redirects are not supported. Tried to redirect from ${redirectFrom} to ${redirectTo}`
949
+ );
950
+ }
951
+ }
952
+ }
953
+ function isLocalPath(requestUrl, redirectUrl) {
954
+ try {
955
+ return new URL(requestUrl).origin === new URL(redirectUrl, requestUrl).origin;
956
+ } catch (e) {
957
+ return false;
958
+ }
959
+ }
960
+
961
+ // src/routing/redirect.ts
962
+ async function storefrontRedirect(options) {
963
+ const {
964
+ storefront,
965
+ request,
966
+ noAdminRedirect,
967
+ response = new Response("Not Found", { status: 404 })
968
+ } = options;
969
+ const url = new URL(request.url);
970
+ const isSoftNavigation = url.searchParams.has("_data");
971
+ url.searchParams.delete("_data");
972
+ const redirectFrom = url.toString().replace(url.origin, "");
973
+ if (url.pathname === "/admin" && !noAdminRedirect) {
974
+ return createRedirectResponse(
975
+ `${storefront.getShopifyDomain()}/admin`,
976
+ isSoftNavigation
977
+ );
978
+ }
979
+ try {
980
+ const { urlRedirects } = await storefront.query(REDIRECT_QUERY, {
981
+ variables: { query: "path:" + redirectFrom }
982
+ });
983
+ const location = urlRedirects?.edges?.[0]?.node?.target;
984
+ if (location) {
985
+ return createRedirectResponse(location, isSoftNavigation);
986
+ }
987
+ const redirectTo = getRedirectUrl(request.url);
988
+ if (redirectTo) {
989
+ return createRedirectResponse(redirectTo, isSoftNavigation);
990
+ }
991
+ } catch (error) {
992
+ console.error(
993
+ `Failed to fetch redirects from Storefront API for route ${redirectFrom}`,
994
+ error
995
+ );
996
+ }
997
+ return response;
998
+ }
999
+ function createRedirectResponse(location, isSoftNavigation) {
1000
+ if (isSoftNavigation) {
1001
+ return new Response(null, {
1002
+ status: 200,
1003
+ headers: { "X-Remix-Redirect": location, "X-Remix-Status": "302" }
1004
+ });
1005
+ } else {
1006
+ return new Response(null, {
1007
+ status: 302,
1008
+ headers: { location }
1009
+ });
1010
+ }
1011
+ }
1012
+ var REDIRECT_QUERY = `#graphql
1013
+ query redirects($query: String) {
1014
+ urlRedirects(first: 1, query: $query) {
1015
+ edges {
1016
+ node {
1017
+ target
1018
+ }
1019
+ }
1020
+ }
1021
+ }
1022
+ `;
1023
+
1024
+ // src/routing/graphiql.ts
1025
+ var graphiqlLoader = async function graphiqlLoader2({
1026
+ request,
1027
+ context
1028
+ }) {
1029
+ const storefront = context.storefront;
1030
+ const customerAccount = context.customerAccount;
1031
+ const url = new URL(request.url);
1032
+ if (!storefront) {
1033
+ throw new Error(
1034
+ `GraphiQL: Hydrogen's storefront client must be injected in the loader context.`
1035
+ );
1036
+ }
1037
+ const schemas = {};
1038
+ if (storefront) {
1039
+ const authHeader = "X-Shopify-Storefront-Access-Token";
1040
+ schemas.storefront = {
1041
+ name: "Storefront API",
1042
+ authHeader,
1043
+ accessToken: storefront.getPublicTokenHeaders()[authHeader],
1044
+ apiUrl: storefront.getApiUrl(),
1045
+ icon: "SF"
1046
+ };
1047
+ }
1048
+ if (customerAccount) {
1049
+ const customerAccountSchema = await (await fetch(url.origin + "/graphiql/customer-account.schema.json")).json();
1050
+ const accessToken = await customerAccount.getAccessToken();
1051
+ if (customerAccountSchema) {
1052
+ schemas["customer-account"] = {
1053
+ name: "Customer Account API",
1054
+ value: customerAccountSchema,
1055
+ authHeader: "Authorization",
1056
+ accessToken,
1057
+ apiUrl: customerAccount.getApiUrl(),
1058
+ icon: "CA"
1059
+ };
1060
+ }
1061
+ }
1062
+ const favicon = `https://avatars.githubusercontent.com/u/12972006?s=48&v=4`;
1063
+ const html = String.raw;
1064
+ return new Response(
1065
+ html`
1066
+ <!DOCTYPE html>
1067
+ <html lang="en">
1068
+ <head>
1069
+ <title>GraphiQL</title>
1070
+ <link rel="icon" type="image/x-icon" href="${favicon}" />
1071
+ <style>
1072
+ body {
1073
+ height: 100%;
1074
+ margin: 0;
1075
+ width: 100%;
1076
+ overflow: hidden;
1077
+ background-color: hsl(219, 29%, 18%);
1078
+ }
1079
+
1080
+ #graphiql {
1081
+ height: 100vh;
1082
+ }
1083
+
1084
+ #graphiql > .placeholder {
1085
+ color: slategray;
1086
+ width: fit-content;
1087
+ margin: 40px auto;
1088
+ font-family: Arial;
1089
+ }
1090
+
1091
+ .graphiql-api-toolbar-label {
1092
+ position: absolute;
1093
+ bottom: -6px;
1094
+ right: -4px;
1095
+ font-size: 8px;
1096
+ }
1097
+ </style>
1098
+
1099
+ <script
1100
+ crossorigin
1101
+ src="https://unpkg.com/react@18/umd/react.development.js"
1102
+ ></script>
1103
+ <script
1104
+ crossorigin
1105
+ src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"
1106
+ ></script>
1107
+ <link
1108
+ rel="stylesheet"
1109
+ href="https://unpkg.com/graphiql@3/graphiql.min.css"
1110
+ />
1111
+ <link
1112
+ rel="stylesheet"
1113
+ href="https://unpkg.com/@graphiql/plugin-explorer/dist/style.css"
1114
+ />
1115
+ </head>
1116
+
1117
+ <body>
1118
+ <div id="graphiql">
1119
+ <div class="placeholder">Loading GraphiQL...</div>
1120
+ </div>
1121
+
1122
+ <script
1123
+ src="https://unpkg.com/graphiql@3/graphiql.min.js"
1124
+ type="application/javascript"
1125
+ crossorigin="anonymous"
1126
+ ></script>
1127
+ <script
1128
+ src="https://unpkg.com/@graphiql/plugin-explorer/dist/index.umd.js"
1129
+ type="application/javascript"
1130
+ crossorigin="anonymous"
1131
+ ></script>
1132
+
1133
+ <script>
1134
+ const windowUrl = new URL(document.URL);
1135
+ const startingSchemaKey =
1136
+ windowUrl.searchParams.get('schema') || 'storefront';
1137
+
1138
+ let query = '{ shop { name } }';
1139
+ if (windowUrl.searchParams.has('query')) {
1140
+ query = decodeURIComponent(
1141
+ windowUrl.searchParams.get('query') ?? query,
1142
+ );
1143
+ }
1144
+
1145
+ // Prettify query
1146
+ query = GraphiQL.GraphQL.print(GraphiQL.GraphQL.parse(query));
1147
+
1148
+ let variables;
1149
+ if (windowUrl.searchParams.has('variables')) {
1150
+ variables = decodeURIComponent(
1151
+ windowUrl.searchParams.get('variables') ?? '',
1152
+ );
1153
+ }
1154
+
1155
+ // Prettify variables
1156
+ if (variables) {
1157
+ variables = JSON.stringify(JSON.parse(variables), null, 2);
1158
+ }
1159
+
1160
+ const schemas = ${JSON.stringify(schemas)};
1161
+ let lastActiveTabIndex = -1;
1162
+ let lastTabAmount = -1;
1163
+
1164
+ const root = ReactDOM.createRoot(
1165
+ document.getElementById('graphiql'),
1166
+ );
1167
+
1168
+ root.render(React.createElement(RootWrapper));
1169
+
1170
+ const TAB_STATE_KEY = 'graphiql:tabState';
1171
+ const storage = {
1172
+ getTabState: () =>
1173
+ JSON.parse(localStorage.getItem(TAB_STATE_KEY)),
1174
+ setTabState: (state) =>
1175
+ localStorage.setItem(TAB_STATE_KEY, JSON.stringify(state)),
1176
+ };
1177
+
1178
+ let nextSchemaKey;
1179
+
1180
+ function RootWrapper() {
1181
+ const [activeSchema, setActiveSchema] =
1182
+ React.useState(startingSchemaKey);
1183
+
1184
+ const schema = schemas[activeSchema];
1185
+ if (!schema) {
1186
+ throw new Error('No schema found for ' + activeSchema);
1187
+ }
1188
+
1189
+ const keys = Object.keys(schemas);
1190
+
1191
+ return React.createElement(
1192
+ GraphiQL,
1193
+ {
1194
+ fetcher: GraphiQL.createFetcher({
1195
+ url: schema.apiUrl,
1196
+ headers: {[schema.authHeader]: schema.accessToken},
1197
+ }),
1198
+ defaultEditorToolsVisibility: true,
1199
+ query,
1200
+ variables,
1201
+ schema: schema.value,
1202
+ plugins: [GraphiQLPluginExplorer.explorerPlugin()],
1203
+ onTabChange: (state) => {
1204
+ const {activeTabIndex, tabs} = state;
1205
+ const activeTab = tabs[activeTabIndex];
1206
+
1207
+ if (
1208
+ activeTabIndex === lastActiveTabIndex &&
1209
+ lastTabAmount === tabs.length
1210
+ ) {
1211
+ if (
1212
+ nextSchemaKey &&
1213
+ activeTab &&
1214
+ activeTab.schemaKey !== nextSchemaKey
1215
+ ) {
1216
+ activeTab.schemaKey = nextSchemaKey;
1217
+ nextSchemaKey = undefined;
1218
+
1219
+ // Sync state to localStorage. GraphiQL resets the state
1220
+ // asynchronously, so we need to do it in a timeout.
1221
+ storage.setTabState(state);
1222
+ setTimeout(() => storage.setTabState(state), 500);
1223
+ }
1224
+
1225
+ // React rerrendering, skip
1226
+ return;
1227
+ }
1228
+
1229
+ if (activeTab) {
1230
+ if (!activeTab.schemaKey) {
1231
+ // Creating a new tab
1232
+ if (lastTabAmount < tabs.length) {
1233
+ activeTab.schemaKey = activeSchema;
1234
+ storage.setTabState(state);
1235
+ }
1236
+ }
1237
+
1238
+ const nextSchema = activeTab.schemaKey || 'storefront';
1239
+
1240
+ if (nextSchema !== activeSchema) {
1241
+ setActiveSchema(nextSchema);
1242
+ }
1243
+ }
1244
+
1245
+ lastActiveTabIndex = activeTabIndex;
1246
+ lastTabAmount = tabs.length;
1247
+ },
1248
+ toolbar: {
1249
+ additionalComponent: function () {
1250
+ const schema = schemas[activeSchema];
1251
+
1252
+ return React.createElement(
1253
+ GraphiQL.React.ToolbarButton,
1254
+ {
1255
+ onClick: () => {
1256
+ const activeKeyIndex = keys.indexOf(activeSchema);
1257
+ nextSchemaKey =
1258
+ keys[(activeKeyIndex + 1) % keys.length];
1259
+
1260
+ // This triggers onTabChange
1261
+ if (nextSchemaKey) setActiveSchema(nextSchemaKey);
1262
+ },
1263
+ label: 'Toggle between different API schemas',
1264
+ },
1265
+ React.createElement(
1266
+ 'div',
1267
+ {
1268
+ key: 'api-wrapper',
1269
+ className: 'graphiql-toolbar-icon',
1270
+ style: {position: 'relative', fontWeight: 'bolder'},
1271
+ },
1272
+ [
1273
+ React.createElement(
1274
+ 'div',
1275
+ {key: 'icon', style: {textAlign: 'center'}},
1276
+ [
1277
+ schema.icon,
1278
+ React.createElement(
1279
+ 'div',
1280
+ {
1281
+ key: 'icon-label',
1282
+ className: 'graphiql-api-toolbar-label',
1283
+ },
1284
+ 'API',
1285
+ ),
1286
+ ],
1287
+ ),
1288
+ ],
1289
+ ),
1290
+ );
1291
+ },
1292
+ },
1293
+ },
1294
+ [
1295
+ React.createElement(
1296
+ GraphiQL.Logo,
1297
+ {
1298
+ key: 'Logo replacement',
1299
+ },
1300
+ [
1301
+ React.createElement(
1302
+ 'div',
1303
+ {
1304
+ key: 'Logo wrapper',
1305
+ style: {display: 'flex', alignItems: 'center'},
1306
+ },
1307
+ [
1308
+ React.createElement(
1309
+ 'div',
1310
+ {
1311
+ key: 'api',
1312
+ className: 'graphiql-logo',
1313
+ style: {
1314
+ paddingRight: 0,
1315
+ whiteSpace: 'nowrap',
1316
+ },
1317
+ },
1318
+ [schema.name],
1319
+ ),
1320
+ React.createElement(GraphiQL.Logo, {key: 'logo'}),
1321
+ ],
1322
+ ),
1323
+ ],
1324
+ ),
1325
+ ],
1326
+ );
1327
+ }
1328
+ </script>
1329
+ </body>
1330
+ </html>
1331
+ `,
1332
+ { status: 200, headers: { "content-type": "text/html" } }
1333
+ );
1334
+ };
1335
+
1336
+ // src/seo/escape.ts
1337
+ var ESCAPE_LOOKUP = {
1338
+ "&": "\\u0026",
1339
+ ">": "\\u003e",
1340
+ "<": "\\u003c",
1341
+ "\u2028": "\\u2028",
1342
+ "\u2029": "\\u2029"
1343
+ };
1344
+ var ESCAPE_REGEX = /[&><\u2028\u2029]/g;
1345
+ function escapeHtml(html) {
1346
+ return html.replace(ESCAPE_REGEX, (match) => ESCAPE_LOOKUP[match]);
1347
+ }
1348
+
1349
+ // src/seo/generate-seo-tags.ts
1350
+ var ERROR_PREFIX = "Error in SEO input: ";
1351
+ var schema = {
1352
+ title: {
1353
+ validate: (value) => {
1354
+ if (typeof value !== "string") {
1355
+ throw new Error(ERROR_PREFIX.concat("`title` should be a string"));
1356
+ }
1357
+ if (typeof value === "string" && value.length > 120) {
1358
+ throw new Error(
1359
+ ERROR_PREFIX.concat(
1360
+ "`title` should not be longer than 120 characters"
1361
+ )
1362
+ );
1363
+ }
1364
+ return value;
1365
+ }
1366
+ },
1367
+ description: {
1368
+ validate: (value) => {
1369
+ if (typeof value !== "string") {
1370
+ throw new Error(
1371
+ ERROR_PREFIX.concat("`description` should be a string")
1372
+ );
1373
+ }
1374
+ if (typeof value === "string" && value.length > 155) {
1375
+ throw new Error(
1376
+ ERROR_PREFIX.concat(
1377
+ "`description` should not be longer than 155 characters"
1378
+ )
1379
+ );
1380
+ }
1381
+ return value;
1382
+ }
1383
+ },
1384
+ url: {
1385
+ validate: (value) => {
1386
+ if (typeof value !== "string") {
1387
+ throw new Error(ERROR_PREFIX.concat("`url` should be a string"));
1388
+ }
1389
+ if (typeof value === "string" && !value.startsWith("http")) {
1390
+ throw new Error(ERROR_PREFIX.concat("`url` should be a valid URL"));
1391
+ }
1392
+ return value;
1393
+ }
1394
+ },
1395
+ handle: {
1396
+ validate: (value) => {
1397
+ if (typeof value !== "string") {
1398
+ throw new Error(ERROR_PREFIX.concat("`handle` should be a string"));
1399
+ }
1400
+ if (typeof value === "string" && !value.startsWith("@")) {
1401
+ throw new Error(ERROR_PREFIX.concat("`handle` should start with `@`"));
1402
+ }
1403
+ return value;
1404
+ }
1405
+ }
1406
+ };
1407
+ function generateSeoTags(seoInput) {
1408
+ const tagResults = [];
1409
+ for (const seoKey of Object.keys(seoInput)) {
1410
+ switch (seoKey) {
1411
+ case "title": {
1412
+ const content = validate(schema.title, seoInput.title);
1413
+ const title = renderTitle(seoInput?.titleTemplate, content);
1414
+ if (!title) {
1415
+ break;
1416
+ }
1417
+ tagResults.push(
1418
+ generateTag("title", { title }),
1419
+ generateTag("meta", { property: "og:title", content: title }),
1420
+ generateTag("meta", { name: "twitter:title", content: title })
1421
+ );
1422
+ break;
1423
+ }
1424
+ case "description": {
1425
+ const content = validate(schema.description, seoInput.description);
1426
+ if (!content) {
1427
+ break;
1428
+ }
1429
+ tagResults.push(
1430
+ generateTag("meta", {
1431
+ name: "description",
1432
+ content
1433
+ }),
1434
+ generateTag("meta", {
1435
+ property: "og:description",
1436
+ content
1437
+ }),
1438
+ generateTag("meta", {
1439
+ name: "twitter:description",
1440
+ content
1441
+ })
1442
+ );
1443
+ break;
1444
+ }
1445
+ case "url": {
1446
+ const content = validate(schema.url, seoInput.url);
1447
+ if (!content) {
1448
+ break;
1449
+ }
1450
+ const urlWithoutParams = content.split("?")[0];
1451
+ const urlWithoutTrailingSlash = urlWithoutParams.replace(/\/$/, "");
1452
+ tagResults.push(
1453
+ generateTag("link", {
1454
+ rel: "canonical",
1455
+ href: urlWithoutTrailingSlash
1456
+ }),
1457
+ generateTag("meta", {
1458
+ property: "og:url",
1459
+ content: urlWithoutTrailingSlash
1460
+ })
1461
+ );
1462
+ break;
1463
+ }
1464
+ case "handle": {
1465
+ const content = validate(schema.handle, seoInput.handle);
1466
+ if (!content) {
1467
+ break;
1468
+ }
1469
+ tagResults.push(
1470
+ generateTag("meta", { name: "twitter:site", content }),
1471
+ generateTag("meta", { name: "twitter:creator", content })
1472
+ );
1473
+ break;
1474
+ }
1475
+ case "media": {
1476
+ let content;
1477
+ const values = ensureArray(seoInput.media);
1478
+ for (const media of values) {
1479
+ if (typeof media === "string") {
1480
+ tagResults.push(
1481
+ generateTag("meta", { name: "og:image", content: media })
1482
+ );
1483
+ }
1484
+ if (media && typeof media === "object") {
1485
+ const type = media.type || "image";
1486
+ const normalizedMedia = media ? {
1487
+ url: media?.url,
1488
+ secure_url: media?.url,
1489
+ type: inferMimeType(media.url),
1490
+ width: media?.width,
1491
+ height: media?.height,
1492
+ alt: media?.altText
1493
+ } : {};
1494
+ for (const key of Object.keys(normalizedMedia)) {
1495
+ if (normalizedMedia[key]) {
1496
+ content = normalizedMedia[key];
1497
+ tagResults.push(
1498
+ generateTag(
1499
+ "meta",
1500
+ {
1501
+ property: `og:${type}:${key}`,
1502
+ content
1503
+ },
1504
+ normalizedMedia.url
1505
+ )
1506
+ );
1507
+ }
1508
+ }
1509
+ }
1510
+ }
1511
+ break;
1512
+ }
1513
+ case "jsonLd": {
1514
+ const jsonLdBlocks = ensureArray(seoInput.jsonLd);
1515
+ let index = 0;
1516
+ for (const block of jsonLdBlocks) {
1517
+ if (typeof block !== "object") {
1518
+ continue;
1519
+ }
1520
+ const tag = generateTag(
1521
+ "script",
1522
+ {
1523
+ type: "application/ld+json",
1524
+ children: JSON.stringify(block, (k, value) => {
1525
+ return typeof value === "string" ? escapeHtml(value) : value;
1526
+ })
1527
+ },
1528
+ // @ts-expect-error
1529
+ `json-ld-${block?.["@type"] || block?.name || index++}`
1530
+ );
1531
+ tagResults.push(tag);
1532
+ }
1533
+ break;
1534
+ }
1535
+ case "alternates": {
1536
+ const alternates = ensureArray(seoInput.alternates);
1537
+ for (const alternate of alternates) {
1538
+ if (!alternate) {
1539
+ continue;
1540
+ }
1541
+ const { language, url, default: defaultLang } = alternate;
1542
+ const hrefLang = language ? `${language}${defaultLang ? "-default" : ""}` : void 0;
1543
+ tagResults.push(
1544
+ generateTag("link", {
1545
+ rel: "alternate",
1546
+ hrefLang,
1547
+ href: url
1548
+ })
1549
+ );
1550
+ }
1551
+ break;
1552
+ }
1553
+ case "robots": {
1554
+ if (!seoInput.robots) {
1555
+ break;
1556
+ }
1557
+ const {
1558
+ maxImagePreview,
1559
+ maxSnippet,
1560
+ maxVideoPreview,
1561
+ noArchive,
1562
+ noFollow,
1563
+ noImageIndex,
1564
+ noIndex,
1565
+ noSnippet,
1566
+ noTranslate,
1567
+ unavailableAfter
1568
+ } = seoInput.robots;
1569
+ const robotsParams = [
1570
+ noArchive && "noarchive",
1571
+ noImageIndex && "noimageindex",
1572
+ noSnippet && "nosnippet",
1573
+ noTranslate && `notranslate`,
1574
+ maxImagePreview && `max-image-preview:${maxImagePreview}`,
1575
+ maxSnippet && `max-snippet:${maxSnippet}`,
1576
+ maxVideoPreview && `max-video-preview:${maxVideoPreview}`,
1577
+ unavailableAfter && `unavailable_after:${unavailableAfter}`
1578
+ ];
1579
+ let robotsParam = (noIndex ? "noindex" : "index") + "," + (noFollow ? "nofollow" : "follow");
1580
+ for (let param of robotsParams) {
1581
+ if (param) {
1582
+ robotsParam += `,${param}`;
1583
+ }
1584
+ }
1585
+ tagResults.push(
1586
+ generateTag("meta", { name: "robots", content: robotsParam })
1587
+ );
1588
+ break;
1589
+ }
1590
+ }
1591
+ }
1592
+ return tagResults.flat().sort((a, b) => a.key.localeCompare(b.key));
1593
+ }
1594
+ function generateTag(tagName, input, group) {
1595
+ const tag = { tag: tagName, props: {}, key: "" };
1596
+ if (tagName === "title") {
1597
+ tag.children = input.title;
1598
+ tag.key = generateKey(tag);
1599
+ return tag;
1600
+ }
1601
+ if (tagName === "script") {
1602
+ tag.children = typeof input.children === "string" ? input.children : "";
1603
+ tag.key = generateKey(tag, group);
1604
+ delete input.children;
1605
+ tag.props = input;
1606
+ return tag;
1607
+ }
1608
+ tag.props = input;
1609
+ Object.keys(tag.props).forEach(
1610
+ (key) => !tag.props[key] && delete tag.props[key]
1611
+ );
1612
+ tag.key = generateKey(tag, group);
1613
+ return tag;
1614
+ }
1615
+ function generateKey(tag, group) {
1616
+ const { tag: tagName, props } = tag;
1617
+ if (tagName === "title") {
1618
+ return "0-title";
1619
+ }
1620
+ if (tagName === "meta") {
1621
+ const priority = props.content === group && typeof props.property === "string" && !props.property.endsWith("secure_url") && "0";
1622
+ const groupName = [group, priority];
1623
+ return [tagName, ...groupName, props.property || props.name].filter((x) => x).join("-");
1624
+ }
1625
+ if (tagName === "link") {
1626
+ const key = [tagName, props.rel, props.hrefLang || props.media].filter((x) => x).join("-");
1627
+ return key.replace(/\s+/g, "-");
1628
+ }
1629
+ if (tagName === "script") {
1630
+ return `${tagName}-${group}`;
1631
+ }
1632
+ return `${tagName}-${props.type}`;
1633
+ }
1634
+ function renderTitle(template, title) {
1635
+ if (!title) {
1636
+ return void 0;
1637
+ }
1638
+ if (!template) {
1639
+ return title;
1640
+ }
1641
+ if (typeof template === "function") {
1642
+ return template(title);
1643
+ }
1644
+ return template.replace("%s", title ?? "");
1645
+ }
1646
+ function inferMimeType(url) {
1647
+ const ext = url && url.split(".").pop();
1648
+ switch (ext) {
1649
+ case "svg":
1650
+ return "image/svg+xml";
1651
+ case "png":
1652
+ return "image/png";
1653
+ case "gif":
1654
+ return "image/gif";
1655
+ case "swf":
1656
+ return "application/x-shockwave-flash";
1657
+ case "mp3":
1658
+ return "audio/mpeg";
1659
+ case "jpg":
1660
+ case "jpeg":
1661
+ default:
1662
+ return "image/jpeg";
1663
+ }
1664
+ }
1665
+ function ensureArray(value) {
1666
+ return Array.isArray(value) ? value : [value];
1667
+ }
1668
+ function validate(schema2, data) {
1669
+ try {
1670
+ return schema2.validate(data);
1671
+ } catch (error) {
1672
+ console.warn(error.message);
1673
+ return data;
1674
+ }
1675
+ }
1676
+
1677
+ // src/seo/seo.ts
1678
+ var SeoLogger = lazy(() => import('./log-seo-tags-IG37ONQ2.js'));
1679
+ function Seo({ debug }) {
1680
+ const matches = useMatches();
1681
+ const location = useLocation();
1682
+ const seoConfig = useMemo(() => {
1683
+ return matches.flatMap((match) => {
1684
+ const { handle, ...routeMatch } = match;
1685
+ const routeData = { ...routeMatch, ...location };
1686
+ const handleSeo = handle?.seo;
1687
+ const loaderSeo = routeMatch?.data?.seo;
1688
+ if (!handleSeo && !loaderSeo) {
1689
+ return [];
1690
+ }
1691
+ if (handleSeo) {
1692
+ return recursivelyInvokeOrReturn(handleSeo, routeData);
1693
+ } else {
1694
+ return [loaderSeo];
1695
+ }
1696
+ }).reduce((acc, current) => {
1697
+ Object.keys(current).forEach(
1698
+ (key) => !current[key] && delete current[key]
1699
+ );
1700
+ const { jsonLd } = current;
1701
+ if (!jsonLd) {
1702
+ return { ...acc, ...current };
1703
+ }
1704
+ if (!acc?.jsonLd) {
1705
+ return { ...acc, ...current, jsonLd: [jsonLd] };
1706
+ } else {
1707
+ if (Array.isArray(jsonLd)) {
1708
+ return {
1709
+ ...acc,
1710
+ ...current,
1711
+ jsonLd: [...acc.jsonLd, ...jsonLd]
1712
+ };
1713
+ } else {
1714
+ return {
1715
+ ...acc,
1716
+ ...current,
1717
+ jsonLd: [...acc.jsonLd, jsonLd]
1718
+ };
1719
+ }
1720
+ }
1721
+ }, {});
1722
+ }, [matches, location]);
1723
+ const { html, loggerMarkup } = useMemo(() => {
1724
+ const headTags = generateSeoTags(seoConfig);
1725
+ const html2 = headTags.map((tag) => {
1726
+ if (tag.tag === "script") {
1727
+ return createElement(tag.tag, {
1728
+ ...tag.props,
1729
+ key: tag.key,
1730
+ dangerouslySetInnerHTML: { __html: tag.children }
1731
+ });
1732
+ }
1733
+ return createElement(tag.tag, { ...tag.props, key: tag.key }, tag.children);
1734
+ });
1735
+ const loggerMarkup2 = createElement(
1736
+ Suspense,
1737
+ { fallback: null },
1738
+ createElement(SeoLogger, { headTags })
1739
+ );
1740
+ return { html: html2, loggerMarkup: loggerMarkup2 };
1741
+ }, [seoConfig]);
1742
+ return createElement(Fragment, null, html, debug && loggerMarkup);
1743
+ }
1744
+ function recursivelyInvokeOrReturn(value, ...rest) {
1745
+ if (value instanceof Function) {
1746
+ return recursivelyInvokeOrReturn(value(...rest), ...rest);
1747
+ }
1748
+ let result = {};
1749
+ if (Array.isArray(value)) {
1750
+ result = value.reduce((acc, item) => {
1751
+ return [...acc, recursivelyInvokeOrReturn(item)];
1752
+ }, []);
1753
+ return result;
1754
+ }
1755
+ if (value instanceof Object) {
1756
+ const entries = Object.entries(value);
1757
+ entries.forEach(([key, val]) => {
1758
+ result[key] = recursivelyInvokeOrReturn(val, ...rest);
1759
+ });
1760
+ return result;
1761
+ }
1762
+ return value;
1763
+ }
1764
+ function Pagination({
1765
+ connection,
1766
+ children = () => {
1767
+ console.warn("<Pagination> requires children to work properly");
1768
+ return null;
1769
+ }
1770
+ }) {
1771
+ const transition = useNavigation();
1772
+ const isLoading = transition.state === "loading";
1773
+ const {
1774
+ endCursor,
1775
+ hasNextPage,
1776
+ hasPreviousPage,
1777
+ nextPageUrl,
1778
+ nodes,
1779
+ previousPageUrl,
1780
+ startCursor
1781
+ } = usePagination(connection);
1782
+ const state = useMemo(
1783
+ () => ({
1784
+ pageInfo: {
1785
+ endCursor,
1786
+ hasPreviousPage,
1787
+ hasNextPage,
1788
+ startCursor
1789
+ },
1790
+ nodes
1791
+ }),
1792
+ [endCursor, hasNextPage, hasPreviousPage, startCursor, nodes]
1793
+ );
1794
+ const NextLink = useMemo(
1795
+ () => forwardRef(function NextLink2(props, ref) {
1796
+ return hasNextPage ? createElement(Link, {
1797
+ preventScrollReset: true,
1798
+ ...props,
1799
+ to: nextPageUrl,
1800
+ state,
1801
+ replace: true,
1802
+ ref
1803
+ }) : null;
1804
+ }),
1805
+ [hasNextPage, nextPageUrl, state]
1806
+ );
1807
+ const PreviousLink = useMemo(
1808
+ () => forwardRef(function PrevLink(props, ref) {
1809
+ return hasPreviousPage ? createElement(Link, {
1810
+ preventScrollReset: true,
1811
+ ...props,
1812
+ to: previousPageUrl,
1813
+ state,
1814
+ replace: true,
1815
+ ref
1816
+ }) : null;
1817
+ }),
1818
+ [hasPreviousPage, previousPageUrl, state]
1819
+ );
1820
+ return children({
1821
+ state,
1822
+ hasNextPage,
1823
+ hasPreviousPage,
1824
+ isLoading,
1825
+ nextPageUrl,
1826
+ nodes,
1827
+ previousPageUrl,
1828
+ NextLink,
1829
+ PreviousLink
1830
+ });
1831
+ }
1832
+ function getParamsWithoutPagination(paramsString) {
1833
+ const params = new URLSearchParams(paramsString);
1834
+ params.delete("cursor");
1835
+ params.delete("direction");
1836
+ return params.toString();
1837
+ }
1838
+ function makeError(prop) {
1839
+ throw new Error(
1840
+ `The Pagination component requires ${"`" + prop + "`"} to be a part of your query. See the guide on how to setup your query to include ${"`" + prop + "`"}: https://shopify.dev/docs/custom-storefronts/hydrogen/data-fetching/pagination#setup-the-paginated-query`
1841
+ );
1842
+ }
1843
+ function usePagination(connection) {
1844
+ if (!connection.pageInfo) {
1845
+ makeError("pageInfo");
1846
+ }
1847
+ if (typeof connection.pageInfo.startCursor === "undefined") {
1848
+ makeError("pageInfo.startCursor");
1849
+ }
1850
+ if (typeof connection.pageInfo.endCursor === "undefined") {
1851
+ makeError("pageInfo.endCursor");
1852
+ }
1853
+ if (typeof connection.pageInfo.hasNextPage === "undefined") {
1854
+ makeError("pageInfo.hasNextPage");
1855
+ }
1856
+ if (typeof connection.pageInfo.hasPreviousPage === "undefined") {
1857
+ makeError("pageInfo.hasPreviousPage");
1858
+ }
1859
+ const navigate = useNavigate();
1860
+ const { state, search, pathname } = useLocation();
1861
+ const params = new URLSearchParams(search);
1862
+ const direction = params.get("direction");
1863
+ const isPrevious = direction === "previous";
1864
+ const nodes = useMemo(() => {
1865
+ if (!globalThis?.window?.__hydrogenHydrated || !state || !state?.nodes) {
1866
+ return flattenConnection(connection);
1867
+ }
1868
+ if (isPrevious) {
1869
+ return [...flattenConnection(connection), ...state.nodes];
1870
+ } else {
1871
+ return [...state.nodes, ...flattenConnection(connection)];
1872
+ }
1873
+ }, [state, connection]);
1874
+ const currentPageInfo = useMemo(() => {
1875
+ const hydrogenHydrated = globalThis?.window?.__hydrogenHydrated;
1876
+ let pageStartCursor = !hydrogenHydrated || state?.pageInfo?.startCursor === void 0 ? connection.pageInfo.startCursor : state.pageInfo.startCursor;
1877
+ let pageEndCursor = !hydrogenHydrated || state?.pageInfo?.endCursor === void 0 ? connection.pageInfo.endCursor : state.pageInfo.endCursor;
1878
+ let previousPageExists = !hydrogenHydrated || state?.pageInfo?.hasPreviousPage === void 0 ? connection.pageInfo.hasPreviousPage : state.pageInfo.hasPreviousPage;
1879
+ let nextPageExists = !hydrogenHydrated || state?.pageInfo?.hasNextPage === void 0 ? connection.pageInfo.hasNextPage : state.pageInfo.hasNextPage;
1880
+ if (state?.nodes) {
1881
+ if (isPrevious) {
1882
+ pageStartCursor = connection.pageInfo.startCursor;
1883
+ previousPageExists = connection.pageInfo.hasPreviousPage;
1884
+ } else {
1885
+ pageEndCursor = connection.pageInfo.endCursor;
1886
+ nextPageExists = connection.pageInfo.hasNextPage;
1887
+ }
1888
+ }
1889
+ return {
1890
+ startCursor: pageStartCursor,
1891
+ endCursor: pageEndCursor,
1892
+ hasPreviousPage: previousPageExists,
1893
+ hasNextPage: nextPageExists
1894
+ };
1895
+ }, [
1896
+ isPrevious,
1897
+ state,
1898
+ connection.pageInfo.hasNextPage,
1899
+ connection.pageInfo.hasPreviousPage,
1900
+ connection.pageInfo.startCursor,
1901
+ connection.pageInfo.endCursor
1902
+ ]);
1903
+ const urlRef = useRef({
1904
+ params: getParamsWithoutPagination(search),
1905
+ pathname
1906
+ });
1907
+ useEffect(() => {
1908
+ window.__hydrogenHydrated = true;
1909
+ }, []);
1910
+ useEffect(() => {
1911
+ if (
1912
+ // If the URL changes (independent of pagination params)
1913
+ // then reset the pagination params in the URL
1914
+ getParamsWithoutPagination(search) !== urlRef.current.params || pathname !== urlRef.current.pathname
1915
+ ) {
1916
+ urlRef.current = {
1917
+ pathname,
1918
+ params: getParamsWithoutPagination(search)
1919
+ };
1920
+ navigate(`${pathname}?${getParamsWithoutPagination(search)}`, {
1921
+ replace: true,
1922
+ preventScrollReset: true,
1923
+ state: { nodes: void 0, pageInfo: void 0 }
1924
+ });
1925
+ }
1926
+ }, [pathname, search]);
1927
+ const previousPageUrl = useMemo(() => {
1928
+ const params2 = new URLSearchParams(search);
1929
+ params2.set("direction", "previous");
1930
+ currentPageInfo.startCursor && params2.set("cursor", currentPageInfo.startCursor);
1931
+ return `?${params2.toString()}`;
1932
+ }, [search, currentPageInfo.startCursor]);
1933
+ const nextPageUrl = useMemo(() => {
1934
+ const params2 = new URLSearchParams(search);
1935
+ params2.set("direction", "next");
1936
+ currentPageInfo.endCursor && params2.set("cursor", currentPageInfo.endCursor);
1937
+ return `?${params2.toString()}`;
1938
+ }, [search, currentPageInfo.endCursor]);
1939
+ return { ...currentPageInfo, previousPageUrl, nextPageUrl, nodes };
1940
+ }
1941
+ function getPaginationVariables(request, options = { pageBy: 20 }) {
1942
+ if (typeof request?.url === "undefined") {
1943
+ throw new Error(
1944
+ "getPaginationVariables must be called with the Request object passed to your loader function"
1945
+ );
1946
+ }
1947
+ const { pageBy } = options;
1948
+ const searchParams = new URLSearchParams(new URL(request.url).search);
1949
+ const cursor = searchParams.get("cursor") ?? void 0;
1950
+ const direction = searchParams.get("direction") === "previous" ? "previous" : "next";
1951
+ const isPrevious = direction === "previous";
1952
+ const prevPage = {
1953
+ last: pageBy,
1954
+ startCursor: cursor ?? null
1955
+ };
1956
+ const nextPage = {
1957
+ first: pageBy,
1958
+ endCursor: cursor ?? null
1959
+ };
1960
+ const variables = isPrevious ? prevPage : nextPage;
1961
+ return variables;
1962
+ }
1963
+
1964
+ // src/customer/constants.ts
1965
+ var DEFAULT_CUSTOMER_API_VERSION = "2024-01";
1966
+ var USER_AGENT = `Shopify Hydrogen ${LIB_VERSION}`;
1967
+ var CUSTOMER_API_CLIENT_ID = "30243aa5-17c1-465a-8493-944bcc4e88aa";
1968
+ var CUSTOMER_ACCOUNT_SESSION_KEY = "customerAccount";
1969
+
1970
+ // src/customer/BadRequest.ts
1971
+ var BadRequest = class extends Response {
1972
+ constructor(message, helpMessage, headers) {
1973
+ if (helpMessage && true) {
1974
+ console.error("Customer Account API Error: " + helpMessage);
1975
+ }
1976
+ super(`Bad request: ${message}`, { status: 400, headers });
1977
+ }
1978
+ };
1979
+
1980
+ // src/customer/auth.helpers.ts
1981
+ var logSubRequestEvent = ({
1982
+ url,
1983
+ response,
1984
+ startTime,
1985
+ query,
1986
+ variables,
1987
+ ...debugInfo
1988
+ }) => {
1989
+ globalThis.__H2O_LOG_EVENT?.({
1990
+ ...debugInfo,
1991
+ eventType: "subrequest",
1992
+ url,
1993
+ startTime,
1994
+ graphql: query ? JSON.stringify({ query, variables, schema: "customer-account" }) : void 0,
1995
+ responseInit: {
1996
+ status: response.status || 0,
1997
+ statusText: response.statusText || "",
1998
+ headers: Array.from(response.headers.entries() || [])
1999
+ }
2000
+ });
2001
+ } ;
2002
+ function redirect(path, options = {}) {
2003
+ const headers = options.headers ? new Headers(options.headers) : new Headers({});
2004
+ headers.set("location", path);
2005
+ return new Response(null, { status: options.status || 302, headers });
2006
+ }
2007
+ async function refreshToken({
2008
+ session,
2009
+ customerAccountId,
2010
+ customerAccountUrl,
2011
+ origin,
2012
+ debugInfo
2013
+ }) {
2014
+ const newBody = new URLSearchParams();
2015
+ const customerAccount = session.get(CUSTOMER_ACCOUNT_SESSION_KEY);
2016
+ const refreshToken2 = customerAccount?.refreshToken;
2017
+ if (!refreshToken2)
2018
+ throw new BadRequest(
2019
+ "Unauthorized",
2020
+ "No refreshToken found in the session. Make sure your session is configured correctly and passed to `createCustomerAccountClient`."
2021
+ );
2022
+ newBody.append("grant_type", "refresh_token");
2023
+ newBody.append("refresh_token", refreshToken2);
2024
+ newBody.append("client_id", customerAccountId);
2025
+ const headers = {
2026
+ "content-type": "application/x-www-form-urlencoded",
2027
+ "User-Agent": USER_AGENT,
2028
+ Origin: origin
2029
+ };
2030
+ const startTime = (/* @__PURE__ */ new Date()).getTime();
2031
+ const url = `${customerAccountUrl}/auth/oauth/token`;
2032
+ const response = await fetch(url, {
2033
+ method: "POST",
2034
+ headers,
2035
+ body: newBody
2036
+ });
2037
+ logSubRequestEvent?.({
2038
+ displayName: "Customer Account API: access token refresh",
2039
+ url,
2040
+ startTime,
2041
+ response,
2042
+ ...debugInfo
2043
+ });
2044
+ if (!response.ok) {
2045
+ const text = await response.text();
2046
+ throw new Response(text, {
2047
+ status: response.status,
2048
+ headers: {
2049
+ "Content-Type": "text/html; charset=utf-8"
2050
+ }
2051
+ });
2052
+ }
2053
+ const { access_token, expires_in, id_token, refresh_token } = await response.json();
2054
+ const accessToken = await exchangeAccessToken(
2055
+ access_token,
2056
+ customerAccountId,
2057
+ customerAccountUrl,
2058
+ origin,
2059
+ debugInfo
2060
+ );
2061
+ session.set(CUSTOMER_ACCOUNT_SESSION_KEY, {
2062
+ accessToken,
2063
+ // Store the date in future the token expires, separated by two minutes
2064
+ expiresAt: new Date((/* @__PURE__ */ new Date()).getTime() + (expires_in - 120) * 1e3).getTime() + "",
2065
+ refreshToken: refresh_token,
2066
+ idToken: id_token
2067
+ });
2068
+ }
2069
+ function clearSession(session) {
2070
+ session.unset(CUSTOMER_ACCOUNT_SESSION_KEY);
2071
+ }
2072
+ async function checkExpires({
2073
+ locks,
2074
+ expiresAt,
2075
+ session,
2076
+ customerAccountId,
2077
+ customerAccountUrl,
2078
+ origin,
2079
+ debugInfo
2080
+ }) {
2081
+ if (parseInt(expiresAt, 10) - 1e3 < (/* @__PURE__ */ new Date()).getTime()) {
2082
+ try {
2083
+ if (!locks.refresh)
2084
+ locks.refresh = refreshToken({
2085
+ session,
2086
+ customerAccountId,
2087
+ customerAccountUrl,
2088
+ origin,
2089
+ debugInfo
2090
+ });
2091
+ await locks.refresh;
2092
+ delete locks.refresh;
2093
+ } catch (error) {
2094
+ clearSession(session);
2095
+ if (error && error.status !== 401) {
2096
+ throw error;
2097
+ } else {
2098
+ throw new BadRequest(
2099
+ "Unauthorized",
2100
+ "Login before querying the Customer Account API.",
2101
+ {
2102
+ "Set-Cookie": await session.commit()
2103
+ }
2104
+ );
2105
+ }
2106
+ }
2107
+ }
2108
+ }
2109
+ async function generateCodeVerifier() {
2110
+ const rando = generateRandomCode();
2111
+ return base64UrlEncode(rando);
2112
+ }
2113
+ async function generateCodeChallenge(codeVerifier) {
2114
+ const digestOp = await crypto.subtle.digest(
2115
+ { name: "SHA-256" },
2116
+ new TextEncoder().encode(codeVerifier)
2117
+ );
2118
+ const hash = convertBufferToString(digestOp);
2119
+ return base64UrlEncode(hash);
2120
+ }
2121
+ function generateRandomCode() {
2122
+ const array = new Uint8Array(32);
2123
+ crypto.getRandomValues(array);
2124
+ return String.fromCharCode.apply(null, Array.from(array));
2125
+ }
2126
+ function base64UrlEncode(str) {
2127
+ const base64 = btoa(str);
2128
+ return base64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
2129
+ }
2130
+ function convertBufferToString(hash) {
2131
+ const uintArray = new Uint8Array(hash);
2132
+ const numberArray = Array.from(uintArray);
2133
+ return String.fromCharCode(...numberArray);
2134
+ }
2135
+ async function generateState() {
2136
+ const timestamp = Date.now().toString();
2137
+ const randomString = Math.random().toString(36).substring(2);
2138
+ return timestamp + randomString;
2139
+ }
2140
+ async function exchangeAccessToken(authAccessToken, customerAccountId, customerAccountUrl, origin, debugInfo) {
2141
+ const clientId = customerAccountId;
2142
+ if (!authAccessToken)
2143
+ throw new BadRequest(
2144
+ "Unauthorized",
2145
+ "oAuth access token was not provided during token exchange."
2146
+ );
2147
+ const body = new URLSearchParams();
2148
+ body.append("grant_type", "urn:ietf:params:oauth:grant-type:token-exchange");
2149
+ body.append("client_id", clientId);
2150
+ body.append("audience", CUSTOMER_API_CLIENT_ID);
2151
+ body.append("subject_token", authAccessToken);
2152
+ body.append(
2153
+ "subject_token_type",
2154
+ "urn:ietf:params:oauth:token-type:access_token"
2155
+ );
2156
+ body.append("scopes", "https://api.customers.com/auth/customer.graphql");
2157
+ const headers = {
2158
+ "content-type": "application/x-www-form-urlencoded",
2159
+ "User-Agent": USER_AGENT,
2160
+ Origin: origin
2161
+ };
2162
+ const startTime = (/* @__PURE__ */ new Date()).getTime();
2163
+ const url = `${customerAccountUrl}/auth/oauth/token`;
2164
+ const response = await fetch(url, {
2165
+ method: "POST",
2166
+ headers,
2167
+ body
2168
+ });
2169
+ logSubRequestEvent?.({
2170
+ displayName: "Customer Account API: access token exchange",
2171
+ url,
2172
+ startTime,
2173
+ response,
2174
+ ...debugInfo
2175
+ });
2176
+ const data = await response.json();
2177
+ if (data.error) {
2178
+ throw new BadRequest(data.error_description);
2179
+ }
2180
+ return data.access_token;
2181
+ }
2182
+ function getNonce(token) {
2183
+ return decodeJwt(token).payload.nonce;
2184
+ }
2185
+ function decodeJwt(token) {
2186
+ const [header, payload, signature] = token.split(".");
2187
+ const decodedHeader = JSON.parse(atob(header));
2188
+ const decodedPayload = JSON.parse(atob(payload));
2189
+ return {
2190
+ header: decodedHeader,
2191
+ payload: decodedPayload,
2192
+ signature
2193
+ };
2194
+ }
2195
+
2196
+ // src/csp/nonce.ts
2197
+ function generateNonce() {
2198
+ return toHexString(randomUint8Array());
2199
+ }
2200
+ function randomUint8Array() {
2201
+ try {
2202
+ return crypto.getRandomValues(new Uint8Array(16));
2203
+ } catch (e) {
2204
+ return new Uint8Array(16).map(() => Math.random() * 255 | 0);
2205
+ }
2206
+ }
2207
+ function toHexString(byteArray) {
2208
+ return Array.from(byteArray, function(byte) {
2209
+ return ("0" + (byte & 255).toString(16)).slice(-2);
2210
+ }).join("");
2211
+ }
2212
+
2213
+ // src/customer/customer.ts
2214
+ var DEFAULT_LOGIN_URL = "/account/login";
2215
+ var DEFAULT_AUTH_URL = "/account/authorize";
2216
+ var DEFAULT_REDIRECT_PATH = "/account";
2217
+ function defaultAuthStatusHandler(request) {
2218
+ if (!request.url)
2219
+ return DEFAULT_LOGIN_URL;
2220
+ const { pathname } = new URL(request.url);
2221
+ const redirectTo = DEFAULT_LOGIN_URL + `?${new URLSearchParams({ return_to: pathname }).toString()}`;
2222
+ return redirect(redirectTo);
2223
+ }
2224
+ function createCustomerAccountClient({
2225
+ session,
2226
+ customerAccountId,
2227
+ customerAccountUrl,
2228
+ customerApiVersion = DEFAULT_CUSTOMER_API_VERSION,
2229
+ request,
2230
+ waitUntil,
2231
+ authUrl = DEFAULT_AUTH_URL,
2232
+ customAuthStatusHandler,
2233
+ logErrors = true
2234
+ }) {
2235
+ if (customerApiVersion !== DEFAULT_CUSTOMER_API_VERSION) {
2236
+ console.warn(
2237
+ `[h2:warn:createCustomerAccountClient] You are using Customer Account API version ${customerApiVersion} when this version of Hydrogen was built for ${DEFAULT_CUSTOMER_API_VERSION}.`
2238
+ );
2239
+ }
2240
+ if (!customerAccountId || !customerAccountUrl) {
2241
+ console.warn(
2242
+ "[h2:warn:createCustomerAccountClient] `customerAccountId` and `customerAccountUrl` need to be provided to use Customer Account API. Mock.shop doesn't automatically supply these variables.\nUse `npx shopify hydrogen env pull` to link your store credentials."
2243
+ );
2244
+ }
2245
+ if (!request?.url) {
2246
+ throw new Error(
2247
+ "[h2:error:createCustomerAccountClient] The request object does not contain a URL."
2248
+ );
2249
+ }
2250
+ const authStatusHandler = customAuthStatusHandler ? customAuthStatusHandler : () => defaultAuthStatusHandler(request);
2251
+ const requestUrl = new URL(request.url);
2252
+ const origin = requestUrl.protocol === "http:" ? requestUrl.origin.replace("http", "https") : requestUrl.origin;
2253
+ const redirectUri = authUrl.startsWith("/") ? origin + authUrl : authUrl;
2254
+ const customerAccountApiUrl = `${customerAccountUrl}/account/customer/api/${customerApiVersion}/graphql`;
2255
+ const locks = {};
2256
+ async function fetchCustomerAPI({
2257
+ query,
2258
+ type,
2259
+ variables = {}
2260
+ }) {
2261
+ const accessToken = await getAccessToken();
2262
+ if (!accessToken) {
2263
+ throw authStatusHandler();
2264
+ }
2265
+ const stackInfo = getCallerStackLine?.();
2266
+ const startTime = (/* @__PURE__ */ new Date()).getTime();
2267
+ const response = await fetch(customerAccountApiUrl, {
2268
+ method: "POST",
2269
+ headers: {
2270
+ "Content-Type": "application/json",
2271
+ "User-Agent": USER_AGENT,
2272
+ Origin: origin,
2273
+ Authorization: accessToken
2274
+ },
2275
+ body: JSON.stringify({ query, variables })
2276
+ });
2277
+ logSubRequestEvent?.({
2278
+ url: customerAccountApiUrl,
2279
+ startTime,
2280
+ response,
2281
+ waitUntil,
2282
+ stackInfo,
2283
+ query,
2284
+ variables,
2285
+ ...getDebugHeaders(request)
2286
+ });
2287
+ const body = await response.text();
2288
+ const errorOptions = {
2289
+ url: customerAccountApiUrl,
2290
+ response,
2291
+ type,
2292
+ query,
2293
+ queryVariables: variables,
2294
+ errors: void 0,
2295
+ client: "customer"
2296
+ };
2297
+ if (!response.ok) {
2298
+ if (response.status === 401) {
2299
+ clearSession(session);
2300
+ const authFailResponse = authStatusHandler();
2301
+ if (authFailResponse instanceof Response) {
2302
+ authFailResponse.headers.set("Set-Cookie", await session.commit());
2303
+ }
2304
+ throw authFailResponse;
2305
+ }
2306
+ let errors;
2307
+ try {
2308
+ errors = parseJSON(body);
2309
+ } catch (_e) {
2310
+ errors = [{ message: body }];
2311
+ }
2312
+ throwErrorWithGqlLink({ ...errorOptions, errors });
2313
+ }
2314
+ try {
2315
+ const APIresponse = parseJSON(body);
2316
+ const { errors } = APIresponse;
2317
+ const gqlErrors = errors?.map(
2318
+ ({ message, ...rest }) => new GraphQLError(message, {
2319
+ ...rest,
2320
+ clientOperation: `customerAccount.${errorOptions.type}`,
2321
+ requestId: response.headers.get("x-request-id"),
2322
+ queryVariables: variables,
2323
+ query
2324
+ })
2325
+ );
2326
+ return { ...APIresponse, ...errors && { errors: gqlErrors } };
2327
+ } catch (e) {
2328
+ throwErrorWithGqlLink({ ...errorOptions, errors: [{ message: body }] });
2329
+ }
2330
+ }
2331
+ async function isLoggedIn() {
2332
+ const customerAccount = session.get(CUSTOMER_ACCOUNT_SESSION_KEY);
2333
+ const accessToken = customerAccount?.accessToken;
2334
+ const expiresAt = customerAccount?.expiresAt;
2335
+ if (!accessToken || !expiresAt)
2336
+ return false;
2337
+ const stackInfo = getCallerStackLine?.();
2338
+ try {
2339
+ await checkExpires({
2340
+ locks,
2341
+ expiresAt,
2342
+ session,
2343
+ customerAccountId,
2344
+ customerAccountUrl,
2345
+ origin,
2346
+ debugInfo: {
2347
+ waitUntil,
2348
+ stackInfo,
2349
+ ...getDebugHeaders(request)
2350
+ }
2351
+ });
2352
+ } catch {
2353
+ return false;
2354
+ }
2355
+ return true;
2356
+ }
2357
+ async function handleAuthStatus() {
2358
+ if (!await isLoggedIn()) {
2359
+ throw authStatusHandler();
2360
+ }
2361
+ }
2362
+ async function getAccessToken() {
2363
+ const hasAccessToken = await isLoggedIn();
2364
+ if (hasAccessToken)
2365
+ return session.get(CUSTOMER_ACCOUNT_SESSION_KEY)?.accessToken;
2366
+ }
2367
+ return {
2368
+ login: async () => {
2369
+ const loginUrl = new URL(customerAccountUrl + "/auth/oauth/authorize");
2370
+ const state = await generateState();
2371
+ const nonce = await generateNonce();
2372
+ loginUrl.searchParams.set("client_id", customerAccountId);
2373
+ loginUrl.searchParams.set("scope", "openid email");
2374
+ loginUrl.searchParams.append("response_type", "code");
2375
+ loginUrl.searchParams.append("redirect_uri", redirectUri);
2376
+ loginUrl.searchParams.set(
2377
+ "scope",
2378
+ "openid email https://api.customers.com/auth/customer.graphql"
2379
+ );
2380
+ loginUrl.searchParams.append("state", state);
2381
+ loginUrl.searchParams.append("nonce", nonce);
2382
+ const verifier = await generateCodeVerifier();
2383
+ const challenge = await generateCodeChallenge(verifier);
2384
+ session.set(CUSTOMER_ACCOUNT_SESSION_KEY, {
2385
+ ...session.get(CUSTOMER_ACCOUNT_SESSION_KEY),
2386
+ codeVerifier: verifier,
2387
+ state,
2388
+ nonce,
2389
+ redirectPath: getRedirectUrl(request.url) || getHeader(request, "Referer") || DEFAULT_REDIRECT_PATH
2390
+ });
2391
+ loginUrl.searchParams.append("code_challenge", challenge);
2392
+ loginUrl.searchParams.append("code_challenge_method", "S256");
2393
+ return redirect(loginUrl.toString(), {
2394
+ headers: {
2395
+ "Set-Cookie": await session.commit()
2396
+ }
2397
+ });
2398
+ },
2399
+ logout: async () => {
2400
+ const idToken = session.get(CUSTOMER_ACCOUNT_SESSION_KEY)?.idToken;
2401
+ clearSession(session);
2402
+ return redirect(
2403
+ `${customerAccountUrl}/auth/logout?id_token_hint=${idToken}`,
2404
+ {
2405
+ status: 302,
2406
+ headers: {
2407
+ "Set-Cookie": await session.commit()
2408
+ }
2409
+ }
2410
+ );
2411
+ },
2412
+ isLoggedIn,
2413
+ handleAuthStatus,
2414
+ getAccessToken,
2415
+ getApiUrl: () => customerAccountApiUrl,
2416
+ mutate(mutation, options) {
2417
+ mutation = minifyQuery(mutation);
2418
+ assertMutation(mutation, "customer.mutate");
2419
+ return withSyncStack(
2420
+ fetchCustomerAPI({ query: mutation, type: "mutation", ...options }),
2421
+ { logErrors }
2422
+ );
2423
+ },
2424
+ query(query, options) {
2425
+ query = minifyQuery(query);
2426
+ assertQuery(query, "customer.query");
2427
+ return withSyncStack(
2428
+ fetchCustomerAPI({ query, type: "query", ...options }),
2429
+ { logErrors }
2430
+ );
2431
+ },
2432
+ authorize: async () => {
2433
+ const code = requestUrl.searchParams.get("code");
2434
+ const state = requestUrl.searchParams.get("state");
2435
+ if (!code || !state) {
2436
+ clearSession(session);
2437
+ throw new BadRequest(
2438
+ "Unauthorized",
2439
+ "No code or state parameter found in the redirect URL.",
2440
+ {
2441
+ "Set-Cookie": await session.commit()
2442
+ }
2443
+ );
2444
+ }
2445
+ if (session.get(CUSTOMER_ACCOUNT_SESSION_KEY)?.state !== state) {
2446
+ clearSession(session);
2447
+ throw new BadRequest(
2448
+ "Unauthorized",
2449
+ "The session state does not match the state parameter. Make sure that the session is configured correctly and passed to `createCustomerAccountClient`.",
2450
+ {
2451
+ "Set-Cookie": await session.commit()
2452
+ }
2453
+ );
2454
+ }
2455
+ const clientId = customerAccountId;
2456
+ const body = new URLSearchParams();
2457
+ body.append("grant_type", "authorization_code");
2458
+ body.append("client_id", clientId);
2459
+ body.append("redirect_uri", redirectUri);
2460
+ body.append("code", code);
2461
+ const codeVerifier = session.get(
2462
+ CUSTOMER_ACCOUNT_SESSION_KEY
2463
+ )?.codeVerifier;
2464
+ if (!codeVerifier)
2465
+ throw new BadRequest(
2466
+ "Unauthorized",
2467
+ "No code verifier found in the session. Make sure that the session is configured correctly and passed to `createCustomerAccountClient`."
2468
+ );
2469
+ body.append("code_verifier", codeVerifier);
2470
+ const headers = {
2471
+ "content-type": "application/x-www-form-urlencoded",
2472
+ "User-Agent": USER_AGENT,
2473
+ Origin: origin
2474
+ };
2475
+ const stackInfo = getCallerStackLine?.();
2476
+ const startTime = (/* @__PURE__ */ new Date()).getTime();
2477
+ const url = `${customerAccountUrl}/auth/oauth/token`;
2478
+ const response = await fetch(url, {
2479
+ method: "POST",
2480
+ headers,
2481
+ body
2482
+ });
2483
+ logSubRequestEvent?.({
2484
+ url,
2485
+ displayName: "Customer Account API: authorize",
2486
+ startTime,
2487
+ response,
2488
+ waitUntil,
2489
+ stackInfo,
2490
+ ...getDebugHeaders(request)
2491
+ });
2492
+ if (!response.ok) {
2493
+ throw new Response(await response.text(), {
2494
+ status: response.status,
2495
+ headers: {
2496
+ "Content-Type": "text/html; charset=utf-8"
2497
+ }
2498
+ });
2499
+ }
2500
+ const { access_token, expires_in, id_token, refresh_token } = await response.json();
2501
+ const sessionNonce = session.get(CUSTOMER_ACCOUNT_SESSION_KEY)?.nonce;
2502
+ const responseNonce = await getNonce(id_token);
2503
+ if (sessionNonce !== responseNonce) {
2504
+ throw new BadRequest(
2505
+ "Unauthorized",
2506
+ `Returned nonce does not match: ${sessionNonce} !== ${responseNonce}`
2507
+ );
2508
+ }
2509
+ const customerAccessToken = await exchangeAccessToken(
2510
+ access_token,
2511
+ customerAccountId,
2512
+ customerAccountUrl,
2513
+ origin,
2514
+ {
2515
+ waitUntil,
2516
+ stackInfo,
2517
+ ...getDebugHeaders(request)
2518
+ }
2519
+ );
2520
+ const redirectPath = session.get(
2521
+ CUSTOMER_ACCOUNT_SESSION_KEY
2522
+ )?.redirectPath;
2523
+ session.set(CUSTOMER_ACCOUNT_SESSION_KEY, {
2524
+ accessToken: customerAccessToken,
2525
+ expiresAt: new Date(
2526
+ (/* @__PURE__ */ new Date()).getTime() + (expires_in - 120) * 1e3
2527
+ ).getTime() + "",
2528
+ refreshToken: refresh_token,
2529
+ idToken: id_token,
2530
+ redirectPath: void 0
2531
+ });
2532
+ return redirect(redirectPath || DEFAULT_REDIRECT_PATH, {
2533
+ headers: {
2534
+ "Set-Cookie": await session.commit()
2535
+ }
2536
+ });
2537
+ }
2538
+ };
2539
+ }
2540
+
2541
+ // src/changelogHandler.ts
2542
+ var DEFAULT_GITHUB_CHANGELOG_URL = "https://raw.githubusercontent.com/Shopify/hydrogen/main/docs/changelog.json";
2543
+ async function changelogHandler({
2544
+ request,
2545
+ changelogUrl
2546
+ }) {
2547
+ new URL(request.url).searchParams;
2548
+ const GITHUB_CHANGELOG_URL = changelogUrl || DEFAULT_GITHUB_CHANGELOG_URL;
2549
+ return fetch(GITHUB_CHANGELOG_URL);
2550
+ }
2551
+ var INPUT_NAME = "cartFormInput";
2552
+ function CartForm({
2553
+ children,
2554
+ action,
2555
+ inputs,
2556
+ route,
2557
+ fetcherKey
2558
+ }) {
2559
+ const fetcher = useFetcher({ key: fetcherKey });
2560
+ return /* @__PURE__ */ jsxs(fetcher.Form, { action: route || "", method: "post", children: [
2561
+ (action || inputs) && /* @__PURE__ */ jsx(
2562
+ "input",
2563
+ {
2564
+ type: "hidden",
2565
+ name: INPUT_NAME,
2566
+ value: JSON.stringify({ action, inputs })
2567
+ }
2568
+ ),
2569
+ typeof children === "function" ? children(fetcher) : children
2570
+ ] });
2571
+ }
2572
+ CartForm.INPUT_NAME = INPUT_NAME;
2573
+ CartForm.ACTIONS = {
2574
+ AttributesUpdateInput: "AttributesUpdateInput",
2575
+ BuyerIdentityUpdate: "BuyerIdentityUpdate",
2576
+ Create: "Create",
2577
+ DiscountCodesUpdate: "DiscountCodesUpdate",
2578
+ LinesAdd: "LinesAdd",
2579
+ LinesRemove: "LinesRemove",
2580
+ LinesUpdate: "LinesUpdate",
2581
+ NoteUpdate: "NoteUpdate",
2582
+ SelectedDeliveryOptionsUpdate: "SelectedDeliveryOptionsUpdate",
2583
+ MetafieldsSet: "MetafieldsSet",
2584
+ MetafieldDelete: "MetafieldDelete"
2585
+ };
2586
+ function getFormInput(formData) {
2587
+ const data = {};
2588
+ for (const pair of formData.entries()) {
2589
+ const key = pair[0];
2590
+ const values = formData.getAll(key);
2591
+ data[key] = values.length > 1 ? values : pair[1];
2592
+ }
2593
+ const { cartFormInput, ...otherData } = data;
2594
+ const { action, inputs } = cartFormInput ? JSON.parse(String(cartFormInput)) : {};
2595
+ return {
2596
+ action,
2597
+ inputs: {
2598
+ ...inputs,
2599
+ ...otherData
2600
+ }
2601
+ };
2602
+ }
2603
+ CartForm.getFormInput = getFormInput;
2604
+
2605
+ // src/cart/queries/cart-fragments.ts
2606
+ var USER_ERROR_FRAGMENT = `#graphql
2607
+ fragment CartApiError on CartUserError {
2608
+ message
2609
+ field
2610
+ code
2611
+ }
2612
+ `;
2613
+ var MINIMAL_CART_FRAGMENT = `#graphql
2614
+ fragment CartApiMutation on Cart {
2615
+ id
2616
+ totalQuantity
2617
+ }
2618
+ `;
2619
+
2620
+ // src/cart/queries/cartCreateDefault.ts
2621
+ function cartCreateDefault(options) {
2622
+ return async (input, optionalParams) => {
2623
+ const { cartId, ...restOfOptionalParams } = optionalParams || {};
2624
+ const { cartCreate, errors } = await options.storefront.mutate(CART_CREATE_MUTATION(options.cartFragment), {
2625
+ variables: {
2626
+ input,
2627
+ ...restOfOptionalParams
2628
+ }
2629
+ });
2630
+ return formatAPIResult(cartCreate, errors);
2631
+ };
2632
+ }
2633
+ var CART_CREATE_MUTATION = (cartFragment = MINIMAL_CART_FRAGMENT) => `#graphql
2634
+ mutation cartCreate(
2635
+ $input: CartInput!
2636
+ $country: CountryCode = ZZ
2637
+ $language: LanguageCode
2638
+ ) @inContext(country: $country, language: $language) {
2639
+ cartCreate(input: $input) {
2640
+ cart {
2641
+ ...CartApiMutation
2642
+ checkoutUrl
2643
+ }
2644
+ userErrors {
2645
+ ...CartApiError
2646
+ }
2647
+ }
2648
+ }
2649
+ ${cartFragment}
2650
+ ${USER_ERROR_FRAGMENT}
2651
+ `;
2652
+
2653
+ // src/cart/queries/cartGetDefault.ts
2654
+ function cartGetDefault({
2655
+ storefront,
2656
+ customerAccount,
2657
+ getCartId,
2658
+ cartFragment
2659
+ }) {
2660
+ return async (cartInput) => {
2661
+ const cartId = getCartId();
2662
+ if (!cartId)
2663
+ return null;
2664
+ const [isCustomerLoggedIn, { cart, errors }] = await Promise.all([
2665
+ customerAccount ? customerAccount.isLoggedIn() : false,
2666
+ storefront.query(CART_QUERY(cartFragment), {
2667
+ variables: {
2668
+ cartId,
2669
+ ...cartInput
2670
+ },
2671
+ cache: storefront.CacheNone()
2672
+ })
2673
+ ]);
2674
+ return formatAPIResult(
2675
+ addCustomerLoggedInParam(isCustomerLoggedIn, cart),
2676
+ errors
2677
+ );
2678
+ };
2679
+ }
2680
+ function addCustomerLoggedInParam(isCustomerLoggedIn, cart) {
2681
+ if (isCustomerLoggedIn && cart && cart.checkoutUrl) {
2682
+ const finalCheckoutUrl = new URL(cart.checkoutUrl);
2683
+ finalCheckoutUrl.searchParams.set("logged_in", "true");
2684
+ cart.checkoutUrl = finalCheckoutUrl.toString();
2685
+ }
2686
+ return cart;
2687
+ }
2688
+ var CART_QUERY = (cartFragment = DEFAULT_CART_FRAGMENT) => `#graphql
2689
+ query CartQuery(
2690
+ $cartId: ID!
2691
+ $numCartLines: Int = 100
2692
+ $country: CountryCode = ZZ
2693
+ $language: LanguageCode
2694
+ ) @inContext(country: $country, language: $language) {
2695
+ cart(id: $cartId) {
2696
+ ...CartApiQuery
2697
+ }
2698
+ }
2699
+
2700
+ ${cartFragment}
2701
+ `;
2702
+ var DEFAULT_CART_FRAGMENT = `#graphql
2703
+ fragment CartApiQuery on Cart {
2704
+ id
2705
+ checkoutUrl
2706
+ totalQuantity
2707
+ buyerIdentity {
2708
+ countryCode
2709
+ customer {
2710
+ id
2711
+ email
2712
+ firstName
2713
+ lastName
2714
+ displayName
2715
+ }
2716
+ email
2717
+ phone
2718
+ }
2719
+ lines(first: $numCartLines) {
2720
+ edges {
2721
+ node {
2722
+ id
2723
+ quantity
2724
+ attributes {
2725
+ key
2726
+ value
2727
+ }
2728
+ cost {
2729
+ totalAmount {
2730
+ amount
2731
+ currencyCode
2732
+ }
2733
+ amountPerQuantity {
2734
+ amount
2735
+ currencyCode
2736
+ }
2737
+ compareAtAmountPerQuantity {
2738
+ amount
2739
+ currencyCode
2740
+ }
2741
+ }
2742
+ merchandise {
2743
+ ... on ProductVariant {
2744
+ id
2745
+ availableForSale
2746
+ compareAtPrice {
2747
+ ...CartApiMoney
2748
+ }
2749
+ price {
2750
+ ...CartApiMoney
2751
+ }
2752
+ requiresShipping
2753
+ title
2754
+ image {
2755
+ ...CartApiImage
2756
+ }
2757
+ product {
2758
+ handle
2759
+ title
2760
+ id
2761
+ }
2762
+ selectedOptions {
2763
+ name
2764
+ value
2765
+ }
2766
+ }
2767
+ }
2768
+ }
2769
+ }
2770
+ }
2771
+ cost {
2772
+ subtotalAmount {
2773
+ ...CartApiMoney
2774
+ }
2775
+ totalAmount {
2776
+ ...CartApiMoney
2777
+ }
2778
+ totalDutyAmount {
2779
+ ...CartApiMoney
2780
+ }
2781
+ totalTaxAmount {
2782
+ ...CartApiMoney
2783
+ }
2784
+ }
2785
+ note
2786
+ attributes {
2787
+ key
2788
+ value
2789
+ }
2790
+ discountCodes {
2791
+ applicable
2792
+ code
2793
+ }
2794
+ }
2795
+
2796
+ fragment CartApiMoney on MoneyV2 {
2797
+ currencyCode
2798
+ amount
2799
+ }
2800
+
2801
+ fragment CartApiImage on Image {
2802
+ id
2803
+ url
2804
+ altText
2805
+ width
2806
+ height
2807
+ }
2808
+ `;
2809
+
2810
+ // src/cart/queries/cartLinesAddDefault.ts
2811
+ function cartLinesAddDefault(options) {
2812
+ return async (lines, optionalParams) => {
2813
+ const { cartLinesAdd, errors } = await options.storefront.mutate(CART_LINES_ADD_MUTATION(options.cartFragment), {
2814
+ variables: {
2815
+ cartId: options.getCartId(),
2816
+ lines,
2817
+ ...optionalParams
2818
+ }
2819
+ });
2820
+ return formatAPIResult(cartLinesAdd, errors);
2821
+ };
2822
+ }
2823
+ var CART_LINES_ADD_MUTATION = (cartFragment = MINIMAL_CART_FRAGMENT) => `#graphql
2824
+ mutation cartLinesAdd(
2825
+ $cartId: ID!
2826
+ $lines: [CartLineInput!]!
2827
+ $country: CountryCode = ZZ
2828
+ $language: LanguageCode
2829
+ ) @inContext(country: $country, language: $language) {
2830
+ cartLinesAdd(cartId: $cartId, lines: $lines) {
2831
+ cart {
2832
+ ...CartApiMutation
2833
+ }
2834
+ userErrors {
2835
+ ...CartApiError
2836
+ }
2837
+ }
2838
+ }
2839
+
2840
+ ${cartFragment}
2841
+ ${USER_ERROR_FRAGMENT}
2842
+ `;
2843
+
2844
+ // src/cart/queries/cartLinesUpdateDefault.ts
2845
+ function cartLinesUpdateDefault(options) {
2846
+ return async (lines, optionalParams) => {
2847
+ const { cartLinesUpdate, errors } = await options.storefront.mutate(CART_LINES_UPDATE_MUTATION(options.cartFragment), {
2848
+ variables: {
2849
+ cartId: options.getCartId(),
2850
+ lines,
2851
+ ...optionalParams
2852
+ }
2853
+ });
2854
+ return formatAPIResult(cartLinesUpdate, errors);
2855
+ };
2856
+ }
2857
+ var CART_LINES_UPDATE_MUTATION = (cartFragment = MINIMAL_CART_FRAGMENT) => `#graphql
2858
+ mutation cartLinesUpdate(
2859
+ $cartId: ID!
2860
+ $lines: [CartLineUpdateInput!]!
2861
+ $language: LanguageCode
2862
+ $country: CountryCode
2863
+ ) @inContext(country: $country, language: $language) {
2864
+ cartLinesUpdate(cartId: $cartId, lines: $lines) {
2865
+ cart {
2866
+ ...CartApiMutation
2867
+ }
2868
+ userErrors {
2869
+ ...CartApiError
2870
+ }
2871
+ }
2872
+ }
2873
+
2874
+ ${cartFragment}
2875
+ ${USER_ERROR_FRAGMENT}
2876
+ `;
2877
+
2878
+ // src/cart/queries/cartLinesRemoveDefault.ts
2879
+ function cartLinesRemoveDefault(options) {
2880
+ return async (lineIds, optionalParams) => {
2881
+ const { cartLinesRemove, errors } = await options.storefront.mutate(CART_LINES_REMOVE_MUTATION(options.cartFragment), {
2882
+ variables: {
2883
+ cartId: options.getCartId(),
2884
+ lineIds,
2885
+ ...optionalParams
2886
+ }
2887
+ });
2888
+ return formatAPIResult(cartLinesRemove, errors);
2889
+ };
2890
+ }
2891
+ var CART_LINES_REMOVE_MUTATION = (cartFragment = MINIMAL_CART_FRAGMENT) => `#graphql
2892
+ mutation cartLinesRemove(
2893
+ $cartId: ID!
2894
+ $lineIds: [ID!]!
2895
+ $language: LanguageCode
2896
+ $country: CountryCode
2897
+ ) @inContext(country: $country, language: $language) {
2898
+ cartLinesRemove(cartId: $cartId, lineIds: $lineIds) {
2899
+ cart {
2900
+ ...CartApiMutation
2901
+ }
2902
+ userErrors {
2903
+ ...CartApiError
2904
+ }
2905
+ }
2906
+ }
2907
+
2908
+ ${cartFragment}
2909
+ ${USER_ERROR_FRAGMENT}
2910
+ `;
2911
+
2912
+ // src/cart/queries/cartDiscountCodesUpdateDefault.ts
2913
+ function cartDiscountCodesUpdateDefault(options) {
2914
+ return async (discountCodes, optionalParams) => {
2915
+ const uniqueCodes = discountCodes.filter((value, index, array) => {
2916
+ return array.indexOf(value) === index;
2917
+ });
2918
+ const { cartDiscountCodesUpdate, errors } = await options.storefront.mutate(CART_DISCOUNT_CODE_UPDATE_MUTATION(options.cartFragment), {
2919
+ variables: {
2920
+ cartId: options.getCartId(),
2921
+ discountCodes: uniqueCodes,
2922
+ ...optionalParams
2923
+ }
2924
+ });
2925
+ return formatAPIResult(cartDiscountCodesUpdate, errors);
2926
+ };
2927
+ }
2928
+ var CART_DISCOUNT_CODE_UPDATE_MUTATION = (cartFragment = MINIMAL_CART_FRAGMENT) => `#graphql
2929
+ mutation cartDiscountCodesUpdate(
2930
+ $cartId: ID!
2931
+ $discountCodes: [String!]
2932
+ $language: LanguageCode
2933
+ $country: CountryCode
2934
+ ) @inContext(country: $country, language: $language) {
2935
+ cartDiscountCodesUpdate(cartId: $cartId, discountCodes: $discountCodes) {
2936
+ cart {
2937
+ ...CartApiMutation
2938
+ }
2939
+ userErrors {
2940
+ ...CartApiError
2941
+ }
2942
+ }
2943
+ }
2944
+ ${cartFragment}
2945
+ ${USER_ERROR_FRAGMENT}
2946
+ `;
2947
+
2948
+ // src/cart/queries/cartBuyerIdentityUpdateDefault.ts
2949
+ function cartBuyerIdentityUpdateDefault(options) {
2950
+ return async (buyerIdentity, optionalParams) => {
2951
+ const { cartBuyerIdentityUpdate, errors } = await options.storefront.mutate(CART_BUYER_IDENTITY_UPDATE_MUTATION(options.cartFragment), {
2952
+ variables: {
2953
+ cartId: options.getCartId(),
2954
+ buyerIdentity,
2955
+ ...optionalParams
2956
+ }
2957
+ });
2958
+ return formatAPIResult(cartBuyerIdentityUpdate, errors);
2959
+ };
2960
+ }
2961
+ var CART_BUYER_IDENTITY_UPDATE_MUTATION = (cartFragment = MINIMAL_CART_FRAGMENT) => `#graphql
2962
+ mutation cartBuyerIdentityUpdate(
2963
+ $cartId: ID!
2964
+ $buyerIdentity: CartBuyerIdentityInput!
2965
+ $language: LanguageCode
2966
+ $country: CountryCode
2967
+ ) @inContext(country: $country, language: $language) {
2968
+ cartBuyerIdentityUpdate(cartId: $cartId, buyerIdentity: $buyerIdentity) {
2969
+ cart {
2970
+ ...CartApiMutation
2971
+ }
2972
+ userErrors {
2973
+ ...CartApiError
2974
+ }
2975
+ }
2976
+ }
2977
+ ${cartFragment}
2978
+ ${USER_ERROR_FRAGMENT}
2979
+ `;
2980
+
2981
+ // src/cart/queries/cartNoteUpdateDefault.ts
2982
+ function cartNoteUpdateDefault(options) {
2983
+ return async (note, optionalParams) => {
2984
+ const { cartNoteUpdate, errors } = await options.storefront.mutate(CART_NOTE_UPDATE_MUTATION(options.cartFragment), {
2985
+ variables: {
2986
+ cartId: options.getCartId(),
2987
+ note,
2988
+ ...optionalParams
2989
+ }
2990
+ });
2991
+ return formatAPIResult(cartNoteUpdate, errors);
2992
+ };
2993
+ }
2994
+ var CART_NOTE_UPDATE_MUTATION = (cartFragment = MINIMAL_CART_FRAGMENT) => `#graphql
2995
+ mutation cartNoteUpdate(
2996
+ $cartId: ID!
2997
+ $note: String
2998
+ $language: LanguageCode
2999
+ $country: CountryCode
3000
+ ) @inContext(country: $country, language: $language) {
3001
+ cartNoteUpdate(cartId: $cartId, note: $note) {
3002
+ cart {
3003
+ ...CartApiMutation
3004
+ }
3005
+ userErrors {
3006
+ ...CartApiError
3007
+ }
3008
+ }
3009
+ }
3010
+ ${cartFragment}
3011
+ ${USER_ERROR_FRAGMENT}
3012
+ `;
3013
+
3014
+ // src/cart/queries/cartSelectedDeliveryOptionsUpdateDefault.ts
3015
+ function cartSelectedDeliveryOptionsUpdateDefault(options) {
3016
+ return async (selectedDeliveryOptions, optionalParams) => {
3017
+ const { cartSelectedDeliveryOptionsUpdate, errors } = await options.storefront.mutate(CART_SELECTED_DELIVERY_OPTIONS_UPDATE_MUTATION(options.cartFragment), {
3018
+ variables: {
3019
+ cartId: options.getCartId(),
3020
+ selectedDeliveryOptions,
3021
+ ...optionalParams
3022
+ }
3023
+ });
3024
+ return formatAPIResult(cartSelectedDeliveryOptionsUpdate, errors);
3025
+ };
3026
+ }
3027
+ var CART_SELECTED_DELIVERY_OPTIONS_UPDATE_MUTATION = (cartFragment = MINIMAL_CART_FRAGMENT) => `#graphql
3028
+ mutation cartSelectedDeliveryOptionsUpdate(
3029
+ $cartId: ID!
3030
+ $selectedDeliveryOptions: [CartSelectedDeliveryOptionInput!]!
3031
+ $language: LanguageCode
3032
+ $country: CountryCode
3033
+ ) @inContext(country: $country, language: $language) {
3034
+ cartSelectedDeliveryOptionsUpdate(cartId: $cartId, selectedDeliveryOptions: $selectedDeliveryOptions) {
3035
+ cart {
3036
+ ...CartApiMutation
3037
+ }
3038
+ userErrors {
3039
+ ...CartApiError
3040
+ }
3041
+ }
3042
+ }
3043
+ ${cartFragment}
3044
+ ${USER_ERROR_FRAGMENT}
3045
+ `;
3046
+
3047
+ // src/cart/queries/cartAttributesUpdateDefault.ts
3048
+ function cartAttributesUpdateDefault(options) {
3049
+ return async (attributes, optionalParams) => {
3050
+ const { cartAttributesUpdate, errors } = await options.storefront.mutate(CART_ATTRIBUTES_UPDATE_MUTATION(options.cartFragment), {
3051
+ variables: {
3052
+ cartId: optionalParams?.cartId || options.getCartId(),
3053
+ attributes
3054
+ }
3055
+ });
3056
+ return formatAPIResult(cartAttributesUpdate, errors);
3057
+ };
3058
+ }
3059
+ var CART_ATTRIBUTES_UPDATE_MUTATION = (cartFragment = MINIMAL_CART_FRAGMENT) => `#graphql
3060
+ mutation cartAttributesUpdate(
3061
+ $cartId: ID!
3062
+ $attributes: [AttributeInput!]!
3063
+ ) {
3064
+ cartAttributesUpdate(cartId: $cartId, attributes: $attributes) {
3065
+ cart {
3066
+ ...CartApiMutation
3067
+ }
3068
+ userErrors {
3069
+ ...CartApiError
3070
+ }
3071
+ }
3072
+ }
3073
+ ${cartFragment}
3074
+ ${USER_ERROR_FRAGMENT}
3075
+ `;
3076
+
3077
+ // src/cart/queries/cartMetafieldsSetDefault.ts
3078
+ function cartMetafieldsSetDefault(options) {
3079
+ return async (metafields, optionalParams) => {
3080
+ const ownerId = optionalParams?.cartId || options.getCartId();
3081
+ const metafieldsWithOwnerId = metafields.map(
3082
+ (metafield) => ({
3083
+ ...metafield,
3084
+ ownerId
3085
+ })
3086
+ );
3087
+ const { cartMetafieldsSet, errors } = await options.storefront.mutate(CART_METAFIELD_SET_MUTATION(), {
3088
+ variables: { metafields: metafieldsWithOwnerId }
3089
+ });
3090
+ return formatAPIResult(
3091
+ {
3092
+ cart: {
3093
+ id: ownerId
3094
+ },
3095
+ ...cartMetafieldsSet
3096
+ },
3097
+ errors
3098
+ );
3099
+ };
3100
+ }
3101
+ var CART_METAFIELD_SET_MUTATION = () => `#graphql
3102
+ mutation cartMetafieldsSet(
3103
+ $metafields: [CartMetafieldsSetInput!]!
3104
+ $language: LanguageCode
3105
+ $country: CountryCode
3106
+ ) @inContext(country: $country, language: $language) {
3107
+ cartMetafieldsSet(metafields: $metafields) {
3108
+ userErrors {
3109
+ code
3110
+ elementIndex
3111
+ field
3112
+ message
3113
+ }
3114
+ }
3115
+ }
3116
+ `;
3117
+
3118
+ // src/cart/queries/cartMetafieldDeleteDefault.ts
3119
+ function cartMetafieldDeleteDefault(options) {
3120
+ return async (key, optionalParams) => {
3121
+ const ownerId = optionalParams?.cartId || options.getCartId();
3122
+ const { cartMetafieldDelete, errors } = await options.storefront.mutate(CART_METAFIELD_DELETE_MUTATION(), {
3123
+ variables: {
3124
+ input: {
3125
+ ownerId,
3126
+ key
3127
+ }
3128
+ }
3129
+ });
3130
+ return formatAPIResult(
3131
+ {
3132
+ cart: {
3133
+ id: ownerId
3134
+ },
3135
+ ...cartMetafieldDelete
3136
+ },
3137
+ errors
3138
+ );
3139
+ };
3140
+ }
3141
+ var CART_METAFIELD_DELETE_MUTATION = () => `#graphql
3142
+ mutation cartMetafieldDelete(
3143
+ $input: CartMetafieldDeleteInput!
3144
+ ) {
3145
+ cartMetafieldDelete(input: $input) {
3146
+ userErrors {
3147
+ code
3148
+ field
3149
+ message
3150
+ }
3151
+ }
3152
+ }
3153
+ `;
3154
+
3155
+ // ../../node_modules/worktop/cookie/index.mjs
3156
+ var g = /* @__PURE__ */ new Set([
3157
+ "domain",
3158
+ "path",
3159
+ "max-age",
3160
+ "expires",
3161
+ "samesite",
3162
+ "secure",
3163
+ "httponly"
3164
+ ]);
3165
+ function u(a) {
3166
+ let r = {}, e, t, n = 0, m = a.split(/;\s*/g), s, i;
3167
+ for (; n < m.length; n++)
3168
+ if (t = m[n], e = t.indexOf("="), ~e) {
3169
+ if (s = t.substring(0, e++).trim(), i = t.substring(e).trim(), i[0] === '"' && (i = i.substring(1, i.length - 1)), ~i.indexOf("%"))
3170
+ try {
3171
+ i = decodeURIComponent(i);
3172
+ } catch (f) {
3173
+ }
3174
+ g.has(t = s.toLowerCase()) ? t === "expires" ? r.expires = new Date(i) : t === "max-age" ? r.maxage = +i : r[t] = i : r[s] = i;
3175
+ } else
3176
+ (s = t.trim().toLowerCase()) && (s === "httponly" || s === "secure") && (r[s] = true);
3177
+ return r;
3178
+ }
3179
+ function l(a, r, e = {}) {
3180
+ let t = a + "=" + encodeURIComponent(r);
3181
+ return e.expires && (t += "; Expires=" + new Date(e.expires).toUTCString()), e.maxage != null && e.maxage >= 0 && (t += "; Max-Age=" + (e.maxage | 0)), e.domain && (t += "; Domain=" + e.domain), e.path && (t += "; Path=" + e.path), e.samesite && (t += "; SameSite=" + e.samesite), (e.secure || e.samesite === "None") && (t += "; Secure"), e.httponly && (t += "; HttpOnly"), t;
3182
+ }
3183
+
3184
+ // src/cart/cartGetIdDefault.ts
3185
+ var cartGetIdDefault = (requestHeaders) => {
3186
+ const cookies = u(requestHeaders.get("Cookie") || "");
3187
+ return () => {
3188
+ return cookies.cart ? `gid://shopify/Cart/${cookies.cart}` : void 0;
3189
+ };
3190
+ };
3191
+
3192
+ // src/cart/cartSetIdDefault.ts
3193
+ var cartSetIdDefault = (cookieOptions) => {
3194
+ return (cartId) => {
3195
+ const headers = new Headers();
3196
+ headers.append(
3197
+ "Set-Cookie",
3198
+ l("cart", cartId.split("/").pop() || "", {
3199
+ path: "/",
3200
+ ...cookieOptions
3201
+ })
3202
+ );
3203
+ return headers;
3204
+ };
3205
+ };
3206
+
3207
+ // src/cart/createCartHandler.ts
3208
+ function createCartHandler(options) {
3209
+ const {
3210
+ getCartId,
3211
+ setCartId,
3212
+ storefront,
3213
+ customerAccount,
3214
+ cartQueryFragment,
3215
+ cartMutateFragment
3216
+ } = options;
3217
+ const mutateOptions = {
3218
+ storefront,
3219
+ getCartId,
3220
+ cartFragment: cartMutateFragment
3221
+ };
3222
+ const cartId = getCartId();
3223
+ const cartCreate = cartCreateDefault(mutateOptions);
3224
+ const methods = {
3225
+ get: cartGetDefault({
3226
+ storefront,
3227
+ customerAccount,
3228
+ getCartId,
3229
+ cartFragment: cartQueryFragment
3230
+ }),
3231
+ getCartId,
3232
+ setCartId,
3233
+ create: cartCreate,
3234
+ addLines: async (lines, optionalParams) => {
3235
+ return cartId || optionalParams?.cartId ? await cartLinesAddDefault(mutateOptions)(lines, optionalParams) : await cartCreate({ lines }, optionalParams);
3236
+ },
3237
+ updateLines: cartLinesUpdateDefault(mutateOptions),
3238
+ removeLines: cartLinesRemoveDefault(mutateOptions),
3239
+ updateDiscountCodes: async (discountCodes, optionalParams) => {
3240
+ return cartId || optionalParams?.cartId ? await cartDiscountCodesUpdateDefault(mutateOptions)(
3241
+ discountCodes,
3242
+ optionalParams
3243
+ ) : await cartCreate({ discountCodes }, optionalParams);
3244
+ },
3245
+ updateBuyerIdentity: async (buyerIdentity, optionalParams) => {
3246
+ return cartId || optionalParams?.cartId ? await cartBuyerIdentityUpdateDefault(mutateOptions)(
3247
+ buyerIdentity,
3248
+ optionalParams
3249
+ ) : await cartCreate({ buyerIdentity }, optionalParams);
3250
+ },
3251
+ updateNote: async (note, optionalParams) => {
3252
+ return cartId || optionalParams?.cartId ? await cartNoteUpdateDefault(mutateOptions)(note, optionalParams) : await cartCreate({ note }, optionalParams);
3253
+ },
3254
+ updateSelectedDeliveryOption: cartSelectedDeliveryOptionsUpdateDefault(mutateOptions),
3255
+ updateAttributes: async (attributes, optionalParams) => {
3256
+ return cartId || optionalParams?.cartId ? await cartAttributesUpdateDefault(mutateOptions)(
3257
+ attributes,
3258
+ optionalParams
3259
+ ) : await cartCreate({ attributes }, optionalParams);
3260
+ },
3261
+ setMetafields: async (metafields, optionalParams) => {
3262
+ return cartId || optionalParams?.cartId ? await cartMetafieldsSetDefault(mutateOptions)(
3263
+ metafields,
3264
+ optionalParams
3265
+ ) : await cartCreate({ metafields }, optionalParams);
3266
+ },
3267
+ deleteMetafield: cartMetafieldDeleteDefault(mutateOptions)
3268
+ };
3269
+ if ("customMethods" in options) {
3270
+ return {
3271
+ ...methods,
3272
+ ...options.customMethods ?? {}
3273
+ };
3274
+ } else {
3275
+ return methods;
3276
+ }
3277
+ }
3278
+ function VariantSelector({
3279
+ handle,
3280
+ options = [],
3281
+ variants: _variants = [],
3282
+ productPath = "products",
3283
+ children
3284
+ }) {
3285
+ const variants = _variants instanceof Array ? _variants : flattenConnection(_variants);
3286
+ const { searchParams, path, alreadyOnProductPage } = useVariantPath(
3287
+ handle,
3288
+ productPath
3289
+ );
3290
+ const optionsWithOnlyOneValue = options.filter(
3291
+ (option) => option?.values?.length === 1
3292
+ );
3293
+ return createElement(
3294
+ Fragment,
3295
+ null,
3296
+ ...useMemo(() => {
3297
+ return options.filter((option) => option?.values?.length > 1).map((option) => {
3298
+ let activeValue;
3299
+ let availableValues = [];
3300
+ for (let value of option.values) {
3301
+ const clonedSearchParams = new URLSearchParams(
3302
+ alreadyOnProductPage ? searchParams : void 0
3303
+ );
3304
+ clonedSearchParams.set(option.name, value);
3305
+ optionsWithOnlyOneValue.forEach((option2) => {
3306
+ clonedSearchParams.set(option2.name, option2.values[0]);
3307
+ });
3308
+ const variant = variants.find(
3309
+ (variant2) => variant2?.selectedOptions?.every(
3310
+ (selectedOption) => clonedSearchParams.get(selectedOption?.name) === selectedOption?.value
3311
+ )
3312
+ );
3313
+ const currentParam = searchParams.get(option.name);
3314
+ const calculatedActiveValue = currentParam ? (
3315
+ // If a URL parameter exists for the current option, check if it equals the current value
3316
+ currentParam === value
3317
+ ) : false;
3318
+ if (calculatedActiveValue) {
3319
+ activeValue = value;
3320
+ }
3321
+ const searchString = "?" + clonedSearchParams.toString();
3322
+ availableValues.push({
3323
+ value,
3324
+ isAvailable: variant ? variant.availableForSale : true,
3325
+ to: path + searchString,
3326
+ search: searchString,
3327
+ isActive: calculatedActiveValue
3328
+ });
3329
+ }
3330
+ return children({
3331
+ option: {
3332
+ name: option.name,
3333
+ value: activeValue,
3334
+ values: availableValues
3335
+ }
3336
+ });
3337
+ });
3338
+ }, [options, variants, children])
3339
+ );
3340
+ }
3341
+ var getSelectedProductOptions = (request) => {
3342
+ if (typeof request?.url === "undefined")
3343
+ throw new TypeError(`Expected a Request instance, got ${typeof request}`);
3344
+ const searchParams = new URL(request.url).searchParams;
3345
+ const selectedOptions = [];
3346
+ searchParams.forEach((value, name) => {
3347
+ selectedOptions.push({ name, value });
3348
+ });
3349
+ return selectedOptions;
3350
+ };
3351
+ function useVariantPath(handle, productPath) {
3352
+ const { pathname, search } = useLocation();
3353
+ return useMemo(() => {
3354
+ const match = /(\/[a-zA-Z]{2}-[a-zA-Z]{2}\/)/g.exec(pathname);
3355
+ const isLocalePathname = match && match.length > 0;
3356
+ productPath = productPath.startsWith("/") ? productPath.substring(1) : productPath;
3357
+ const path = isLocalePathname ? `${match[0]}${productPath}/${handle}` : `/${productPath}/${handle}`;
3358
+ const searchParams = new URLSearchParams(search);
3359
+ return {
3360
+ searchParams,
3361
+ // If the current pathname matches the product page, we need to make sure
3362
+ // that we append to the current search params. Otherwise all the search
3363
+ // params can be generated new.
3364
+ alreadyOnProductPage: path === pathname,
3365
+ path
3366
+ };
3367
+ }, [pathname, search, handle, productPath]);
3368
+ }
3369
+ var NonceContext = createContext(void 0);
3370
+ var NonceProvider = NonceContext.Provider;
3371
+ var useNonce = () => useContext(NonceContext);
3372
+ function createContentSecurityPolicy(directives = {}) {
3373
+ const nonce = generateNonce();
3374
+ const header = createCSPHeader(nonce, directives);
3375
+ const Provider = ({ children }) => {
3376
+ return createElement(NonceProvider, { value: nonce }, children);
3377
+ };
3378
+ return {
3379
+ nonce,
3380
+ header,
3381
+ NonceProvider: Provider
3382
+ };
3383
+ }
3384
+ function createCSPHeader(nonce, directives = {}) {
3385
+ const nonceString = `'nonce-${nonce}'`;
3386
+ const styleSrc = ["'self'", "'unsafe-inline'", "https://cdn.shopify.com"];
3387
+ const connectSrc = ["'self'", "https://monorail-edge.shopifysvc.com"];
3388
+ const defaultSrc = [
3389
+ "'self'",
3390
+ nonceString,
3391
+ "https://cdn.shopify.com",
3392
+ // Used for the Customer Account API
3393
+ "https://shopify.com"
3394
+ ];
3395
+ const defaultDirectives = {
3396
+ baseUri: ["'self'"],
3397
+ defaultSrc,
3398
+ frameAncestors: ["none"],
3399
+ styleSrc,
3400
+ connectSrc
3401
+ };
3402
+ {
3403
+ defaultDirectives.styleSrc = [...styleSrc, "http://localhost:*"];
3404
+ defaultDirectives.defaultSrc = [...defaultSrc, "http://localhost:*"];
3405
+ defaultDirectives.connectSrc = [
3406
+ ...connectSrc,
3407
+ "http://localhost:*",
3408
+ // For HMR:
3409
+ "ws://localhost:*",
3410
+ "ws://127.0.0.1:*"
3411
+ ];
3412
+ }
3413
+ const combinedDirectives = Object.assign({}, defaultDirectives, directives);
3414
+ for (const key in defaultDirectives) {
3415
+ if (directives[key]) {
3416
+ combinedDirectives[key] = addCspDirective(
3417
+ directives[key],
3418
+ defaultDirectives[key]
3419
+ );
3420
+ }
3421
+ }
3422
+ if (combinedDirectives.scriptSrc instanceof Array && !combinedDirectives.scriptSrc.includes(nonceString)) {
3423
+ combinedDirectives.scriptSrc.push(nonceString);
3424
+ } else if (combinedDirectives.defaultSrc instanceof Array && !combinedDirectives.defaultSrc.includes(nonceString)) {
3425
+ combinedDirectives.defaultSrc.push(nonceString);
3426
+ }
3427
+ return cspBuilder({
3428
+ directives: combinedDirectives
3429
+ });
3430
+ }
3431
+ function addCspDirective(currentValue, value) {
3432
+ const normalizedValue = typeof value === "string" ? [value] : value;
3433
+ const normalizedCurrentValue = Array.isArray(currentValue) ? currentValue : [String(currentValue)];
3434
+ const newValue = Array.isArray(normalizedValue) ? [...normalizedCurrentValue, ...normalizedValue] : normalizedValue;
3435
+ return newValue;
3436
+ }
3437
+ var Script = forwardRef(
3438
+ (props, ref) => {
3439
+ const nonce = useNonce();
3440
+ return /* @__PURE__ */ jsx("script", { suppressHydrationWarning: true, ...props, nonce, ref });
3441
+ }
3442
+ );
3443
+ function useOptimisticData(identifier) {
3444
+ const fetchers = useFetchers();
3445
+ const data = {};
3446
+ for (const { formData } of fetchers) {
3447
+ if (formData?.get("optimistic-identifier") === identifier) {
3448
+ try {
3449
+ if (formData.has("optimistic-data")) {
3450
+ const dataInForm = JSON.parse(
3451
+ String(formData.get("optimistic-data"))
3452
+ );
3453
+ Object.assign(data, dataInForm);
3454
+ }
3455
+ } catch {
3456
+ }
3457
+ }
3458
+ }
3459
+ return data;
3460
+ }
3461
+ function OptimisticInput({ id, data }) {
3462
+ return /* @__PURE__ */ jsxs(Fragment$1, { children: [
3463
+ /* @__PURE__ */ jsx("input", { type: "hidden", name: "optimistic-identifier", value: id }),
3464
+ /* @__PURE__ */ jsx(
3465
+ "input",
3466
+ {
3467
+ type: "hidden",
3468
+ name: "optimistic-data",
3469
+ value: JSON.stringify(data)
3470
+ }
3471
+ )
3472
+ ] });
3473
+ }
3474
+ function ShopPayButton(props) {
3475
+ return /* @__PURE__ */ jsx(ShopPayButton$1, { channel: "hydrogen", ...props });
3476
+ }
3477
+ //! @see: https://shopify.dev/docs/api/storefront/latest/mutations/cartCreate
3478
+ //! @see https://shopify.dev/docs/api/storefront/latest/queries/cart
3479
+ //! @see: https://shopify.dev/docs/api/storefront/latest/mutations/cartLinesAdd
3480
+ //! @see: https://shopify.dev/docs/api/storefront/latest/mutations/cartLinesUpdate
3481
+ //! @see: https://shopify.dev/docs/api/storefront/latest/mutations/cartLinesRemove
3482
+ //! @see https://shopify.dev/docs/api/storefront/latest/mutations/cartDiscountCodesUpdate
3483
+ //! @see https://shopify.dev/docs/api/storefront/latest/mutations/cartBuyerIdentityUpdate
3484
+ //! @see https://shopify.dev/docs/api/storefront/latest/mutations/cartNoteUpdate
3485
+ //! @see https://shopify.dev/docs/api/storefront/latest/mutations/cartSelectedDeliveryOptionsUpdate
3486
+ //! @see https://shopify.dev/docs/api/storefront/latest/mutations/cartMetafieldsSet
3487
+ //! @see https://shopify.dev/docs/api/storefront/2024-01/mutations/cartMetafieldDelete
3488
+
3489
+ export { CacheCustom, CacheLong, CacheNone, CacheShort, CartForm, InMemoryCache, OptimisticInput, Pagination, Script, Seo, ShopPayButton, VariantSelector, cartAttributesUpdateDefault, cartBuyerIdentityUpdateDefault, cartCreateDefault, cartDiscountCodesUpdateDefault, cartGetDefault, cartGetIdDefault, cartLinesAddDefault, cartLinesRemoveDefault, cartLinesUpdateDefault, cartMetafieldDeleteDefault, cartMetafieldsSetDefault, cartNoteUpdateDefault, cartSelectedDeliveryOptionsUpdateDefault, cartSetIdDefault, changelogHandler, createCartHandler, createContentSecurityPolicy, createCustomerAccountClient, createStorefrontClient, createWithCache, formatAPIResult, generateCacheControlHeader, getPaginationVariables, getSelectedProductOptions, graphiqlLoader, storefrontRedirect, useNonce, useOptimisticData };
3490
+ //# sourceMappingURL=out.js.map
3491
+ //# sourceMappingURL=index.js.map