@shopify/hydrogen 1.6.1 → 1.6.3

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.
Files changed (33) hide show
  1. package/dist/esnext/components/Link/Link.client.d.ts +1 -1
  2. package/dist/esnext/entry-server.js +6 -7
  3. package/dist/esnext/foundation/Cookie/Cookie.d.ts +1 -1
  4. package/dist/esnext/foundation/Cookie/Cookie.js +1 -1
  5. package/dist/esnext/foundation/Form/Form.client.js +1 -1
  6. package/dist/esnext/foundation/HydrogenRequest/HydrogenRequest.server.js +4 -2
  7. package/dist/esnext/foundation/ShopifyProvider/ShopifyProvider.server.d.ts +1 -1
  8. package/dist/esnext/foundation/ShopifyProvider/ShopifyProvider.server.js +1 -1
  9. package/dist/esnext/foundation/fetchSync/server/fetchSync.d.ts +1 -1
  10. package/dist/esnext/foundation/fetchSync/server/fetchSync.js +18 -5
  11. package/dist/esnext/foundation/useQuery/hooks.d.ts +4 -3
  12. package/dist/esnext/foundation/useQuery/hooks.js +2 -2
  13. package/dist/esnext/foundation/useServerProps/use-server-props.d.ts +1 -1
  14. package/dist/esnext/foundation/useServerProps/use-server-props.js +1 -1
  15. package/dist/esnext/foundation/useShop/use-shop.d.ts +1 -1
  16. package/dist/esnext/foundation/useShop/use-shop.js +1 -1
  17. package/dist/esnext/foundation/useUrl/useUrl.js +3 -3
  18. package/dist/esnext/framework/plugins/vite-plugin-hydrogen-config.js +1 -5
  19. package/dist/esnext/hooks/useShopQuery/hooks.d.ts +2 -2
  20. package/dist/esnext/hooks/useShopQuery/hooks.js +46 -57
  21. package/dist/esnext/index.d.ts +1 -0
  22. package/dist/esnext/index.js +1 -0
  23. package/dist/esnext/utilities/bot-ua.js +4 -0
  24. package/dist/esnext/utilities/hash.js +0 -4
  25. package/dist/esnext/utilities/log/log-query-timeline.js +1 -1
  26. package/dist/esnext/utilities/parse.d.ts +3 -0
  27. package/dist/esnext/utilities/parse.js +16 -0
  28. package/dist/esnext/utilities/storefrontApi.d.ts +1 -0
  29. package/dist/esnext/utilities/storefrontApi.js +17 -2
  30. package/dist/esnext/version.d.ts +1 -1
  31. package/dist/esnext/version.js +1 -1
  32. package/dist/node/framework/plugins/vite-plugin-hydrogen-config.js +1 -5
  33. package/package.json +2 -1
@@ -8,7 +8,7 @@ export interface LinkProps extends Omit<React.AnchorHTMLAttributes<HTMLAnchorEle
8
8
  clientState?: any;
9
9
  /** Whether to reload the whole document on navigation. */
10
10
  reloadDocument?: boolean;
11
- /** Whether to prefetch the link source when the user signals intent. Defaults to `true`. For more information, refer to [Prefetching a link source](https://shopify.dev/custom-storefronts/hydrogen/framework/routes#prefetching-a-link-source). */
11
+ /** Whether to prefetch the link source when the user signals intent. Defaults to `true`. For more information, refer to [Prefetching a link source](https://shopify.dev/custom-storefronts/hydrogen/routing/manage-routes/#prefetching-a-link-source). */
12
12
  prefetch?: boolean;
13
13
  /** Whether to emulate natural browser behavior and restore scroll position on navigation. Defaults to `true`. */
14
14
  scroll?: boolean;
@@ -14,7 +14,7 @@ import { getTemplate } from './utilities/template.js';
14
14
  import { Analytics } from './foundation/Analytics/Analytics.server.js';
15
15
  import { DevTools } from './foundation/DevTools/DevTools.server.js';
16
16
  import { getSyncSessionApi } from './foundation/session/session.js';
17
- import { parseJSON } from './utilities/parse.js';
17
+ import { parseState } from './utilities/parse.js';
18
18
  import { htmlEncode } from './utilities/index.js';
19
19
  import { generateUUID } from './utilities/random.js';
20
20
  import { splitCookiesString } from 'set-cookie-parser';
@@ -153,12 +153,11 @@ async function processRequest(handleRequest, App, url, request, sessionApi, opti
153
153
  })
154
154
  : apiResponse;
155
155
  }
156
- const state = isRSCRequest
157
- ? parseJSON(decodeURIComponent(url.searchParams.get('state') || '{}'))
158
- : {
159
- pathname: decodeURIComponent(url.pathname),
160
- search: decodeURIComponent(url.search),
161
- };
156
+ const state = parseState(url);
157
+ if (!state) {
158
+ postRequestTasks(isRSCRequest ? 'rsc' : 'ssr', 400, request, response, true);
159
+ return new Response(`Invalid RSC state`, { status: 400 });
160
+ }
162
161
  const rsc = runRSC({ App, state, log, request, response });
163
162
  if (isRSCRequest) {
164
163
  const buffered = await bufferReadableStream(rsc.readable.getReader());
@@ -27,7 +27,7 @@ export declare type CookieOptions = {
27
27
  maxAge?: number;
28
28
  };
29
29
  /** The `Cookie` component helps you build your own custom cookie and session implementations. All
30
- * [Hydrogen session storage mechanisms](https://shopify.dev/custom-storefronts/hydrogen/framework/sessions#types-of-session-storage) use the
30
+ * [Hydrogen session storage mechanisms](https://shopify.dev/custom-storefronts/hydrogen/sessions#types-of-session-storage) use the
31
31
  * same configuration options as what's available in `Cookie`.
32
32
  */
33
33
  export declare class Cookie {
@@ -3,7 +3,7 @@ import { log } from '../../utilities/log/index.js';
3
3
  import { parseJSON } from '../../utilities/parse.js';
4
4
  const reservedCookieNames = ['mac', 'user_session_id'];
5
5
  /** The `Cookie` component helps you build your own custom cookie and session implementations. All
6
- * [Hydrogen session storage mechanisms](https://shopify.dev/custom-storefronts/hydrogen/framework/sessions#types-of-session-storage) use the
6
+ * [Hydrogen session storage mechanisms](https://shopify.dev/custom-storefronts/hydrogen/sessions#types-of-session-storage) use the
7
7
  * same configuration options as what's available in `Cookie`.
8
8
  */
9
9
  export class Cookie {
@@ -30,7 +30,7 @@ export function Form({ action, method, children, onSubmit, encType = 'applicatio
30
30
  .then((fetchResponse) => {
31
31
  const rscPathname = fetchResponse.headers.get('Hydrogen-RSC-Pathname');
32
32
  if (!rscPathname)
33
- throw new Error(`The component's \`action\` attribute must point to an API route that responds with a new Request()\nRead more at https://shopify.dev/custom-storefronts/hydrogen/framework/forms`);
33
+ throw new Error(`The component's \`action\` attribute must point to an API route that responds with a new Request()\nRead more at https://shopify.dev/custom-storefronts/hydrogen/forms`);
34
34
  if (rscPathname !== window.location.pathname) {
35
35
  window.history.pushState(null, '', rscPathname);
36
36
  }
@@ -2,7 +2,7 @@ import { getTime } from '../../utilities/timing.js';
2
2
  import { hashKey } from '../../utilities/hash.js';
3
3
  import { HelmetData as HeadData } from 'react-helmet-async';
4
4
  import { RSC_PATHNAME } from '../../constants.js';
5
- import { parseJSON } from '../../utilities/parse.js';
5
+ import { parseState } from '../../utilities/parse.js';
6
6
  import { generateUUID } from '../../utilities/random.js';
7
7
  // Stores queries by url or '*'
8
8
  const preloadCache = new Map();
@@ -197,7 +197,9 @@ function getInitFromNodeRequest(request) {
197
197
  }
198
198
  function normalizeUrl(rawUrl) {
199
199
  const url = new URL(rawUrl);
200
- const state = parseJSON(url.searchParams.get('state') ?? '');
200
+ const state = parseState(url);
201
+ if (!state)
202
+ return '';
201
203
  const normalizedUrl = new URL(state?.pathname ?? '', url.origin);
202
204
  normalizedUrl.search = state?.search;
203
205
  return normalizedUrl.toString();
@@ -11,7 +11,7 @@ export declare const SHOPIFY_PROVIDER_CONTEXT_KEY: unique symbol;
11
11
  export declare function ShopifyProvider({
12
12
  /**
13
13
  * Shopify connection information. Defaults to
14
- * [the `shopify` property in the `hydrogen.config.js` file](https://shopify.dev/custom-storefronts/hydrogen/framework/hydrogen-config).
14
+ * [the `shopify` property in the `hydrogen.config.js` file](https://shopify.dev/custom-storefronts/hydrogen/configuration).
15
15
  */
16
16
  shopifyConfig, countryCode, languageCode,
17
17
  /** Any `ReactNode` elements. */
@@ -45,7 +45,7 @@ export const SHOPIFY_PROVIDER_CONTEXT_KEY = Symbol.for('SHOPIFY_PROVIDER_RSC');
45
45
  export function ShopifyProvider({
46
46
  /**
47
47
  * Shopify connection information. Defaults to
48
- * [the `shopify` property in the `hydrogen.config.js` file](https://shopify.dev/custom-storefronts/hydrogen/framework/hydrogen-config).
48
+ * [the `shopify` property in the `hydrogen.config.js` file](https://shopify.dev/custom-storefronts/hydrogen/configuration).
49
49
  */
50
50
  shopifyConfig, countryCode, languageCode,
51
51
  /** Any `ReactNode` elements. */
@@ -5,4 +5,4 @@ import { ResponseSync } from '../ResponseSync.js';
5
5
  * It's designed similar to the [Web API's `fetch`](https://developer.mozilla.org/en-US/docs/Web/API/fetch), only in a way
6
6
  * that supports [Suspense](https://reactjs.org/docs/concurrent-mode-suspense.html).
7
7
  */
8
- export declare function fetchSync(url: string, options?: Omit<RequestInit, 'cache'> & HydrogenUseQueryOptions): ResponseSync;
8
+ export declare function fetchSync(originalUrl: string, options?: Omit<RequestInit, 'cache'> & HydrogenUseQueryOptions): ResponseSync;
@@ -1,18 +1,31 @@
1
1
  import { useQuery } from '../../useQuery/index.js';
2
- import { useUrl } from '../../useUrl/index.js';
3
2
  import { ResponseSync } from '../ResponseSync.js';
4
3
  /**
5
4
  * The `fetchSync` hook makes API requests and is the recommended way to make simple fetch calls on the server and the client.
6
5
  * It's designed similar to the [Web API's `fetch`](https://developer.mozilla.org/en-US/docs/Web/API/fetch), only in a way
7
6
  * that supports [Suspense](https://reactjs.org/docs/concurrent-mode-suspense.html).
8
7
  */
9
- export function fetchSync(url, options) {
8
+ export function fetchSync(originalUrl, options) {
9
+ let requestUrl;
10
+ try {
11
+ requestUrl = new URL(originalUrl);
12
+ }
13
+ catch (error) {
14
+ // The URL is not valid, and the user likely tried to call a relative path in the same app.
15
+ if (error instanceof TypeError && originalUrl.startsWith('/')) {
16
+ throw new TypeError(`fetchSync() was called with a relative URL (${originalUrl}). Please use an absolute URL instead.\n` +
17
+ `- If you're trying to fetch a relative path in the same app, query the data source directly from the server instead.\n` +
18
+ `- If you're calling fetchSync() within a client component, use a conditional state like useEffect() to ` +
19
+ `prevent it from being called on the server during pre-render.`);
20
+ }
21
+ // The URL is not valid for some other reason.
22
+ throw error;
23
+ }
24
+ const url = requestUrl.toString();
10
25
  const { cache, preload, shouldCacheResponse, ...requestInit } = options ?? {};
11
26
  // eslint-disable-next-line react-hooks/rules-of-hooks
12
- const { origin } = useUrl();
13
- // eslint-disable-next-line react-hooks/rules-of-hooks
14
27
  const { data, error } = useQuery([url, requestInit], async () => {
15
- const response = await globalThis.fetch(new URL(url, origin), requestInit);
28
+ const response = await globalThis.fetch(url, requestInit);
16
29
  return ResponseSync.toSerializable(response);
17
30
  }, {
18
31
  cache,
@@ -1,10 +1,11 @@
1
1
  import type { CachingStrategy, PreloadOptions, QueryKey } from '../../types.js';
2
+ import type { HydrogenRequest } from '../HydrogenRequest/HydrogenRequest.server.js';
2
3
  export interface HydrogenUseQueryOptions {
3
- /** The [caching strategy](https://shopify.dev/custom-storefronts/hydrogen/framework/cache#caching-strategies) to help you
4
+ /** The [caching strategy](https://shopify.dev/custom-storefronts/hydrogen/querying/cache#caching-strategies) to help you
4
5
  * determine which cache control header to set.
5
6
  */
6
7
  cache?: CachingStrategy;
7
- /** Whether to [preload the query](https://shopify.dev/custom-storefronts/hydrogen/framework/preloaded-queries).
8
+ /** Whether to [preload the query](https://shopify.dev/custom-storefronts/hydrogen/querying/preloaded-queries).
8
9
  * Defaults to `false`. Specify `true` to preload the query for the URL or `'*'`
9
10
  * to preload the query for all requests.
10
11
  */
@@ -28,7 +29,7 @@ export declare function useQuery<T>(
28
29
  /** A string or array to uniquely identify the current query. */
29
30
  key: QueryKey,
30
31
  /** An asynchronous query function like `fetch` which returns data. */
31
- queryFn: () => Promise<T>,
32
+ queryFn: (request: HydrogenRequest) => Promise<T>,
32
33
  /** The options to manage the cache behavior of the sub-request. */
33
34
  queryOptions?: HydrogenUseQueryOptions): {
34
35
  data?: undefined;
@@ -73,7 +73,7 @@ function cachedQueryFnBuilder(key, generateNewOutput, queryOptions) {
73
73
  maxAge: 10,
74
74
  }));
75
75
  try {
76
- const output = await generateNewOutput();
76
+ const output = await generateNewOutput(request);
77
77
  if (shouldCacheResponse(output)) {
78
78
  await setItemInCache(key, output, resolvedQueryOptions?.cache);
79
79
  }
@@ -90,7 +90,7 @@ function cachedQueryFnBuilder(key, generateNewOutput, queryOptions) {
90
90
  }
91
91
  return output;
92
92
  }
93
- const newOutput = await generateNewOutput();
93
+ const newOutput = await generateNewOutput(request);
94
94
  /**
95
95
  * Important: Do this async
96
96
  */
@@ -1,6 +1,6 @@
1
1
  import { ServerPropsContextValue, InternalServerPropsContextValue } from '../ServerPropsProvider/ServerPropsProvider.js';
2
2
  /**
3
- * The `useServerProps` hook allows you to manage the [server props](https://shopify.dev/custom-storefronts/hydrogen/framework/server-props) passed to your server components when using Hydrogen as a React Server Component framework. The server props get cleared when you navigate from one route to another.
3
+ * The `useServerProps` hook allows you to manage the [server props](https://shopify.dev/custom-storefronts/hydrogen/server-props) passed to your server components when using Hydrogen as a React Server Component framework. The server props get cleared when you navigate from one route to another.
4
4
  *
5
5
  * ## Return value
6
6
  *
@@ -1,7 +1,7 @@
1
1
  import { useContext } from 'react';
2
2
  import { ServerPropsContext, } from '../ServerPropsProvider/ServerPropsProvider.js';
3
3
  /**
4
- * The `useServerProps` hook allows you to manage the [server props](https://shopify.dev/custom-storefronts/hydrogen/framework/server-props) passed to your server components when using Hydrogen as a React Server Component framework. The server props get cleared when you navigate from one route to another.
4
+ * The `useServerProps` hook allows you to manage the [server props](https://shopify.dev/custom-storefronts/hydrogen/server-props) passed to your server components when using Hydrogen as a React Server Component framework. The server props get cleared when you navigate from one route to another.
5
5
  *
6
6
  * ## Return value
7
7
  *
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * The `useShop` hook provides access to values within
3
- * [the `shopify` property in the `hydrogen.config.js` file](https://shopify.dev/custom-storefronts/hydrogen/framework/hydrogen-config).
3
+ * [the `shopify` property in the `hydrogen.config.js` file](https://shopify.dev/custom-storefronts/hydrogen/configuration).
4
4
  * The `useShop` hook must be a descendent of a `ShopifyProvider` component.
5
5
  */
6
6
  export declare function useShop(): import("../ShopifyProvider/types.js").ShopifyContextServerValue;
@@ -2,7 +2,7 @@ import { ShopifyContext } from '../ShopifyProvider/index.js';
2
2
  import { useEnvContext } from '../ssr-interop.js';
3
3
  /**
4
4
  * The `useShop` hook provides access to values within
5
- * [the `shopify` property in the `hydrogen.config.js` file](https://shopify.dev/custom-storefronts/hydrogen/framework/hydrogen-config).
5
+ * [the `shopify` property in the `hydrogen.config.js` file](https://shopify.dev/custom-storefronts/hydrogen/configuration).
6
6
  * The `useShop` hook must be a descendent of a `ShopifyProvider` component.
7
7
  */
8
8
  export function useShop() {
@@ -1,6 +1,6 @@
1
1
  import { useContext, useMemo } from 'react';
2
2
  import { RSC_PATHNAME } from '../../constants.js';
3
- import { parseJSON } from '../../utilities/parse.js';
3
+ import { parseState } from '../../utilities/parse.js';
4
4
  import { RouterContext } from '../Router/BrowserRouter.client.js';
5
5
  import { useEnvContext, META_ENV_SSR } from '../ssr-interop.js';
6
6
  /**
@@ -11,8 +11,8 @@ export function useUrl() {
11
11
  const serverUrl = new URL(useEnvContext((req) => req.url) // eslint-disable-line react-hooks/rules-of-hooks
12
12
  );
13
13
  if (serverUrl.pathname === RSC_PATHNAME) {
14
- const state = parseJSON(serverUrl.searchParams.get('state') || '{}');
15
- const parsedUrl = `${serverUrl.origin}${state.pathname ?? ''}${state.search ?? ''}`;
14
+ const state = parseState(serverUrl);
15
+ const parsedUrl = `${serverUrl.origin}${state?.pathname ?? ''}${state?.search ?? ''}`;
16
16
  return new URL(parsedUrl);
17
17
  }
18
18
  return new URL(serverUrl);
@@ -64,10 +64,7 @@ export default (pluginOptions) => {
64
64
  // Reload when updating local Hydrogen lib
65
65
  server: process.env.LOCAL_DEV && {
66
66
  watch: {
67
- ignored: [
68
- '!**/node_modules/@shopify/hydrogen/**',
69
- '!**/node_modules/@shopify/hydrogen-ui/**',
70
- ],
67
+ ignored: ['!**/node_modules/@shopify/hydrogen/**'],
71
68
  },
72
69
  },
73
70
  optimizeDeps: {
@@ -75,7 +72,6 @@ export default (pluginOptions) => {
75
72
  '@shopify/hydrogen',
76
73
  '@shopify/hydrogen/client',
77
74
  '@shopify/hydrogen/entry-client',
78
- '@shopify/hydrogen-ui',
79
75
  ],
80
76
  include: [
81
77
  /**
@@ -14,13 +14,13 @@ export declare function useShopQuery<T>({ query, variables, cache, preload, }: {
14
14
  query?: string;
15
15
  /** An object of the variables for the GraphQL query. */
16
16
  variables?: Record<string, any>;
17
- /** The [caching strategy](https://shopify.dev/custom-storefronts/hydrogen/framework/cache#caching-strategies) to
17
+ /** The [caching strategy](https://shopify.dev/custom-storefronts/hydrogen/querying/cache#caching-strategies) to
18
18
  * help you determine which cache control header to set.
19
19
  */
20
20
  cache?: CachingStrategy;
21
21
  /** A string corresponding to a valid locale identifier like `en-us` used to make the request. */
22
22
  locale?: string;
23
- /** Whether to[preload the query](https://shopify.dev/custom-storefronts/hydrogen/framework/preloaded-queries).
23
+ /** Whether to[preload the query](https://shopify.dev//custom-storefronts/hydrogen/querying/preloaded-queries).
24
24
  * Defaults to `false`. Specify `true` to preload the query for the URL or `'*'`
25
25
  * to preload the query for all requests.
26
26
  */
@@ -1,24 +1,18 @@
1
+ /* eslint-disable react-hooks/rules-of-hooks */
1
2
  import { useShop } from '../../foundation/useShop/index.js';
2
3
  import { getLoggerWithContext } from '../../utilities/log/index.js';
3
4
  import { graphqlRequestBody } from '../../utilities/index.js';
4
5
  import { useServerRequest } from '../../foundation/ServerRequestProvider/index.js';
5
6
  import { injectGraphQLTracker } from '../../utilities/graphql-tracker.js';
6
7
  import { sendMessageToClient } from '../../utilities/devtools.js';
7
- import { fetchSync } from '../../foundation/fetchSync/server/fetchSync.js';
8
8
  import { META_ENV_SSR } from '../../foundation/ssr-interop.js';
9
9
  import { getStorefrontApiRequestHeaders } from '../../utilities/storefrontApi.js';
10
10
  import { parseJSON } from '../../utilities/parse.js';
11
+ import { useQuery } from '../../foundation/useQuery/hooks.js';
11
12
  // Check if the response body has GraphQL errors
12
13
  // https://spec.graphql.org/June2018/#sec-Response-Format
13
- const shouldCacheResponse = ([body]) => {
14
- try {
15
- return !parseJSON(body)?.errors;
16
- }
17
- catch {
18
- // If we can't parse the response, then assume
19
- // an error and don't cache the response
20
- return false;
21
- }
14
+ const shouldCacheResponse = (body) => {
15
+ return !body?.errors;
22
16
  };
23
17
  /**
24
18
  * The `useShopQuery` hook allows you to make server-only GraphQL queries to the Storefront API. It must be a descendent of a `ShopifyProvider` component.
@@ -33,48 +27,61 @@ export function useShopQuery({ query, variables = {}, cache, preload = false, })
33
27
  if (!META_ENV_SSR) {
34
28
  throw new Error('Shopify Storefront API requests should only be made from the server.');
35
29
  }
36
- const serverRequest = useServerRequest(); // eslint-disable-line react-hooks/rules-of-hooks
30
+ const serverRequest = useServerRequest();
37
31
  const log = getLoggerWithContext(serverRequest);
32
+ const { storeDomain, storefrontApiVersion, storefrontToken, storefrontId, privateStorefrontToken, } = useShop();
38
33
  const body = query ? graphqlRequestBody(query, variables) : '';
39
- const { url, requestInit } = useCreateShopRequest(body); // eslint-disable-line react-hooks/rules-of-hooks
40
- let text;
41
- let data;
42
- let useQueryError;
43
- let response = null;
44
- try {
45
- response = fetchSync(url, {
46
- ...requestInit,
47
- cache,
48
- preload,
49
- shouldCacheResponse,
34
+ const { data, error } = useQuery([storeDomain, storefrontApiVersion, body], async (request) => {
35
+ const { url, requestInit } = useCreateShopRequest({
36
+ body,
37
+ request,
38
+ storeDomain,
39
+ storefrontToken,
40
+ storefrontApiVersion,
41
+ storefrontId,
42
+ privateStorefrontToken,
50
43
  });
51
- text = response.text();
44
+ const response = await fetch(url, requestInit);
45
+ const text = await response.text();
52
46
  try {
53
- data = response.json();
47
+ const data = parseJSON(text);
48
+ /**
49
+ * GraphQL errors get printed to the console but ultimately
50
+ * get returned to the consumer.
51
+ */
52
+ if (data?.errors) {
53
+ const errors = Array.isArray(data.errors)
54
+ ? data.errors
55
+ : [data.errors];
56
+ const requestId = response?.headers?.get('x-request-id') ?? '';
57
+ for (const error of errors) {
58
+ if (__HYDROGEN_DEV__ && !__HYDROGEN_TEST__) {
59
+ throw new Error(`Storefront API GraphQL Error: ${error.message}.\nRequest id: ${requestId}`);
60
+ }
61
+ else {
62
+ log.error('Storefront API GraphQL Error', error, 'Storefront API GraphQL request id', requestId);
63
+ }
64
+ }
65
+ log.error(`Storefront API GraphQL error count: ${errors.length}`);
66
+ }
67
+ return data;
54
68
  }
55
69
  catch (error) {
56
70
  if (response.headers.get('content-length')) {
57
- useQueryError = new Error(`Unable to parse response (x-request-id: ${response.headers.get('x-request-id')}):\n${text}`);
71
+ throw new Error(`Unable to parse response (x-request-id: ${response.headers.get('x-request-id')}):\n${text}`);
58
72
  }
59
73
  else {
60
- useQueryError = new Error(`${response.status} ${response.statusText} (x-request-id: ${response.headers.get('x-request-id')})`);
74
+ throw new Error(`${response.status} ${response.statusText} (x-request-id: ${response.headers.get('x-request-id')})`);
61
75
  }
62
76
  }
63
- }
64
- catch (error) {
65
- // Pass-through thrown promise for Suspense functionality
66
- if (error?.then) {
67
- throw error;
68
- }
69
- useQueryError = error;
70
- }
77
+ }, { cache, preload, shouldCacheResponse });
71
78
  /**
72
79
  * The fetch request itself failed, so we handle that differently than a GraphQL error
73
80
  */
74
- if (useQueryError) {
75
- const errorMessage = createErrorMessage(useQueryError);
81
+ if (error) {
82
+ const errorMessage = createErrorMessage(error);
76
83
  log.error(errorMessage);
77
- log.error(useQueryError);
84
+ log.error(error);
78
85
  if (__HYDROGEN_DEV__ && !__HYDROGEN_TEST__) {
79
86
  throw new Error(errorMessage);
80
87
  }
@@ -83,23 +90,6 @@ export function useShopQuery({ query, variables = {}, cache, preload = false, })
83
90
  throw new Error(`The fetch attempt failed; there was an issue connecting to the data source.`);
84
91
  }
85
92
  }
86
- /**
87
- * GraphQL errors get printed to the console but ultimately
88
- * get returned to the consumer.
89
- */
90
- if (data?.errors) {
91
- const errors = Array.isArray(data.errors) ? data.errors : [data.errors];
92
- const requestId = response?.headers?.get('x-request-id') ?? '';
93
- for (const error of errors) {
94
- if (__HYDROGEN_DEV__ && !__HYDROGEN_TEST__) {
95
- throw new Error(`Storefront API GraphQL Error: ${error.message}.\nRequest id: ${requestId}`);
96
- }
97
- else {
98
- log.error('Storefront API GraphQL Error', error, 'Storefront API GraphQL request id', requestId);
99
- }
100
- }
101
- log.error(`Storefront API GraphQL error count: ${errors.length}`);
102
- }
103
93
  if (__HYDROGEN_DEV__ &&
104
94
  (log.options().showUnusedQueryProperties ||
105
95
  serverRequest.ctx.hydrogenConfig?.__EXPERIMENTAL__devTools) &&
@@ -144,9 +134,7 @@ export function useShopQuery({ query, variables = {}, cache, preload = false, })
144
134
  }
145
135
  return data;
146
136
  }
147
- function useCreateShopRequest(body) {
148
- const { storeDomain, storefrontToken, storefrontApiVersion, storefrontId, privateStorefrontToken, } = useShop();
149
- const request = useServerRequest();
137
+ function useCreateShopRequest({ body, request, storeDomain, storefrontToken, storefrontApiVersion, storefrontId, privateStorefrontToken, }) {
150
138
  const buyerIp = request.getBuyerIp();
151
139
  let headers = {
152
140
  'X-SDK-Variant': 'hydrogen',
@@ -185,3 +173,4 @@ function createErrorMessage(fetchError) {
185
173
  return `Failed to connect to the Storefront API: ${fetchError.message}`;
186
174
  }
187
175
  }
176
+ /* eslint-enable react-hooks/rules-of-hooks */
@@ -42,3 +42,4 @@ export { type HydrogenRequest } from './foundation/HydrogenRequest/HydrogenReque
42
42
  export { type HydrogenResponse } from './foundation/HydrogenResponse/HydrogenResponse.server.js';
43
43
  export { type HydrogenRouteProps, type CachingStrategy } from './types.js';
44
44
  export { type ResourceGetter as HydrogenApiRoute, RequestOptions as HydrogenApiRouteOptions, } from './utilities/apiRoutes.js';
45
+ export { getOnlineStorefrontHeaders } from './utilities/storefrontApi.js';
@@ -38,3 +38,4 @@ export { CartQuery } from './components/CartProvider/cart-queries.js';
38
38
  * Override the client version of `fetchSync` with the server version.
39
39
  */
40
40
  export { fetchSync } from './foundation/fetchSync/server/fetchSync.js';
41
+ export { getOnlineStorefrontHeaders } from './utilities/storefrontApi.js';
@@ -5,6 +5,7 @@
5
5
  */
6
6
  const botUserAgents = [
7
7
  'AdsBot-Google',
8
+ 'AdsBot-Google-Mobile',
8
9
  'applebot',
9
10
  'Baiduspider',
10
11
  'baiduspider',
@@ -23,6 +24,9 @@ const botUserAgents = [
23
24
  'facebookexternalhit',
24
25
  'Google-PageRenderer',
25
26
  'Googlebot',
27
+ 'Googlebot-Image',
28
+ 'Googlebot-News',
29
+ 'Googlebot-Video',
26
30
  'googleweblight',
27
31
  'ia_archive',
28
32
  'LinkedInBot',
@@ -1,4 +1,3 @@
1
- import { STOREFRONT_API_BUYER_IP_HEADER } from '../constants.js';
2
1
  export function hashKey(queryKey) {
3
2
  const rawKeys = Array.isArray(queryKey) ? queryKey : [queryKey];
4
3
  let hash = '';
@@ -13,9 +12,6 @@ export function hashKey(queryKey) {
13
12
  // fallback to a safer (but slower) stringify.
14
13
  if (!!key.body && typeof key.body === 'string') {
15
14
  hash += key.body;
16
- if (!!key.headers && key.headers[STOREFRONT_API_BUYER_IP_HEADER]) {
17
- hash += key.headers[STOREFRONT_API_BUYER_IP_HEADER];
18
- }
19
15
  }
20
16
  else {
21
17
  hash += JSON.stringify(key);
@@ -100,6 +100,6 @@ export function logQueryTimings(type, request) {
100
100
  log.debug(yellow('Suspense waterfall detected on query: ' +
101
101
  firstSuspenseWaterfallQueryName));
102
102
  log.debug(' Add the `showQueryTiming` property to your Hydrogen configuration to see more information:');
103
- log.debug(' https://shopify.dev/custom-storefronts/hydrogen/framework/hydrogen-config#logger');
103
+ log.debug(' https://shopify.dev/custom-storefronts/hydrogen/configuration#logger');
104
104
  }
105
105
  }
@@ -1 +1,4 @@
1
1
  export declare function parseJSON(json: any): any;
2
+ export declare function parseState(url: URL): {
3
+ [k: string]: string;
4
+ } | undefined;
@@ -1,3 +1,4 @@
1
+ import { RSC_PATHNAME } from '../constants.js';
1
2
  export function parseJSON(json) {
2
3
  if (String(json).includes('__proto__'))
3
4
  return JSON.parse(json, noproto);
@@ -7,3 +8,18 @@ function noproto(k, v) {
7
8
  if (k !== '__proto__')
8
9
  return v;
9
10
  }
11
+ export function parseState(url) {
12
+ try {
13
+ const { pathname, search } = url;
14
+ const state = pathname === RSC_PATHNAME
15
+ ? parseJSON(url.searchParams.get('state') ?? '{}')
16
+ : { pathname, search };
17
+ return Object.fromEntries(Object.entries(state).map(([key, value]) => [
18
+ decodeURIComponent(key ?? ''),
19
+ decodeURIComponent(value ?? ''),
20
+ ]));
21
+ }
22
+ catch {
23
+ // Do not throw to prevent unhandled errors
24
+ }
25
+ }
@@ -5,3 +5,4 @@ export declare function getStorefrontApiRequestHeaders({ buyerIp, publicStorefro
5
5
  storefrontId: string | undefined;
6
6
  }): Record<string, any>;
7
7
  export declare function getOxygenVariable(key: string): any;
8
+ export declare function getOnlineStorefrontHeaders(request: Request, origin: string): Headers;
@@ -13,7 +13,7 @@ export function getStorefrontApiRequestHeaders({ buyerIp, publicStorefrontToken,
13
13
  log.error('No secret Shopify storefront API token was defined. This means your app will be rate limited!\nSee how to add the token: ');
14
14
  }
15
15
  else if (privateStorefrontToken) {
16
- log.warn('The private shopify storefront API token was loaded implicitly by an environment variable. This is deprecated, and instead the variable should be defined directly in the Hydrogen Config.\nFor more information: https://shopify.dev/custom-storefronts/hydrogen/framework/hydrogen-config');
16
+ log.warn('The private shopify storefront API token was loaded implicitly by an environment variable. This is deprecated, and instead the variable should be defined directly in the Hydrogen Config.\nFor more information: https://shopify.dev/custom-storefronts/hydrogen/configuration');
17
17
  }
18
18
  }
19
19
  }
@@ -22,7 +22,7 @@ export function getStorefrontApiRequestHeaders({ buyerIp, publicStorefrontToken,
22
22
  if (!storefrontIdWarned) {
23
23
  storefrontIdWarned = true;
24
24
  if (storefrontId) {
25
- log.warn('The storefrontId was loaded implicitly by an environment variable. This is deprecated, and instead the variable should be defined directly in the Hydrogen Config.\nFor more information: https://shopify.dev/custom-storefronts/hydrogen/framework/hydrogen-config');
25
+ log.warn('The storefrontId was loaded implicitly by an environment variable. This is deprecated, and instead the variable should be defined directly in the Hydrogen Config.\nFor more information: https://shopify.dev/custom-storefronts/hydrogen/configuration');
26
26
  }
27
27
  }
28
28
  }
@@ -46,3 +46,18 @@ export function getStorefrontApiRequestHeaders({ buyerIp, publicStorefrontToken,
46
46
  export function getOxygenVariable(key) {
47
47
  return typeof Oxygen !== 'undefined' ? Oxygen?.env?.[key] : null;
48
48
  }
49
+ export function getOnlineStorefrontHeaders(request, origin) {
50
+ const clientIP = request.headers.get('X-Shopify-Client-IP');
51
+ const clientIPSig = request.headers.get('X-Shopify-Client-IP-Sig');
52
+ const headers = new Headers();
53
+ for (const [key, value] of request.headers.entries()) {
54
+ headers.append(key, swapHostname(value, { hostname: new URL(request.url).host, origin }));
55
+ }
56
+ if (!__HYDROGEN_DEV__ && (!clientIP || !clientIPSig)) {
57
+ log.warn('Proxying the online store is only available in Oxygen. This request is likely to be throttled.');
58
+ }
59
+ return headers;
60
+ }
61
+ function swapHostname(str, { hostname, origin }) {
62
+ return str.replaceAll(hostname, origin);
63
+ }
@@ -1 +1 @@
1
- export declare const LIB_VERSION = "1.6.1";
1
+ export declare const LIB_VERSION = "1.6.3";
@@ -1 +1 @@
1
- export const LIB_VERSION = '1.6.1';
1
+ export const LIB_VERSION = '1.6.3';
@@ -69,10 +69,7 @@ exports.default = (pluginOptions) => {
69
69
  // Reload when updating local Hydrogen lib
70
70
  server: process.env.LOCAL_DEV && {
71
71
  watch: {
72
- ignored: [
73
- '!**/node_modules/@shopify/hydrogen/**',
74
- '!**/node_modules/@shopify/hydrogen-ui/**',
75
- ],
72
+ ignored: ['!**/node_modules/@shopify/hydrogen/**'],
76
73
  },
77
74
  },
78
75
  optimizeDeps: {
@@ -80,7 +77,6 @@ exports.default = (pluginOptions) => {
80
77
  '@shopify/hydrogen',
81
78
  '@shopify/hydrogen/client',
82
79
  '@shopify/hydrogen/entry-client',
83
- '@shopify/hydrogen-ui',
84
80
  ],
85
81
  include: [
86
82
  /**
package/package.json CHANGED
@@ -7,7 +7,7 @@
7
7
  "engines": {
8
8
  "node": ">=14"
9
9
  },
10
- "version": "1.6.1",
10
+ "version": "1.6.3",
11
11
  "description": "Modern custom Shopify storefronts",
12
12
  "license": "MIT",
13
13
  "main": "dist/esnext/index.js",
@@ -100,6 +100,7 @@
100
100
  "@types/set-cookie-parser": "^2.4.2",
101
101
  "@types/uuid": "^8.3.4",
102
102
  "@types/ws": "^8.2.0",
103
+ "@vitest/coverage-c8": "^0.24.3",
103
104
  "babel-loader": "^8.2.2",
104
105
  "cpy-cli": "^3.1.0",
105
106
  "eslint-plugin-import": "^2.26.0",