@shopify/hydrogen 2026.1.3 → 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/package.json +3 -3
|
@@ -610,7 +610,7 @@ function getPrivacyBanner() {
|
|
|
610
610
|
}
|
|
611
611
|
|
|
612
612
|
// package.json
|
|
613
|
-
var version = "2026.1.
|
|
613
|
+
var version = "2026.1.4";
|
|
614
614
|
|
|
615
615
|
// src/analytics-manager/ShopifyAnalytics.tsx
|
|
616
616
|
function getCustomerPrivacyRequired() {
|
|
@@ -1326,6 +1326,7 @@ function getStorefrontHeaders(request) {
|
|
|
1326
1326
|
};
|
|
1327
1327
|
}
|
|
1328
1328
|
var SFAPI_RE = /^\/api\/(unstable|2\d{3}-\d{2})\/graphql\.json$/;
|
|
1329
|
+
var MCP_RE = /^\/api\/mcp$/;
|
|
1329
1330
|
var getSafePathname = (url) => {
|
|
1330
1331
|
try {
|
|
1331
1332
|
return new URL(url, "http://e.c").pathname;
|
|
@@ -2005,7 +2006,7 @@ function generateUUID() {
|
|
|
2005
2006
|
}
|
|
2006
2007
|
|
|
2007
2008
|
// src/version.ts
|
|
2008
|
-
var LIB_VERSION = "2026.1.
|
|
2009
|
+
var LIB_VERSION = "2026.1.4";
|
|
2009
2010
|
|
|
2010
2011
|
// src/utils/graphql.ts
|
|
2011
2012
|
function minifyQuery(string) {
|
|
@@ -2430,6 +2431,70 @@ function createStorefrontClient(options) {
|
|
|
2430
2431
|
);
|
|
2431
2432
|
return new Response(sfapiResponse.body, sfapiResponse);
|
|
2432
2433
|
},
|
|
2434
|
+
/**
|
|
2435
|
+
* Checks if the request is targeting the Storefront MCP endpoint.
|
|
2436
|
+
*/
|
|
2437
|
+
isMcpUrl(request) {
|
|
2438
|
+
return MCP_RE.test(getSafePathname(request.url ?? ""));
|
|
2439
|
+
},
|
|
2440
|
+
/**
|
|
2441
|
+
* Forwards the request to the Storefront MCP endpoint.
|
|
2442
|
+
* CORS headers are intentionally omitted — the Storefront MCP
|
|
2443
|
+
* server is server-to-server only (OPTIONS preflight returns 404).
|
|
2444
|
+
*/
|
|
2445
|
+
async forwardMcp(request) {
|
|
2446
|
+
const forwardedHeaders = new Headers([
|
|
2447
|
+
...extractHeaders(
|
|
2448
|
+
(key) => request.headers.get(key),
|
|
2449
|
+
[
|
|
2450
|
+
"accept",
|
|
2451
|
+
"accept-encoding",
|
|
2452
|
+
"accept-language",
|
|
2453
|
+
"content-type",
|
|
2454
|
+
"cookie",
|
|
2455
|
+
"origin",
|
|
2456
|
+
"referer",
|
|
2457
|
+
"user-agent"
|
|
2458
|
+
]
|
|
2459
|
+
),
|
|
2460
|
+
...extractHeaders(
|
|
2461
|
+
(key) => defaultHeaders[key],
|
|
2462
|
+
[
|
|
2463
|
+
SHOPIFY_CLIENT_IP_HEADER,
|
|
2464
|
+
SHOPIFY_CLIENT_IP_SIG_HEADER,
|
|
2465
|
+
STOREFRONT_ACCESS_TOKEN_HEADER,
|
|
2466
|
+
STOREFRONT_REQUEST_GROUP_ID_HEADER,
|
|
2467
|
+
hydrogenReact.SHOPIFY_STOREFRONT_ID_HEADER
|
|
2468
|
+
]
|
|
2469
|
+
)
|
|
2470
|
+
]);
|
|
2471
|
+
if (storefrontHeaders?.buyerIp) {
|
|
2472
|
+
forwardedHeaders.set("x-forwarded-for", storefrontHeaders.buyerIp);
|
|
2473
|
+
}
|
|
2474
|
+
const mcpUrl = `${getShopifyDomain()}/api/mcp`;
|
|
2475
|
+
try {
|
|
2476
|
+
const mcpResponse = await fetch(mcpUrl, {
|
|
2477
|
+
method: request.method,
|
|
2478
|
+
body: request.body,
|
|
2479
|
+
headers: forwardedHeaders
|
|
2480
|
+
});
|
|
2481
|
+
return new Response(mcpResponse.body, mcpResponse);
|
|
2482
|
+
} catch (error) {
|
|
2483
|
+
const JSON_RPC_INTERNAL_ERROR = -32603;
|
|
2484
|
+
const message = error instanceof Error ? error.message : "Internal proxy error";
|
|
2485
|
+
return new Response(
|
|
2486
|
+
JSON.stringify({
|
|
2487
|
+
jsonrpc: "2.0",
|
|
2488
|
+
error: { code: JSON_RPC_INTERNAL_ERROR, message },
|
|
2489
|
+
id: null
|
|
2490
|
+
}),
|
|
2491
|
+
{
|
|
2492
|
+
status: 502,
|
|
2493
|
+
headers: { "content-type": "application/json" }
|
|
2494
|
+
}
|
|
2495
|
+
);
|
|
2496
|
+
}
|
|
2497
|
+
},
|
|
2433
2498
|
setCollectedSubrequestHeaders: (response) => {
|
|
2434
2499
|
if (collectedSubrequestHeaders) {
|
|
2435
2500
|
for (const value of collectedSubrequestHeaders.setCookie) {
|
|
@@ -3923,29 +3988,32 @@ function createCustomerAccountHelper(customerApiVersion, shopId) {
|
|
|
3923
3988
|
|
|
3924
3989
|
// src/customer/customer.ts
|
|
3925
3990
|
var HYDROGEN_TUNNEL_DOMAIN_SUFFIX = ".tryhydrogen.dev";
|
|
3926
|
-
function
|
|
3927
|
-
|
|
3928
|
-
|
|
3929
|
-
|
|
3930
|
-
|
|
3931
|
-
|
|
3932
|
-
|
|
3933
|
-
|
|
3934
|
-
].join("\n\n"),
|
|
3935
|
-
{
|
|
3936
|
-
status: 400,
|
|
3937
|
-
headers: {
|
|
3938
|
-
"Content-Type": "text/plain; charset=utf-8"
|
|
3939
|
-
}
|
|
3940
|
-
}
|
|
3941
|
-
);
|
|
3942
|
-
}
|
|
3991
|
+
function checkTunnelDomain(hostname, useCustomAuthDomain, redirectUri) {
|
|
3992
|
+
if (hostname.endsWith(HYDROGEN_TUNNEL_DOMAIN_SUFFIX)) return;
|
|
3993
|
+
if (useCustomAuthDomain) {
|
|
3994
|
+
const redirectHint = redirectUri ? ` (${redirectUri})` : "";
|
|
3995
|
+
warnOnce(
|
|
3996
|
+
`[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.`
|
|
3997
|
+
);
|
|
3998
|
+
return;
|
|
3943
3999
|
}
|
|
4000
|
+
throw new Response(
|
|
4001
|
+
[
|
|
4002
|
+
"Customer Account API OAuth requires a Hydrogen tunnel in local development.",
|
|
4003
|
+
"Run the development server with the `--customer-account-push` flag,",
|
|
4004
|
+
`then open the tunnel URL shown in your terminal (\`https://*${HYDROGEN_TUNNEL_DOMAIN_SUFFIX}\`) instead of localhost.`
|
|
4005
|
+
].join("\n\n"),
|
|
4006
|
+
{
|
|
4007
|
+
status: 400,
|
|
4008
|
+
headers: {
|
|
4009
|
+
"Content-Type": "text/plain; charset=utf-8"
|
|
4010
|
+
}
|
|
4011
|
+
}
|
|
4012
|
+
);
|
|
3944
4013
|
}
|
|
3945
4014
|
function defaultAuthStatusHandler(request, defaultLoginUrl) {
|
|
3946
4015
|
if (!request.url) return defaultLoginUrl;
|
|
3947
|
-
const {
|
|
3948
|
-
throwIfNotTunnelled(hostname);
|
|
4016
|
+
const { pathname } = new URL(request.url);
|
|
3949
4017
|
const cleanedPathname = pathname.replace(/\.data$/, "").replace(/\/_root$/, "/").replace(/(.+)\/$/, "$1");
|
|
3950
4018
|
const redirectTo = defaultLoginUrl + `?${new URLSearchParams({ return_to: cleanedPathname }).toString()}`;
|
|
3951
4019
|
return redirect(redirectTo);
|
|
@@ -3963,7 +4031,8 @@ function createCustomerAccountClient({
|
|
|
3963
4031
|
loginPath = "/account/login",
|
|
3964
4032
|
authorizePath = "/account/authorize",
|
|
3965
4033
|
defaultRedirectPath = "/account",
|
|
3966
|
-
language
|
|
4034
|
+
language,
|
|
4035
|
+
useCustomAuthDomain
|
|
3967
4036
|
}) {
|
|
3968
4037
|
if (customerApiVersion !== DEFAULT_CUSTOMER_API_VERSION) {
|
|
3969
4038
|
console.warn(
|
|
@@ -3980,7 +4049,6 @@ function createCustomerAccountClient({
|
|
|
3980
4049
|
"[h2:error:createCustomerAccountClient] The request object does not contain a URL."
|
|
3981
4050
|
);
|
|
3982
4051
|
}
|
|
3983
|
-
const authStatusHandler = customAuthStatusHandler ? customAuthStatusHandler : () => defaultAuthStatusHandler(request, loginPath);
|
|
3984
4052
|
const requestUrl = new URL(request.url);
|
|
3985
4053
|
const httpsOrigin = requestUrl.protocol === "http:" ? requestUrl.origin.replace("http", "https") : requestUrl.origin;
|
|
3986
4054
|
const redirectUri = ensureLocalRedirectUrl({
|
|
@@ -3988,6 +4056,11 @@ function createCustomerAccountClient({
|
|
|
3988
4056
|
defaultUrl: authorizePath,
|
|
3989
4057
|
redirectUrl: authUrl
|
|
3990
4058
|
});
|
|
4059
|
+
const ensureTunnel = (hostname) => checkTunnelDomain(hostname, useCustomAuthDomain, redirectUri);
|
|
4060
|
+
const authStatusHandler = customAuthStatusHandler ? customAuthStatusHandler : () => {
|
|
4061
|
+
ensureTunnel(requestUrl.hostname);
|
|
4062
|
+
return defaultAuthStatusHandler(request, loginPath);
|
|
4063
|
+
};
|
|
3991
4064
|
const getCustomerAccountUrl = createCustomerAccountHelper(
|
|
3992
4065
|
customerApiVersion,
|
|
3993
4066
|
shopId
|
|
@@ -4109,7 +4182,7 @@ function createCustomerAccountClient({
|
|
|
4109
4182
|
return session.get(CUSTOMER_ACCOUNT_SESSION_KEY)?.accessToken;
|
|
4110
4183
|
}
|
|
4111
4184
|
async function mutate(mutation, options) {
|
|
4112
|
-
|
|
4185
|
+
ensureTunnel(requestUrl.hostname);
|
|
4113
4186
|
ifInvalidCredentialThrowError();
|
|
4114
4187
|
mutation = minifyQuery(mutation);
|
|
4115
4188
|
assertMutation(mutation, "customer.mutate");
|
|
@@ -4119,7 +4192,7 @@ function createCustomerAccountClient({
|
|
|
4119
4192
|
);
|
|
4120
4193
|
}
|
|
4121
4194
|
async function query(query2, options) {
|
|
4122
|
-
|
|
4195
|
+
ensureTunnel(requestUrl.hostname);
|
|
4123
4196
|
ifInvalidCredentialThrowError();
|
|
4124
4197
|
query2 = minifyQuery(query2);
|
|
4125
4198
|
assertQuery(query2, "customer.query");
|
|
@@ -4143,7 +4216,7 @@ function createCustomerAccountClient({
|
|
|
4143
4216
|
return {
|
|
4144
4217
|
i18n: { language: language ?? "EN" },
|
|
4145
4218
|
login: async (options) => {
|
|
4146
|
-
|
|
4219
|
+
ensureTunnel(requestUrl.hostname);
|
|
4147
4220
|
ifInvalidCredentialThrowError();
|
|
4148
4221
|
const loginUrl = new URL(getCustomerAccountUrl("AUTH" /* AUTH */));
|
|
4149
4222
|
const state = generateState();
|
|
@@ -4195,7 +4268,7 @@ function createCustomerAccountClient({
|
|
|
4195
4268
|
return redirect(loginUrl.toString());
|
|
4196
4269
|
},
|
|
4197
4270
|
logout: async (options) => {
|
|
4198
|
-
|
|
4271
|
+
ensureTunnel(requestUrl.hostname);
|
|
4199
4272
|
ifInvalidCredentialThrowError();
|
|
4200
4273
|
const idToken = session.get(CUSTOMER_ACCOUNT_SESSION_KEY)?.idToken;
|
|
4201
4274
|
const postLogoutRedirectUri = ensureLocalRedirectUrl({
|
|
@@ -4230,7 +4303,7 @@ function createCustomerAccountClient({
|
|
|
4230
4303
|
mutate,
|
|
4231
4304
|
query,
|
|
4232
4305
|
authorize: async () => {
|
|
4233
|
-
|
|
4306
|
+
ensureTunnel(requestUrl.hostname);
|
|
4234
4307
|
ifInvalidCredentialThrowError();
|
|
4235
4308
|
const code = requestUrl.searchParams.get("code");
|
|
4236
4309
|
const state = requestUrl.searchParams.get("state");
|
|
@@ -4436,6 +4509,7 @@ function createHydrogenContext(options, additionalContext) {
|
|
|
4436
4509
|
customerApiVersion: customerAccountOptions?.apiVersion,
|
|
4437
4510
|
authUrl: customerAccountOptions?.authUrl,
|
|
4438
4511
|
customAuthStatusHandler: customerAccountOptions?.customAuthStatusHandler,
|
|
4512
|
+
useCustomAuthDomain: customerAccountOptions?.useCustomAuthDomain,
|
|
4439
4513
|
// locale - i18n.language is a union of StorefrontLanguageCode | CustomerLanguageCode
|
|
4440
4514
|
// We cast here because createCustomerAccountClient expects CustomerLanguageCode specifically,
|
|
4441
4515
|
// but the union type is compatible since most language codes overlap between the two APIs
|
|
@@ -4548,6 +4622,11 @@ function createRequestHandler({
|
|
|
4548
4622
|
appendPoweredByHeader?.(response2);
|
|
4549
4623
|
return response2;
|
|
4550
4624
|
}
|
|
4625
|
+
if (storefront?.isMcpUrl(request)) {
|
|
4626
|
+
const response2 = await storefront.forwardMcp(request);
|
|
4627
|
+
appendPoweredByHeader?.(response2);
|
|
4628
|
+
return response2;
|
|
4629
|
+
}
|
|
4551
4630
|
}
|
|
4552
4631
|
const response = await handleRequest(request, context);
|
|
4553
4632
|
if (storefront && proxyStandardRoutes) {
|