hydrogen-sfdgspsdmq-test1 0.0.1-security → 2024.1.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of hydrogen-sfdgspsdmq-test1 might be problematic. Click here for more details.
- package/LICENSE.md +7 -0
- package/README.md +20 -3
- package/dist/customer-account-api-types.d.ts +9675 -0
- package/dist/customer-account.schema.json +1 -0
- package/dist/development/build.js +223 -0
- package/dist/development/index.cjs +3708 -0
- package/dist/development/index.cjs.map +1 -0
- package/dist/development/index.js +3491 -0
- package/dist/development/index.js.map +1 -0
- package/dist/development/log-seo-tags-IG37ONQ2.js +74 -0
- package/dist/development/log-seo-tags-IG37ONQ2.js.map +1 -0
- package/dist/index.cjs +1 -0
- package/dist/production/index.cjs +754 -0
- package/dist/production/index.cjs.map +1 -0
- package/dist/production/index.d.cts +1515 -0
- package/dist/production/index.d.ts +1515 -0
- package/dist/production/index.js +626 -0
- package/dist/production/index.js.map +1 -0
- package/dist/production/log-seo-tags-TY72EQWZ.js +5 -0
- package/dist/production/log-seo-tags-TY72EQWZ.js.map +1 -0
- package/dist/storefront-api-types.d.ts +7986 -0
- package/dist/storefront.schema.json +1 -0
- package/package.json +77 -3
@@ -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
|