@shopify/hydrogen 2026.1.2 → 2026.1.4
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/dist/development/index.cjs +107 -28
- package/dist/development/index.cjs.map +1 -1
- package/dist/development/index.js +107 -28
- package/dist/development/index.js.map +1 -1
- package/dist/oxygen/index.js +5 -0
- package/dist/production/index.cjs +45 -45
- package/dist/production/index.cjs.map +1 -1
- package/dist/production/index.d.cts +17 -1
- package/dist/production/index.d.ts +17 -1
- package/dist/production/index.js +45 -45
- package/dist/production/index.js.map +1 -1
- package/dist/vite/plugin.js +3 -7
- package/package.json +4 -4
|
@@ -434,7 +434,7 @@ function getPrivacyBanner() {
|
|
|
434
434
|
}
|
|
435
435
|
|
|
436
436
|
// package.json
|
|
437
|
-
var version = "2026.1.
|
|
437
|
+
var version = "2026.1.4";
|
|
438
438
|
|
|
439
439
|
// src/analytics-manager/ShopifyAnalytics.tsx
|
|
440
440
|
function getCustomerPrivacyRequired() {
|
|
@@ -1150,6 +1150,7 @@ function getStorefrontHeaders(request) {
|
|
|
1150
1150
|
};
|
|
1151
1151
|
}
|
|
1152
1152
|
var SFAPI_RE = /^\/api\/(unstable|2\d{3}-\d{2})\/graphql\.json$/;
|
|
1153
|
+
var MCP_RE = /^\/api\/mcp$/;
|
|
1153
1154
|
var getSafePathname = (url) => {
|
|
1154
1155
|
try {
|
|
1155
1156
|
return new URL(url, "http://e.c").pathname;
|
|
@@ -1829,7 +1830,7 @@ function generateUUID() {
|
|
|
1829
1830
|
}
|
|
1830
1831
|
|
|
1831
1832
|
// src/version.ts
|
|
1832
|
-
var LIB_VERSION = "2026.1.
|
|
1833
|
+
var LIB_VERSION = "2026.1.4";
|
|
1833
1834
|
|
|
1834
1835
|
// src/utils/graphql.ts
|
|
1835
1836
|
function minifyQuery(string) {
|
|
@@ -2254,6 +2255,70 @@ function createStorefrontClient(options) {
|
|
|
2254
2255
|
);
|
|
2255
2256
|
return new Response(sfapiResponse.body, sfapiResponse);
|
|
2256
2257
|
},
|
|
2258
|
+
/**
|
|
2259
|
+
* Checks if the request is targeting the Storefront MCP endpoint.
|
|
2260
|
+
*/
|
|
2261
|
+
isMcpUrl(request) {
|
|
2262
|
+
return MCP_RE.test(getSafePathname(request.url ?? ""));
|
|
2263
|
+
},
|
|
2264
|
+
/**
|
|
2265
|
+
* Forwards the request to the Storefront MCP endpoint.
|
|
2266
|
+
* CORS headers are intentionally omitted — the Storefront MCP
|
|
2267
|
+
* server is server-to-server only (OPTIONS preflight returns 404).
|
|
2268
|
+
*/
|
|
2269
|
+
async forwardMcp(request) {
|
|
2270
|
+
const forwardedHeaders = new Headers([
|
|
2271
|
+
...extractHeaders(
|
|
2272
|
+
(key) => request.headers.get(key),
|
|
2273
|
+
[
|
|
2274
|
+
"accept",
|
|
2275
|
+
"accept-encoding",
|
|
2276
|
+
"accept-language",
|
|
2277
|
+
"content-type",
|
|
2278
|
+
"cookie",
|
|
2279
|
+
"origin",
|
|
2280
|
+
"referer",
|
|
2281
|
+
"user-agent"
|
|
2282
|
+
]
|
|
2283
|
+
),
|
|
2284
|
+
...extractHeaders(
|
|
2285
|
+
(key) => defaultHeaders[key],
|
|
2286
|
+
[
|
|
2287
|
+
SHOPIFY_CLIENT_IP_HEADER,
|
|
2288
|
+
SHOPIFY_CLIENT_IP_SIG_HEADER,
|
|
2289
|
+
STOREFRONT_ACCESS_TOKEN_HEADER,
|
|
2290
|
+
STOREFRONT_REQUEST_GROUP_ID_HEADER,
|
|
2291
|
+
SHOPIFY_STOREFRONT_ID_HEADER
|
|
2292
|
+
]
|
|
2293
|
+
)
|
|
2294
|
+
]);
|
|
2295
|
+
if (storefrontHeaders?.buyerIp) {
|
|
2296
|
+
forwardedHeaders.set("x-forwarded-for", storefrontHeaders.buyerIp);
|
|
2297
|
+
}
|
|
2298
|
+
const mcpUrl = `${getShopifyDomain()}/api/mcp`;
|
|
2299
|
+
try {
|
|
2300
|
+
const mcpResponse = await fetch(mcpUrl, {
|
|
2301
|
+
method: request.method,
|
|
2302
|
+
body: request.body,
|
|
2303
|
+
headers: forwardedHeaders
|
|
2304
|
+
});
|
|
2305
|
+
return new Response(mcpResponse.body, mcpResponse);
|
|
2306
|
+
} catch (error) {
|
|
2307
|
+
const JSON_RPC_INTERNAL_ERROR = -32603;
|
|
2308
|
+
const message = error instanceof Error ? error.message : "Internal proxy error";
|
|
2309
|
+
return new Response(
|
|
2310
|
+
JSON.stringify({
|
|
2311
|
+
jsonrpc: "2.0",
|
|
2312
|
+
error: { code: JSON_RPC_INTERNAL_ERROR, message },
|
|
2313
|
+
id: null
|
|
2314
|
+
}),
|
|
2315
|
+
{
|
|
2316
|
+
status: 502,
|
|
2317
|
+
headers: { "content-type": "application/json" }
|
|
2318
|
+
}
|
|
2319
|
+
);
|
|
2320
|
+
}
|
|
2321
|
+
},
|
|
2257
2322
|
setCollectedSubrequestHeaders: (response) => {
|
|
2258
2323
|
if (collectedSubrequestHeaders) {
|
|
2259
2324
|
for (const value of collectedSubrequestHeaders.setCookie) {
|
|
@@ -3747,29 +3812,32 @@ function createCustomerAccountHelper(customerApiVersion, shopId) {
|
|
|
3747
3812
|
|
|
3748
3813
|
// src/customer/customer.ts
|
|
3749
3814
|
var HYDROGEN_TUNNEL_DOMAIN_SUFFIX = ".tryhydrogen.dev";
|
|
3750
|
-
function
|
|
3751
|
-
|
|
3752
|
-
|
|
3753
|
-
|
|
3754
|
-
|
|
3755
|
-
|
|
3756
|
-
|
|
3757
|
-
|
|
3758
|
-
].join("\n\n"),
|
|
3759
|
-
{
|
|
3760
|
-
status: 400,
|
|
3761
|
-
headers: {
|
|
3762
|
-
"Content-Type": "text/plain; charset=utf-8"
|
|
3763
|
-
}
|
|
3764
|
-
}
|
|
3765
|
-
);
|
|
3766
|
-
}
|
|
3815
|
+
function checkTunnelDomain(hostname, useCustomAuthDomain, redirectUri) {
|
|
3816
|
+
if (hostname.endsWith(HYDROGEN_TUNNEL_DOMAIN_SUFFIX)) return;
|
|
3817
|
+
if (useCustomAuthDomain) {
|
|
3818
|
+
const redirectHint = redirectUri ? ` (${redirectUri})` : "";
|
|
3819
|
+
warnOnce(
|
|
3820
|
+
`[h2:warn:customerAccount] You are using a custom domain (${hostname}) instead of a Hydrogen dev tunnel. Make sure you have manually registered your redirect_uri${redirectHint} in your Customer Account API settings in the Shopify admin. See https://shopify.dev/docs/api/customer for details.`
|
|
3821
|
+
);
|
|
3822
|
+
return;
|
|
3767
3823
|
}
|
|
3824
|
+
throw new Response(
|
|
3825
|
+
[
|
|
3826
|
+
"Customer Account API OAuth requires a Hydrogen tunnel in local development.",
|
|
3827
|
+
"Run the development server with the `--customer-account-push` flag,",
|
|
3828
|
+
`then open the tunnel URL shown in your terminal (\`https://*${HYDROGEN_TUNNEL_DOMAIN_SUFFIX}\`) instead of localhost.`
|
|
3829
|
+
].join("\n\n"),
|
|
3830
|
+
{
|
|
3831
|
+
status: 400,
|
|
3832
|
+
headers: {
|
|
3833
|
+
"Content-Type": "text/plain; charset=utf-8"
|
|
3834
|
+
}
|
|
3835
|
+
}
|
|
3836
|
+
);
|
|
3768
3837
|
}
|
|
3769
3838
|
function defaultAuthStatusHandler(request, defaultLoginUrl) {
|
|
3770
3839
|
if (!request.url) return defaultLoginUrl;
|
|
3771
|
-
const {
|
|
3772
|
-
throwIfNotTunnelled(hostname);
|
|
3840
|
+
const { pathname } = new URL(request.url);
|
|
3773
3841
|
const cleanedPathname = pathname.replace(/\.data$/, "").replace(/\/_root$/, "/").replace(/(.+)\/$/, "$1");
|
|
3774
3842
|
const redirectTo = defaultLoginUrl + `?${new URLSearchParams({ return_to: cleanedPathname }).toString()}`;
|
|
3775
3843
|
return redirect(redirectTo);
|
|
@@ -3787,7 +3855,8 @@ function createCustomerAccountClient({
|
|
|
3787
3855
|
loginPath = "/account/login",
|
|
3788
3856
|
authorizePath = "/account/authorize",
|
|
3789
3857
|
defaultRedirectPath = "/account",
|
|
3790
|
-
language
|
|
3858
|
+
language,
|
|
3859
|
+
useCustomAuthDomain
|
|
3791
3860
|
}) {
|
|
3792
3861
|
if (customerApiVersion !== DEFAULT_CUSTOMER_API_VERSION) {
|
|
3793
3862
|
console.warn(
|
|
@@ -3804,7 +3873,6 @@ function createCustomerAccountClient({
|
|
|
3804
3873
|
"[h2:error:createCustomerAccountClient] The request object does not contain a URL."
|
|
3805
3874
|
);
|
|
3806
3875
|
}
|
|
3807
|
-
const authStatusHandler = customAuthStatusHandler ? customAuthStatusHandler : () => defaultAuthStatusHandler(request, loginPath);
|
|
3808
3876
|
const requestUrl = new URL(request.url);
|
|
3809
3877
|
const httpsOrigin = requestUrl.protocol === "http:" ? requestUrl.origin.replace("http", "https") : requestUrl.origin;
|
|
3810
3878
|
const redirectUri = ensureLocalRedirectUrl({
|
|
@@ -3812,6 +3880,11 @@ function createCustomerAccountClient({
|
|
|
3812
3880
|
defaultUrl: authorizePath,
|
|
3813
3881
|
redirectUrl: authUrl
|
|
3814
3882
|
});
|
|
3883
|
+
const ensureTunnel = (hostname) => checkTunnelDomain(hostname, useCustomAuthDomain, redirectUri);
|
|
3884
|
+
const authStatusHandler = customAuthStatusHandler ? customAuthStatusHandler : () => {
|
|
3885
|
+
ensureTunnel(requestUrl.hostname);
|
|
3886
|
+
return defaultAuthStatusHandler(request, loginPath);
|
|
3887
|
+
};
|
|
3815
3888
|
const getCustomerAccountUrl = createCustomerAccountHelper(
|
|
3816
3889
|
customerApiVersion,
|
|
3817
3890
|
shopId
|
|
@@ -3933,7 +4006,7 @@ function createCustomerAccountClient({
|
|
|
3933
4006
|
return session.get(CUSTOMER_ACCOUNT_SESSION_KEY)?.accessToken;
|
|
3934
4007
|
}
|
|
3935
4008
|
async function mutate(mutation, options) {
|
|
3936
|
-
|
|
4009
|
+
ensureTunnel(requestUrl.hostname);
|
|
3937
4010
|
ifInvalidCredentialThrowError();
|
|
3938
4011
|
mutation = minifyQuery(mutation);
|
|
3939
4012
|
assertMutation(mutation, "customer.mutate");
|
|
@@ -3943,7 +4016,7 @@ function createCustomerAccountClient({
|
|
|
3943
4016
|
);
|
|
3944
4017
|
}
|
|
3945
4018
|
async function query(query2, options) {
|
|
3946
|
-
|
|
4019
|
+
ensureTunnel(requestUrl.hostname);
|
|
3947
4020
|
ifInvalidCredentialThrowError();
|
|
3948
4021
|
query2 = minifyQuery(query2);
|
|
3949
4022
|
assertQuery(query2, "customer.query");
|
|
@@ -3967,7 +4040,7 @@ function createCustomerAccountClient({
|
|
|
3967
4040
|
return {
|
|
3968
4041
|
i18n: { language: language ?? "EN" },
|
|
3969
4042
|
login: async (options) => {
|
|
3970
|
-
|
|
4043
|
+
ensureTunnel(requestUrl.hostname);
|
|
3971
4044
|
ifInvalidCredentialThrowError();
|
|
3972
4045
|
const loginUrl = new URL(getCustomerAccountUrl("AUTH" /* AUTH */));
|
|
3973
4046
|
const state = generateState();
|
|
@@ -4019,7 +4092,7 @@ function createCustomerAccountClient({
|
|
|
4019
4092
|
return redirect(loginUrl.toString());
|
|
4020
4093
|
},
|
|
4021
4094
|
logout: async (options) => {
|
|
4022
|
-
|
|
4095
|
+
ensureTunnel(requestUrl.hostname);
|
|
4023
4096
|
ifInvalidCredentialThrowError();
|
|
4024
4097
|
const idToken = session.get(CUSTOMER_ACCOUNT_SESSION_KEY)?.idToken;
|
|
4025
4098
|
const postLogoutRedirectUri = ensureLocalRedirectUrl({
|
|
@@ -4054,7 +4127,7 @@ function createCustomerAccountClient({
|
|
|
4054
4127
|
mutate,
|
|
4055
4128
|
query,
|
|
4056
4129
|
authorize: async () => {
|
|
4057
|
-
|
|
4130
|
+
ensureTunnel(requestUrl.hostname);
|
|
4058
4131
|
ifInvalidCredentialThrowError();
|
|
4059
4132
|
const code = requestUrl.searchParams.get("code");
|
|
4060
4133
|
const state = requestUrl.searchParams.get("state");
|
|
@@ -4260,6 +4333,7 @@ function createHydrogenContext(options, additionalContext) {
|
|
|
4260
4333
|
customerApiVersion: customerAccountOptions?.apiVersion,
|
|
4261
4334
|
authUrl: customerAccountOptions?.authUrl,
|
|
4262
4335
|
customAuthStatusHandler: customerAccountOptions?.customAuthStatusHandler,
|
|
4336
|
+
useCustomAuthDomain: customerAccountOptions?.useCustomAuthDomain,
|
|
4263
4337
|
// locale - i18n.language is a union of StorefrontLanguageCode | CustomerLanguageCode
|
|
4264
4338
|
// We cast here because createCustomerAccountClient expects CustomerLanguageCode specifically,
|
|
4265
4339
|
// but the union type is compatible since most language codes overlap between the two APIs
|
|
@@ -4372,6 +4446,11 @@ function createRequestHandler({
|
|
|
4372
4446
|
appendPoweredByHeader?.(response2);
|
|
4373
4447
|
return response2;
|
|
4374
4448
|
}
|
|
4449
|
+
if (storefront?.isMcpUrl(request)) {
|
|
4450
|
+
const response2 = await storefront.forwardMcp(request);
|
|
4451
|
+
appendPoweredByHeader?.(response2);
|
|
4452
|
+
return response2;
|
|
4453
|
+
}
|
|
4375
4454
|
}
|
|
4376
4455
|
const response = await handleRequest(request, context);
|
|
4377
4456
|
if (storefront && proxyStandardRoutes) {
|