@shopify/hydrogen 0.15.0 → 0.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,27 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.16.0
4
+
5
+ ### Patch Changes
6
+
7
+ - [#1082](https://github.com/Shopify/hydrogen/pull/1082) [`bd14340c`](https://github.com/Shopify/hydrogen/commit/bd14340c3099a0bf375a5879410cdf0697ed22f6) Thanks [@jplhomer](https://github.com/jplhomer)! - Update `useUrl()` to allow a developer to subscribe to a reactive version of the current router location.
8
+
9
+ Example:
10
+
11
+ ```jsx
12
+ import {useUrl} from '@shopify/hydrogen/client';
13
+
14
+ function MyClientComponent() {
15
+ const url = useUrl();
16
+
17
+ useEffect(() => {
18
+ // Record navigation, analytics, etc
19
+ }, [url]);
20
+ }
21
+ ```
22
+
23
+ * [#1075](https://github.com/Shopify/hydrogen/pull/1075) [`05dea552`](https://github.com/Shopify/hydrogen/commit/05dea552c90862a125b5111993003355a019b556) Thanks [@jplhomer](https://github.com/jplhomer)! - Properly set buyer IP and secret token for API Route queryShop helper
24
+
3
25
  ## 0.15.0
4
26
 
5
27
  ### Minor Changes
@@ -2,6 +2,7 @@ export * from './components';
2
2
  export * from './hooks';
3
3
  export * from './foundation/useServerState';
4
4
  export * from './foundation/useShop';
5
+ export * from './foundation/useUrl';
5
6
  export * from './foundation/ServerStateProvider';
6
7
  export { Head } from './foundation/Head';
7
8
  export * from './utilities';
@@ -2,6 +2,7 @@ export * from './components';
2
2
  export * from './hooks';
3
3
  export * from './foundation/useServerState';
4
4
  export * from './foundation/useShop';
5
+ export * from './foundation/useUrl';
5
6
  export * from './foundation/ServerStateProvider';
6
7
  export { Head } from './foundation/Head';
7
8
  export * from './utilities';
@@ -1,3 +1,7 @@
1
1
  export declare const RSC_PATHNAME = "/__rsc";
2
2
  export declare const EVENT_PATHNAME = "/__event";
3
3
  export declare const EVENT_PATHNAME_REGEX: RegExp;
4
+ export declare const OXYGEN_SECRET_TOKEN_ENVIRONMENT_VARIABLE = "SHOPIFY_STOREFRONT_API_SECRET_TOKEN";
5
+ export declare const STOREFRONT_API_SECRET_TOKEN_HEADER = "Shopify-Storefront-Private-Token";
6
+ export declare const STOREFRONT_API_PUBLIC_TOKEN_HEADER = "X-Shopify-Storefront-Access-Token";
7
+ export declare const STOREFRONT_API_BUYER_IP_HEADER = "Shopify-Storefront-Buyer-IP";
@@ -1,3 +1,7 @@
1
1
  export const RSC_PATHNAME = '/__rsc';
2
2
  export const EVENT_PATHNAME = '/__event';
3
3
  export const EVENT_PATHNAME_REGEX = new RegExp(`^${EVENT_PATHNAME}\/`);
4
+ export const OXYGEN_SECRET_TOKEN_ENVIRONMENT_VARIABLE = 'SHOPIFY_STOREFRONT_API_SECRET_TOKEN';
5
+ export const STOREFRONT_API_SECRET_TOKEN_HEADER = 'Shopify-Storefront-Private-Token';
6
+ export const STOREFRONT_API_PUBLIC_TOKEN_HEADER = 'X-Shopify-Storefront-Access-Token';
7
+ export const STOREFRONT_API_BUYER_IP_HEADER = 'Shopify-Storefront-Buyer-IP';
@@ -1,10 +1,13 @@
1
+ import { useMemo } from 'react';
1
2
  import { RSC_PATHNAME } from '../../constants';
3
+ import { useLocation } from '../Router/BrowserRouter.client';
2
4
  import { useEnvContext, META_ENV_SSR } from '../ssr-interop';
3
5
  /**
4
6
  * The `useUrl` hook retrieves the current URL in a server or client component.
5
7
  */
6
8
  export function useUrl() {
7
9
  var _a, _b;
10
+ const location = useLocation();
8
11
  if (META_ENV_SSR) {
9
12
  const serverUrl = new URL(useEnvContext((req) => req.url));
10
13
  if (serverUrl.pathname === RSC_PATHNAME) {
@@ -14,5 +17,9 @@ export function useUrl() {
14
17
  }
15
18
  return new URL(serverUrl);
16
19
  }
17
- return new URL(window.location.href);
20
+ /**
21
+ * We return a `URL` object instead of passing through `location` because
22
+ * the URL object contains important info like hostname, etc.
23
+ */
24
+ return useMemo(() => new URL(window.location.href), [location]);
18
25
  }
@@ -7,6 +7,7 @@ import { injectGraphQLTracker } from '../../utilities/graphql-tracker';
7
7
  import { sendMessageToClient } from '../../utilities/devtools';
8
8
  import { fetchSync } from '../../foundation/fetchSync/server/fetchSync';
9
9
  import { META_ENV_SSR } from '../../foundation/ssr-interop';
10
+ import { getStorefrontApiRequestHeaders } from '../../utilities/storefrontApi';
10
11
  // Check if the response body has GraphQL errors
11
12
  // https://spec.graphql.org/June2018/#sec-Response-Format
12
13
  const shouldCacheResponse = ([body]) => { var _a; return !((_a = JSON.parse(body)) === null || _a === void 0 ? void 0 : _a.errors); };
@@ -114,26 +115,13 @@ export function useShopQuery({ query, variables = {}, cache, preload = false, })
114
115
  return data;
115
116
  }
116
117
  function useCreateShopRequest(body) {
117
- var _a;
118
118
  const { storeDomain, storefrontToken, storefrontApiVersion } = useShop();
119
119
  const request = useServerRequest();
120
- const secretToken = typeof Oxygen !== 'undefined'
121
- ? (_a = Oxygen === null || Oxygen === void 0 ? void 0 : Oxygen.env) === null || _a === void 0 ? void 0 : _a.SHOPIFY_STOREFRONT_API_SECRET_TOKEN
122
- : null;
123
120
  const buyerIp = request.getBuyerIp();
124
- const extraHeaders = {};
125
- /**
126
- * Only pass one type of storefront token at a time.
127
- */
128
- if (secretToken) {
129
- extraHeaders['Shopify-Storefront-Private-Token'] = secretToken;
130
- }
131
- else {
132
- extraHeaders['X-Shopify-Storefront-Access-Token'] = storefrontToken;
133
- }
134
- if (buyerIp) {
135
- extraHeaders['Shopify-Storefront-Buyer-IP'] = buyerIp;
136
- }
121
+ const extraHeaders = getStorefrontApiRequestHeaders({
122
+ buyerIp,
123
+ storefrontToken,
124
+ });
137
125
  return {
138
126
  key: [storeDomain, storefrontApiVersion, body],
139
127
  url: `https://${storeDomain}/api/${storefrontApiVersion}/graphql.json`,
@@ -1,4 +1,5 @@
1
1
  import { ImportGlobEagerOutput, ShopifyConfig } from '../types';
2
+ import type { ServerComponentRequest } from '../framework/Hydration/ServerComponentRequest.server';
2
3
  import type { ASTNode } from 'graphql';
3
4
  declare type RouteParams = Record<string, string>;
4
5
  declare type RequestOptions = {
@@ -29,8 +30,6 @@ interface QueryShopArgs {
29
30
  query: ASTNode | string;
30
31
  /** An object of the variables for the GraphQL query. */
31
32
  variables?: Record<string, any>;
32
- /** A string corresponding to a valid locale identifier like `en-us` used to make the request. */
33
- locale?: string;
34
33
  }
35
- export declare function renderApiRoute(request: Request, route: ApiRouteMatch, shopifyConfig: ShopifyConfig): Promise<Response | Request>;
34
+ export declare function renderApiRoute(request: ServerComponentRequest, route: ApiRouteMatch, shopifyConfig: ShopifyConfig): Promise<Response | Request>;
36
35
  export {};
@@ -1,6 +1,7 @@
1
1
  import { matchPath } from './matchPath';
2
2
  import { getLoggerWithContext, logServerResponse } from '../utilities/log/';
3
3
  import { fetchBuilder, graphqlRequestBody } from './fetch';
4
+ import { getStorefrontApiRequestHeaders } from './storefrontApi';
4
5
  let memoizedRoutes = [];
5
6
  let memoizedPages = {};
6
7
  export function getApiRoutes(pages, topLevelPath = '*') {
@@ -64,17 +65,20 @@ export function getApiRouteFromURL(url, routes) {
64
65
  hasServerComponent: foundRoute.hasServerComponent,
65
66
  };
66
67
  }
67
- function queryShopBuilder(shopifyConfig) {
68
- return async function queryShop({ query, variables, locale, }) {
69
- var _a;
70
- const { storeDomain, storefrontApiVersion, storefrontToken, defaultLocale } = shopifyConfig;
68
+ function queryShopBuilder(shopifyConfig, request) {
69
+ return async function queryShop({ query, variables, }) {
70
+ const { storeDomain, storefrontApiVersion, storefrontToken } = shopifyConfig;
71
+ const buyerIp = request.getBuyerIp();
72
+ const extraHeaders = getStorefrontApiRequestHeaders({
73
+ buyerIp,
74
+ storefrontToken,
75
+ });
71
76
  const fetcher = fetchBuilder(`https://${storeDomain}/api/${storefrontApiVersion}/graphql.json`, {
72
77
  method: 'POST',
73
78
  body: graphqlRequestBody(query, variables),
74
79
  headers: {
75
- 'X-Shopify-Storefront-Access-Token': storefrontToken,
76
- 'Accept-Language': (_a = locale) !== null && _a !== void 0 ? _a : defaultLocale,
77
80
  'Content-Type': 'application/json',
81
+ ...extraHeaders,
78
82
  },
79
83
  });
80
84
  return await fetcher();
@@ -87,7 +91,7 @@ export async function renderApiRoute(request, route, shopifyConfig) {
87
91
  try {
88
92
  response = await route.resource(request, {
89
93
  params: route.params,
90
- queryShop: queryShopBuilder(shopifyConfig),
94
+ queryShop: queryShopBuilder(shopifyConfig, request),
91
95
  });
92
96
  if (!(response instanceof Response || response instanceof Request)) {
93
97
  if (typeof response === 'string' || response instanceof String) {
@@ -0,0 +1,4 @@
1
+ export declare function getStorefrontApiRequestHeaders({ buyerIp, storefrontToken, }: {
2
+ buyerIp?: string | null;
3
+ storefrontToken: string;
4
+ }): Record<string, any>;
@@ -0,0 +1,21 @@
1
+ import { OXYGEN_SECRET_TOKEN_ENVIRONMENT_VARIABLE, STOREFRONT_API_SECRET_TOKEN_HEADER, STOREFRONT_API_PUBLIC_TOKEN_HEADER, STOREFRONT_API_BUYER_IP_HEADER, } from '../constants';
2
+ export function getStorefrontApiRequestHeaders({ buyerIp, storefrontToken, }) {
3
+ var _a;
4
+ const headers = {};
5
+ const secretToken = typeof Oxygen !== 'undefined'
6
+ ? (_a = Oxygen === null || Oxygen === void 0 ? void 0 : Oxygen.env) === null || _a === void 0 ? void 0 : _a[OXYGEN_SECRET_TOKEN_ENVIRONMENT_VARIABLE]
7
+ : null;
8
+ /**
9
+ * Only pass one type of storefront token at a time.
10
+ */
11
+ if (secretToken) {
12
+ headers[STOREFRONT_API_SECRET_TOKEN_HEADER] = secretToken;
13
+ }
14
+ else {
15
+ headers[STOREFRONT_API_PUBLIC_TOKEN_HEADER] = storefrontToken;
16
+ }
17
+ if (buyerIp) {
18
+ headers[STOREFRONT_API_BUYER_IP_HEADER] = buyerIp;
19
+ }
20
+ return headers;
21
+ }
@@ -1 +1 @@
1
- export declare const LIB_VERSION = "0.15.0";
1
+ export declare const LIB_VERSION = "0.16.0";
@@ -1 +1 @@
1
- export const LIB_VERSION = '0.15.0';
1
+ export const LIB_VERSION = '0.16.0';
@@ -1,3 +1,7 @@
1
1
  export declare const RSC_PATHNAME = "/__rsc";
2
2
  export declare const EVENT_PATHNAME = "/__event";
3
3
  export declare const EVENT_PATHNAME_REGEX: RegExp;
4
+ export declare const OXYGEN_SECRET_TOKEN_ENVIRONMENT_VARIABLE = "SHOPIFY_STOREFRONT_API_SECRET_TOKEN";
5
+ export declare const STOREFRONT_API_SECRET_TOKEN_HEADER = "Shopify-Storefront-Private-Token";
6
+ export declare const STOREFRONT_API_PUBLIC_TOKEN_HEADER = "X-Shopify-Storefront-Access-Token";
7
+ export declare const STOREFRONT_API_BUYER_IP_HEADER = "Shopify-Storefront-Buyer-IP";
@@ -1,6 +1,10 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.EVENT_PATHNAME_REGEX = exports.EVENT_PATHNAME = exports.RSC_PATHNAME = void 0;
3
+ exports.STOREFRONT_API_BUYER_IP_HEADER = exports.STOREFRONT_API_PUBLIC_TOKEN_HEADER = exports.STOREFRONT_API_SECRET_TOKEN_HEADER = exports.OXYGEN_SECRET_TOKEN_ENVIRONMENT_VARIABLE = exports.EVENT_PATHNAME_REGEX = exports.EVENT_PATHNAME = exports.RSC_PATHNAME = void 0;
4
4
  exports.RSC_PATHNAME = '/__rsc';
5
5
  exports.EVENT_PATHNAME = '/__event';
6
6
  exports.EVENT_PATHNAME_REGEX = new RegExp(`^${exports.EVENT_PATHNAME}\/`);
7
+ exports.OXYGEN_SECRET_TOKEN_ENVIRONMENT_VARIABLE = 'SHOPIFY_STOREFRONT_API_SECRET_TOKEN';
8
+ exports.STOREFRONT_API_SECRET_TOKEN_HEADER = 'Shopify-Storefront-Private-Token';
9
+ exports.STOREFRONT_API_PUBLIC_TOKEN_HEADER = 'X-Shopify-Storefront-Access-Token';
10
+ exports.STOREFRONT_API_BUYER_IP_HEADER = 'Shopify-Storefront-Buyer-IP';
@@ -1,4 +1,5 @@
1
1
  import { ImportGlobEagerOutput, ShopifyConfig } from '../types';
2
+ import type { ServerComponentRequest } from '../framework/Hydration/ServerComponentRequest.server';
2
3
  import type { ASTNode } from 'graphql';
3
4
  declare type RouteParams = Record<string, string>;
4
5
  declare type RequestOptions = {
@@ -29,8 +30,6 @@ interface QueryShopArgs {
29
30
  query: ASTNode | string;
30
31
  /** An object of the variables for the GraphQL query. */
31
32
  variables?: Record<string, any>;
32
- /** A string corresponding to a valid locale identifier like `en-us` used to make the request. */
33
- locale?: string;
34
33
  }
35
- export declare function renderApiRoute(request: Request, route: ApiRouteMatch, shopifyConfig: ShopifyConfig): Promise<Response | Request>;
34
+ export declare function renderApiRoute(request: ServerComponentRequest, route: ApiRouteMatch, shopifyConfig: ShopifyConfig): Promise<Response | Request>;
36
35
  export {};
@@ -4,6 +4,7 @@ exports.renderApiRoute = exports.getApiRouteFromURL = exports.getApiRoutes = voi
4
4
  const matchPath_1 = require("./matchPath");
5
5
  const log_1 = require("../utilities/log/");
6
6
  const fetch_1 = require("./fetch");
7
+ const storefrontApi_1 = require("./storefrontApi");
7
8
  let memoizedRoutes = [];
8
9
  let memoizedPages = {};
9
10
  function getApiRoutes(pages, topLevelPath = '*') {
@@ -69,17 +70,20 @@ function getApiRouteFromURL(url, routes) {
69
70
  };
70
71
  }
71
72
  exports.getApiRouteFromURL = getApiRouteFromURL;
72
- function queryShopBuilder(shopifyConfig) {
73
- return async function queryShop({ query, variables, locale, }) {
74
- var _a;
75
- const { storeDomain, storefrontApiVersion, storefrontToken, defaultLocale } = shopifyConfig;
73
+ function queryShopBuilder(shopifyConfig, request) {
74
+ return async function queryShop({ query, variables, }) {
75
+ const { storeDomain, storefrontApiVersion, storefrontToken } = shopifyConfig;
76
+ const buyerIp = request.getBuyerIp();
77
+ const extraHeaders = (0, storefrontApi_1.getStorefrontApiRequestHeaders)({
78
+ buyerIp,
79
+ storefrontToken,
80
+ });
76
81
  const fetcher = (0, fetch_1.fetchBuilder)(`https://${storeDomain}/api/${storefrontApiVersion}/graphql.json`, {
77
82
  method: 'POST',
78
83
  body: (0, fetch_1.graphqlRequestBody)(query, variables),
79
84
  headers: {
80
- 'X-Shopify-Storefront-Access-Token': storefrontToken,
81
- 'Accept-Language': (_a = locale) !== null && _a !== void 0 ? _a : defaultLocale,
82
85
  'Content-Type': 'application/json',
86
+ ...extraHeaders,
83
87
  },
84
88
  });
85
89
  return await fetcher();
@@ -92,7 +96,7 @@ async function renderApiRoute(request, route, shopifyConfig) {
92
96
  try {
93
97
  response = await route.resource(request, {
94
98
  params: route.params,
95
- queryShop: queryShopBuilder(shopifyConfig),
99
+ queryShop: queryShopBuilder(shopifyConfig, request),
96
100
  });
97
101
  if (!(response instanceof Response || response instanceof Request)) {
98
102
  if (typeof response === 'string' || response instanceof String) {
@@ -0,0 +1,4 @@
1
+ export declare function getStorefrontApiRequestHeaders({ buyerIp, storefrontToken, }: {
2
+ buyerIp?: string | null;
3
+ storefrontToken: string;
4
+ }): Record<string, any>;
@@ -0,0 +1,25 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getStorefrontApiRequestHeaders = void 0;
4
+ const constants_1 = require("../constants");
5
+ function getStorefrontApiRequestHeaders({ buyerIp, storefrontToken, }) {
6
+ var _a;
7
+ const headers = {};
8
+ const secretToken = typeof Oxygen !== 'undefined'
9
+ ? (_a = Oxygen === null || Oxygen === void 0 ? void 0 : Oxygen.env) === null || _a === void 0 ? void 0 : _a[constants_1.OXYGEN_SECRET_TOKEN_ENVIRONMENT_VARIABLE]
10
+ : null;
11
+ /**
12
+ * Only pass one type of storefront token at a time.
13
+ */
14
+ if (secretToken) {
15
+ headers[constants_1.STOREFRONT_API_SECRET_TOKEN_HEADER] = secretToken;
16
+ }
17
+ else {
18
+ headers[constants_1.STOREFRONT_API_PUBLIC_TOKEN_HEADER] = storefrontToken;
19
+ }
20
+ if (buyerIp) {
21
+ headers[constants_1.STOREFRONT_API_BUYER_IP_HEADER] = buyerIp;
22
+ }
23
+ return headers;
24
+ }
25
+ exports.getStorefrontApiRequestHeaders = getStorefrontApiRequestHeaders;
@@ -1 +1 @@
1
- export declare const LIB_VERSION = "0.15.0";
1
+ export declare const LIB_VERSION = "0.16.0";
@@ -1,4 +1,4 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.LIB_VERSION = void 0;
4
- exports.LIB_VERSION = '0.15.0';
4
+ exports.LIB_VERSION = '0.16.0';
package/package.json CHANGED
@@ -7,7 +7,7 @@
7
7
  "engines": {
8
8
  "node": ">=14"
9
9
  },
10
- "version": "0.15.0",
10
+ "version": "0.16.0",
11
11
  "description": "Modern custom Shopify storefronts",
12
12
  "license": "MIT",
13
13
  "main": "dist/esnext/index.js",