@shopify/hydrogen 2026.1.4 → 2026.4.1

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.
@@ -1,4 +1,5 @@
1
1
  import { ServerBuild } from 'react-router';
2
+ import { CountryCode, LanguageCode } from '@shopify/hydrogen-react/storefront-api-types';
2
3
 
3
4
  type CreateRequestHandlerOptions<Context = unknown> = {
4
5
  /** React Router's server build */
@@ -23,21 +24,14 @@ type CreateRequestHandlerOptions<Context = unknown> = {
23
24
  * @default true
24
25
  */
25
26
  collectTrackingInformation?: boolean;
26
- /**
27
- * Whether to proxy standard routes such as `/api/.../graphql.json` (Storefront API).
28
- * You can disable this if you are handling these routes yourself. Ensure that
29
- * the proxy works if you rely on Hydrogen's built-in behaviors such as analytics.
30
- * @default true
31
- */
32
- proxyStandardRoutes?: boolean;
33
27
  };
34
28
  /**
35
29
  * Creates a request handler for Hydrogen apps using React Router.
30
+ * @publicDocs
36
31
  */
37
- declare function createRequestHandler<Context = unknown>({ build, mode, poweredByHeader, getLoadContext, collectTrackingInformation, proxyStandardRoutes, }: CreateRequestHandlerOptions<Context>): (request: Request) => Promise<Response>;
32
+ declare function createRequestHandler<Context = unknown>({ build, mode, poweredByHeader, getLoadContext, collectTrackingInformation, }: CreateRequestHandlerOptions<Context>): (request: Request) => Promise<Response>;
38
33
 
39
34
  type RequestEventPayload = {
40
- __fromVite?: boolean;
41
35
  url: string;
42
36
  eventType: 'request' | 'subrequest';
43
37
  requestId?: string | null;
@@ -65,6 +59,63 @@ type RequestEventPayload = {
65
59
  displayName?: string;
66
60
  };
67
61
 
62
+ type ConsentStatus = boolean | undefined;
63
+ type VisitorConsent = {
64
+ marketing: ConsentStatus;
65
+ analytics: ConsentStatus;
66
+ preferences: ConsentStatus;
67
+ sale_of_data: ConsentStatus;
68
+ };
69
+ type CustomerPrivacyConsentConfig = {
70
+ checkoutRootDomain: string;
71
+ storefrontRootDomain?: string;
72
+ storefrontAccessToken: string;
73
+ country?: CountryCode;
74
+ /** The privacyBanner refers to `language` as `locale` */
75
+ locale?: LanguageCode;
76
+ };
77
+ type SetConsentHeadlessParams = VisitorConsent & CustomerPrivacyConsentConfig & {
78
+ headlessStorefront?: boolean;
79
+ };
80
+ /**
81
+ Ideally this type should come from the Custoemr Privacy API sdk
82
+ analyticsProcessingAllowed -
83
+ currentVisitorConsent
84
+ doesMerchantSupportGranularConsent
85
+ firstPartyMarketingAllowed
86
+ getCCPAConsent
87
+ getTrackingConsent
88
+ marketingAllowed
89
+ preferencesProcessingAllowed
90
+ saleOfDataAllowed
91
+ saleOfDataRegion
92
+ setTrackingConsent
93
+ shouldShowBanner
94
+ shouldShowGDPRBanner
95
+ thirdPartyMarketingAllowed
96
+ **/
97
+ type OriginalCustomerPrivacy = {
98
+ currentVisitorConsent: () => VisitorConsent;
99
+ preferencesProcessingAllowed: () => boolean;
100
+ saleOfDataAllowed: () => boolean;
101
+ marketingAllowed: () => boolean;
102
+ analyticsProcessingAllowed: () => boolean;
103
+ setTrackingConsent: (consent: SetConsentHeadlessParams, callback: (data: {
104
+ error: string;
105
+ } | undefined) => void) => void;
106
+ shouldShowBanner: () => boolean;
107
+ };
108
+ type CustomerPrivacy = Omit<OriginalCustomerPrivacy, 'setTrackingConsent'> & {
109
+ setTrackingConsent: (consent: VisitorConsent, // we have already applied the headlessStorefront in the override
110
+ callback: (data: {
111
+ error: string;
112
+ } | undefined) => void) => void;
113
+ };
114
+ type PrivacyBanner = {
115
+ loadBanner: (options?: Partial<CustomerPrivacyConsentConfig>) => void;
116
+ showPreferences: (options?: Partial<CustomerPrivacyConsentConfig>) => void;
117
+ };
118
+
68
119
  type WaitUntil = (promise: Promise<unknown>) => void;
69
120
 
70
121
  type StorefrontHeaders = {
@@ -84,7 +135,9 @@ declare global {
84
135
  interface Window {
85
136
  privacyBanner: PrivacyBanner;
86
137
  Shopify: {
87
- customerPrivacy: CustomerPrivacy;
138
+ customerPrivacy?: Partial<CustomerPrivacy> & {
139
+ backendConsentEnabled?: boolean;
140
+ };
88
141
  };
89
142
  }
90
143
  interface Document {
@@ -27,23 +27,13 @@ function appendServerTimingHeader(response, values) {
27
27
  }
28
28
  }
29
29
 
30
- // src/utils/warning.ts
31
- var warnings = /* @__PURE__ */ new Set();
32
- var warnOnce = (string) => {
33
- if (!warnings.has(string)) {
34
- console.warn(string);
35
- warnings.add(string);
36
- }
37
- };
38
-
39
30
  // src/createRequestHandler.ts
40
31
  function createRequestHandler({
41
32
  build,
42
33
  mode,
43
34
  poweredByHeader = true,
44
35
  getLoadContext,
45
- collectTrackingInformation = true,
46
- proxyStandardRoutes = true
36
+ collectTrackingInformation = true
47
37
  }) {
48
38
  const handleRequest = createReactRouterRequestHandler(build, mode);
49
39
  const appendPoweredByHeader = poweredByHeader ? (response) => response.headers.append("powered-by", "Shopify, Hydrogen") : void 0;
@@ -65,32 +55,28 @@ function createRequestHandler({
65
55
  }
66
56
  const context = await getLoadContext?.(request);
67
57
  const storefront = context?.storefront || context?.get?.(storefrontContext);
68
- if (proxyStandardRoutes) {
69
- if (!storefront) {
70
- warnOnce(
71
- "[h2:createRequestHandler] Storefront instance is required to proxy standard routes."
72
- );
73
- }
74
- if (storefront?.isStorefrontApiUrl(request)) {
75
- const response2 = await storefront.forward(request);
76
- appendPoweredByHeader?.(response2);
77
- return response2;
78
- }
79
- if (storefront?.isMcpUrl(request)) {
80
- const response2 = await storefront.forwardMcp(request);
81
- appendPoweredByHeader?.(response2);
82
- return response2;
83
- }
58
+ if (!storefront) {
59
+ throw new Error(
60
+ "[h2:createRequestHandler] Storefront instance is required in the load context. Make sure to use createHydrogenContext() or provide a storefront instance via getLoadContext."
61
+ );
62
+ }
63
+ if (storefront.isStorefrontApiUrl(request)) {
64
+ const response2 = await storefront.forward(request);
65
+ appendPoweredByHeader?.(response2);
66
+ return response2;
67
+ }
68
+ if (storefront.isMcpUrl(request)) {
69
+ const response2 = await storefront.forwardMcp(request);
70
+ appendPoweredByHeader?.(response2);
71
+ return response2;
84
72
  }
85
73
  const response = await handleRequest(request, context);
86
- if (storefront && proxyStandardRoutes) {
87
- if (collectTrackingInformation) {
88
- storefront.setCollectedSubrequestHeaders(response);
89
- }
90
- const fetchDest = request.headers.get("sec-fetch-dest");
91
- if (fetchDest && fetchDest === "document" || request.headers.get("accept")?.includes("text/html")) {
92
- appendServerTimingHeader(response, { [HYDROGEN_SFAPI_PROXY_KEY]: "1" });
93
- }
74
+ if (collectTrackingInformation) {
75
+ storefront.setCollectedSubrequestHeaders(response);
76
+ }
77
+ const fetchDest = request.headers.get("sec-fetch-dest");
78
+ if (fetchDest && fetchDest === "document" || request.headers.get("accept")?.includes("text/html")) {
79
+ appendServerTimingHeader(response, { [HYDROGEN_SFAPI_PROXY_KEY]: "1" });
94
80
  }
95
81
  appendPoweredByHeader?.(response);
96
82
  return response;