@@ -20,6 +20,7 @@ const CHECKOUT_PATTERN = /^\/checkouts\/(?!internal\/)/
2020const ACCOUNT_PATTERN = / ^ \/ a c c o u n t ( \/ l o g i n \/ m u l t i p a s s ( \/ [ ^ / ] + ) ? | \/ l o g o u t ) ? \/ ? $ /
2121const VANITY_CDN_PATTERN = new RegExp ( `^${ VANITY_CDN_PREFIX } ` )
2222const EXTENSION_CDN_PATTERN = new RegExp ( `^${ EXTENSION_CDN_PREFIX } ` )
23+ const STOREFRONT_API_PATTERN = / ^ \/ a p i \/ ( u n s t a b l e | \d { 4 } - \d { 2 } ) \/ g r a p h q l \. j s o n /
2324
2425const IGNORED_ENDPOINTS = [
2526 '/.well-known' ,
@@ -118,6 +119,16 @@ function getStoreFqdnForRegEx(ctx: DevServerContext) {
118119 return ctx . session . storeFqdn . replace ( / \\ / g, '\\\\' ) . replace ( / \. / g, '\\.' )
119120}
120121
122+ /**
123+ * Whether the request should be forwarded to SFR without modification.
124+ */
125+ function isPassthroughRequest ( event : H3Event ) {
126+ // Forward Storefront API requests as-is. The public Storefront API expects
127+ // X-Shopify-Storefront-Access-Token from the caller and rejects our SFR
128+ // devtools bearer, so we must not inject theme auth, cookies, or dev params.
129+ return STOREFRONT_API_PATTERN . test ( event . path )
130+ }
131+
121132/**
122133 * Replaces every VanityCDN-like (...myshopify.com/cdn/...) URL to pass through the local server.
123134 * It also replaces MainCDN-like (cdn.shopify.com/...) URLs to files that are known local assets.
@@ -306,41 +317,39 @@ export function proxyStorefrontRequest(event: H3Event, ctx: DevServerContext): P
306317 )
307318 }
308319
309- // When a .css.liquid or .js.liquid file is requested but it doesn't exist in SFR,
310- // it will be rendered with a query string like `assets/file.css?1234`.
311- // For some reason, after refreshing, this rendered URL keeps the wrong `?1234`
312- // query string for a while. We replace it with a proper timestamp here to fix it.
313- if ( / \/ a s s e t s \/ [ ^ / ] + \. ( c s s | j s ) $ / . test ( url . pathname ) && / \? \d + $ / . test ( url . search ) ) {
314- url . search = `?v=${ Date . now ( ) } `
315- }
316-
317- url . searchParams . set ( '_fd' , '0' )
318- url . searchParams . set ( 'pb' , '0' )
319- const headers = getProxyStorefrontHeaders ( event )
320320 const body = getRequestWebStream ( event )
321+ let headers = getProxyStorefrontHeaders ( event )
322+
323+ if ( ! isPassthroughRequest ( event ) ) {
324+ // When a .css.liquid or .js.liquid file is requested but it doesn't exist in SFR,
325+ // it will be rendered with a query string like `assets/file.css?1234`.
326+ // For some reason, after refreshing, this rendered URL keeps the wrong `?1234`
327+ // query string for a while. We replace it with a proper timestamp here to fix it.
328+ if ( / \/ a s s e t s \/ [ ^ / ] + \. ( c s s | j s ) $ / . test ( url . pathname ) && / \? \d + $ / . test ( url . search ) ) {
329+ url . search = `?v=${ Date . now ( ) } `
330+ }
321331
322- const baseHeaders : Record < string , string > = {
323- ...headers ,
324- ...defaultHeaders ( ) ,
325- referer : url . origin ,
326- Cookie : buildCookies ( ctx . session , { headers} ) ,
327- }
332+ url . searchParams . set ( '_fd' , '0' )
333+ url . searchParams . set ( 'pb' , '0' )
328334
329- // Only include Authorization for theme dev, not theme-extensions
330- if ( ctx . type === 'theme' ) {
331- baseHeaders . Authorization = `Bearer ${ ctx . session . storefrontToken } `
335+ headers = cleanHeader ( {
336+ ...headers ,
337+ ...defaultHeaders ( ) ,
338+ referer : url . origin ,
339+ Cookie : buildCookies ( ctx . session , { headers} ) ,
340+ // Only include Authorization for theme dev, not theme-extensions
341+ ...( ctx . type === 'theme' ? { Authorization : `Bearer ${ ctx . session . storefrontToken } ` } : { } ) ,
342+ } )
332343 }
333344
334- const finalHeaders = cleanHeader ( baseHeaders )
335-
336345 // eslint-disable-next-line no-restricted-globals
337346 return fetch ( url , {
338347 method : event . method ,
339348 body,
340349 duplex : body ? 'half' : undefined ,
341350 // Important to return 3xx responses to the client
342351 redirect : 'manual' ,
343- headers : finalHeaders ,
352+ headers,
344353 } as RequestInit & { duplex ?: 'half' } )
345354 . then ( ( response ) => patchProxiedResponseHeaders ( ctx , response ) )
346355 . catch ( ( error : Error ) => {
0 commit comments