diff --git a/.changeset/bright-wombats-count.md b/.changeset/bright-wombats-count.md new file mode 100644 index 0000000000..08b973928b --- /dev/null +++ b/.changeset/bright-wombats-count.md @@ -0,0 +1,5 @@ +--- +'@shopify/hydrogen': patch +--- + +`customerAccount` no longer commit session automatically in any situation. diff --git a/.changeset/red-lamps-switch.md b/.changeset/red-lamps-switch.md new file mode 100644 index 0000000000..a5c69e40a6 --- /dev/null +++ b/.changeset/red-lamps-switch.md @@ -0,0 +1,65 @@ +--- +'skeleton': patch +--- + +Remove manual setting of session in headers and recommend setting it in server after response is created. + +Step 1: Add `isPending` implementation in session + +```diff +// in app/lib/session.ts +export class AppSession implements HydrogenSession { ++ public isPending = false; + + get unset() { ++ this.isPending = true; + return this.#session.unset; + } + + get set() { ++ this.isPending = true; + return this.#session.set; + } + + commit() { ++ this.isPending = false; + return this.#sessionStorage.commitSession(this.#session); + } +} +``` + +Step 2: update response header if `session.isPending` is true + +```diff +// in server.ts +export default { + async fetch(request: Request): Promise { + try { + const response = await handleRequest(request); + ++ if (session.isPending) { ++ response.headers.set('Set-Cookie', await session.commit()); ++ } + + return response; + } catch (error) { + ... + } + }, +}; +``` + +Step 3: remove setting cookie with session.commit() in routes + +```diff +// in route files +export async function loader({context}: LoaderFunctionArgs) { + return json({}, +- { +- headers: { +- 'Set-Cookie': await context.session.commit(), +- }, + }, + ); +} +``` diff --git a/.changeset/six-pumas-allow.md b/.changeset/six-pumas-allow.md new file mode 100644 index 0000000000..f37fd2fe09 --- /dev/null +++ b/.changeset/six-pumas-allow.md @@ -0,0 +1,5 @@ +--- +'@shopify/cli-hydrogen': patch +--- + +skeleton template was updated to do session commit in server call instead of routes diff --git a/docs/shopify-dev/analytics-setup/js/app/root.jsx b/docs/shopify-dev/analytics-setup/js/app/root.jsx index 1c38106ff3..75c6b2b2fb 100644 --- a/docs/shopify-dev/analytics-setup/js/app/root.jsx +++ b/docs/shopify-dev/analytics-setup/js/app/root.jsx @@ -105,12 +105,7 @@ export async function loader({context}) { storefrontAccessToken: env.PUBLIC_STOREFRONT_API_TOKEN, }, // [END consent] - }, - { - headers: { - 'Set-Cookie': await context.session.commit(), - }, - }, + } ); } diff --git a/docs/shopify-dev/analytics-setup/js/app/routes/cart.jsx b/docs/shopify-dev/analytics-setup/js/app/routes/cart.jsx index 04e4870424..aa0426edef 100644 --- a/docs/shopify-dev/analytics-setup/js/app/routes/cart.jsx +++ b/docs/shopify-dev/analytics-setup/js/app/routes/cart.jsx @@ -1,9 +1,6 @@ import {Await} from '@remix-run/react'; import {Suspense} from 'react'; -import { - CartForm, - Analytics, -} from '@shopify/hydrogen'; +import {CartForm, Analytics} from '@shopify/hydrogen'; import {json} from '@shopify/remix-oxygen'; import {CartMain} from '~/components/Cart'; import {useRootLoaderData} from '~/lib/root-data'; @@ -74,8 +71,6 @@ export async function action({request, context}) { headers.set('Location', redirectTo); } - headers.append('Set-Cookie', await context.session.commit()); - return json( { cart: cartResult, diff --git a/docs/shopify-dev/analytics-setup/ts/app/root.tsx b/docs/shopify-dev/analytics-setup/ts/app/root.tsx index 35610d3b56..fb01bea882 100644 --- a/docs/shopify-dev/analytics-setup/ts/app/root.tsx +++ b/docs/shopify-dev/analytics-setup/ts/app/root.tsx @@ -106,12 +106,7 @@ export async function loader({context}: LoaderFunctionArgs) { storefrontAccessToken: env.PUBLIC_STOREFRONT_API_TOKEN, }, // [END consent] - }, - { - headers: { - 'Set-Cookie': await context.session.commit(), - }, - }, + } ); } diff --git a/docs/shopify-dev/analytics-setup/ts/app/routes/cart.tsx b/docs/shopify-dev/analytics-setup/ts/app/routes/cart.tsx index 093ca559cb..81e6f1e4c9 100644 --- a/docs/shopify-dev/analytics-setup/ts/app/routes/cart.tsx +++ b/docs/shopify-dev/analytics-setup/ts/app/routes/cart.tsx @@ -1,10 +1,7 @@ import {Await, type MetaFunction} from '@remix-run/react'; import {Suspense} from 'react'; import type {CartQueryDataReturn} from '@shopify/hydrogen'; -import { - CartForm, - Analytics, -} from '@shopify/hydrogen'; +import {CartForm, Analytics} from '@shopify/hydrogen'; import {json, type ActionFunctionArgs} from '@shopify/remix-oxygen'; import {CartMain} from '~/components/Cart'; import {useRootLoaderData} from '~/lib/root-data'; @@ -71,8 +68,6 @@ export async function action({request, context}: ActionFunctionArgs) { headers.set('Location', redirectTo); } - headers.append('Set-Cookie', await context.session.commit()); - return json( { cart: cartResult, diff --git a/examples/b2b/app/root.tsx b/examples/b2b/app/root.tsx index ab750fc051..3e2f17352a 100644 --- a/examples/b2b/app/root.tsx +++ b/examples/b2b/app/root.tsx @@ -111,28 +111,21 @@ export async function loader({context}: LoaderFunctionArgs) { }, }); - return defer( - { - cart: cartPromise, - footer: footerPromise, - header: await headerPromise, - isLoggedIn: isLoggedInPromise, - publicStoreDomain, - shop: getShopAnalytics({ - storefront, - publicStorefrontId: env.PUBLIC_STOREFRONT_ID, - }), - consent: { - checkoutDomain: env.PUBLIC_CHECKOUT_DOMAIN, - storefrontAccessToken: env.PUBLIC_STOREFRONT_API_TOKEN, - }, - }, - { - headers: { - 'Set-Cookie': await context.session.commit(), - }, + return defer({ + cart: cartPromise, + footer: footerPromise, + header: await headerPromise, + isLoggedIn: isLoggedInPromise, + publicStoreDomain, + shop: getShopAnalytics({ + storefront, + publicStorefrontId: env.PUBLIC_STOREFRONT_ID, + }), + consent: { + checkoutDomain: env.PUBLIC_CHECKOUT_DOMAIN, + storefrontAccessToken: env.PUBLIC_STOREFRONT_API_TOKEN, }, - ); + }); } function Layout({children}: {children?: React.ReactNode}) { diff --git a/examples/b2b/app/routes/b2blocations.tsx b/examples/b2b/app/routes/b2blocations.tsx index 3189765692..6cda28b53a 100644 --- a/examples/b2b/app/routes/b2blocations.tsx +++ b/examples/b2b/app/routes/b2blocations.tsx @@ -30,14 +30,7 @@ export async function loader({context}: LoaderFunctionArgs) { const modalOpen = Boolean(company) && !companyLocationId; - return defer( - {company, companyLocationId, modalOpen}, - { - headers: { - 'Set-Cookie': await context.session.commit(), - }, - }, - ); + return defer({company, companyLocationId, modalOpen}); } export default function CartRoute() { diff --git a/examples/b2b/server.ts b/examples/b2b/server.ts index 84e0c87f47..7279d99887 100644 --- a/examples/b2b/server.ts +++ b/examples/b2b/server.ts @@ -101,6 +101,10 @@ export default { const response = await handleRequest(request); + if (session.isPending) { + response.headers.set('Set-Cookie', await session.commit()); + } + if (response.status === 404) { /** * Check for redirects only when there's a 404 from the app. diff --git a/examples/classic-remix/app/root.tsx b/examples/classic-remix/app/root.tsx index 7e3ba5b9af..b9f34303dd 100644 --- a/examples/classic-remix/app/root.tsx +++ b/examples/classic-remix/app/root.tsx @@ -74,27 +74,19 @@ export async function loader(args: LoaderFunctionArgs) { const {storefront, env} = args.context; - return defer( - { - ...criticalData, - ...deferredData, - publicStoreDomain: env.PUBLIC_STORE_DOMAIN, - shop: getShopAnalytics({ - storefront, - publicStorefrontId: env.PUBLIC_STOREFRONT_ID, - }), - consent: { - checkoutDomain: env.PUBLIC_CHECKOUT_DOMAIN, - storefrontAccessToken: env.PUBLIC_STOREFRONT_API_TOKEN, - }, - }, - - { - headers: { - 'Set-Cookie': await args.context.session.commit(), - }, + return defer({ + ...criticalData, + ...deferredData, + publicStoreDomain: env.PUBLIC_STORE_DOMAIN, + shop: getShopAnalytics({ + storefront, + publicStorefrontId: env.PUBLIC_STOREFRONT_ID, + }), + consent: { + checkoutDomain: env.PUBLIC_CHECKOUT_DOMAIN, + storefrontAccessToken: env.PUBLIC_STOREFRONT_API_TOKEN, }, - ); + }); } /** diff --git a/examples/classic-remix/server.ts b/examples/classic-remix/server.ts index 9b8416f71e..34e7e709d4 100644 --- a/examples/classic-remix/server.ts +++ b/examples/classic-remix/server.ts @@ -95,6 +95,10 @@ export default { const response = await handleRequest(request); + if (session.isPending) { + response.headers.set('Set-Cookie', await session.commit()); + } + if (response.status === 404) { /** * Check for redirects only when there's a 404 from the app. diff --git a/examples/custom-cart-method/app/routes/cart.tsx b/examples/custom-cart-method/app/routes/cart.tsx index 0835fbb257..f2ab4eb335 100644 --- a/examples/custom-cart-method/app/routes/cart.tsx +++ b/examples/custom-cart-method/app/routes/cart.tsx @@ -90,8 +90,6 @@ export async function action({request, context}: ActionFunctionArgs) { headers.set('Location', redirectTo); } - headers.append('Set-Cookie', await context.session.commit()); - return json( { cart: cartResult, diff --git a/examples/custom-cart-method/server.ts b/examples/custom-cart-method/server.ts index f788bdf367..4eae76427a 100644 --- a/examples/custom-cart-method/server.ts +++ b/examples/custom-cart-method/server.ts @@ -132,6 +132,10 @@ export default { const response = await handleRequest(request); + if (session.isPending) { + response.headers.set('Set-Cookie', await session.commit()); + } + if (response.status === 404) { /** * Check for redirects only when there's a 404 from the app. diff --git a/examples/express/app/root.tsx b/examples/express/app/root.tsx index ec1c52b79a..72c430ec78 100644 --- a/examples/express/app/root.tsx +++ b/examples/express/app/root.tsx @@ -58,18 +58,11 @@ export async function loader({context}: LoaderFunctionArgs) { await context.storefront.query<{shop: Shop}>(LAYOUT_QUERY), ]); - return defer( - { - isLoggedIn: Boolean(customerAccessToken), - cart, - layout, - }, - { - headers: { - 'Set-Cookie': await context.session.commit(), - }, - }, - ); + return defer({ + isLoggedIn: Boolean(customerAccessToken), + cart, + layout, + }); } function Layout({children}: {children?: React.ReactNode}) { diff --git a/examples/gtm/app/root.tsx b/examples/gtm/app/root.tsx index 7879bedd30..2adb386451 100644 --- a/examples/gtm/app/root.tsx +++ b/examples/gtm/app/root.tsx @@ -66,26 +66,19 @@ export async function loader(args: LoaderFunctionArgs) { const {storefront, env} = args.context; - return defer( - { - ...deferredData, - ...criticalData, - publicStoreDomain: env.PUBLIC_STORE_DOMAIN, - shop: getShopAnalytics({ - storefront, - publicStorefrontId: env.PUBLIC_STOREFRONT_ID, - }), - consent: { - checkoutDomain: env.PUBLIC_CHECKOUT_DOMAIN, - storefrontAccessToken: env.PUBLIC_STOREFRONT_API_TOKEN, - }, - }, - { - headers: { - 'Set-Cookie': await args.context.session.commit(), - }, + return defer({ + ...deferredData, + ...criticalData, + publicStoreDomain: env.PUBLIC_STORE_DOMAIN, + shop: getShopAnalytics({ + storefront, + publicStorefrontId: env.PUBLIC_STOREFRONT_ID, + }), + consent: { + checkoutDomain: env.PUBLIC_CHECKOUT_DOMAIN, + storefrontAccessToken: env.PUBLIC_STOREFRONT_API_TOKEN, }, - ); + }); } /** diff --git a/examples/legacy-customer-account-flow/app/root.tsx b/examples/legacy-customer-account-flow/app/root.tsx index 219fc27fb8..177b6e1fad 100644 --- a/examples/legacy-customer-account-flow/app/root.tsx +++ b/examples/legacy-customer-account-flow/app/root.tsx @@ -215,7 +215,6 @@ async function validateCustomerAccessToken( if (customerAccessTokenExpired) { session.unset('customerAccessToken'); - headers.append('Set-Cookie', await session.commit()); } else { isLoggedIn = true; } diff --git a/examples/legacy-customer-account-flow/app/routes/account.profile.tsx b/examples/legacy-customer-account-flow/app/routes/account.profile.tsx index 49b2020a24..3edf686d8a 100644 --- a/examples/legacy-customer-account-flow/app/routes/account.profile.tsx +++ b/examples/legacy-customer-account-flow/app/routes/account.profile.tsx @@ -94,14 +94,7 @@ export async function action({request, context}: ActionFunctionArgs) { ); } - return json( - {error: null, customer: updated.customerUpdate?.customer}, - { - headers: { - 'Set-Cookie': await session.commit(), - }, - }, - ); + return json({error: null, customer: updated.customerUpdate?.customer}); } catch (error: any) { return json({error: error.message, customer: null}, {status: 400}); } diff --git a/examples/legacy-customer-account-flow/app/routes/account.tsx b/examples/legacy-customer-account-flow/app/routes/account.tsx index d52e884aae..913cd65a93 100644 --- a/examples/legacy-customer-account-flow/app/routes/account.tsx +++ b/examples/legacy-customer-account-flow/app/routes/account.tsx @@ -20,11 +20,7 @@ export async function loader({request, context}: LoaderFunctionArgs) { if (!isLoggedIn) { if (isPrivateRoute || isAccountHome) { session.unset('customerAccessToken'); - return redirect('/account/login', { - headers: { - 'Set-Cookie': await session.commit(), - }, - }); + return redirect('/account/login'); } else { // public subroute such as /account/login... return json({ @@ -67,11 +63,7 @@ export async function loader({request, context}: LoaderFunctionArgs) { // eslint-disable-next-line no-console console.error('There was a problem loading account', error); session.unset('customerAccessToken'); - return redirect('/account/login', { - headers: { - 'Set-Cookie': await session.commit(), - }, - }); + return redirect('/account/login'); } } diff --git a/examples/legacy-customer-account-flow/app/routes/account_.activate.$id.$activationToken.tsx b/examples/legacy-customer-account-flow/app/routes/account_.activate.$id.$activationToken.tsx index 69724e9e87..49d1c4106e 100644 --- a/examples/legacy-customer-account-flow/app/routes/account_.activate.$id.$activationToken.tsx +++ b/examples/legacy-customer-account-flow/app/routes/account_.activate.$id.$activationToken.tsx @@ -70,11 +70,7 @@ export async function action({request, context, params}: ActionFunctionArgs) { } session.set('customerAccessToken', customerAccessToken); - return redirect('/account', { - headers: { - 'Set-Cookie': await session.commit(), - }, - }); + return redirect('/account'); } catch (error: unknown) { if (error instanceof Error) { return json({error: error.message}, {status: 400}); diff --git a/examples/legacy-customer-account-flow/app/routes/account_.login.tsx b/examples/legacy-customer-account-flow/app/routes/account_.login.tsx index 0277df9336..f3537f8ae2 100644 --- a/examples/legacy-customer-account-flow/app/routes/account_.login.tsx +++ b/examples/legacy-customer-account-flow/app/routes/account_.login.tsx @@ -54,11 +54,7 @@ export async function action({request, context}: ActionFunctionArgs) { const {customerAccessToken} = customerAccessTokenCreate; session.set('customerAccessToken', customerAccessToken); - return redirect('/account', { - headers: { - 'Set-Cookie': await session.commit(), - }, - }); + return redirect('/account'); } catch (error: unknown) { if (error instanceof Error) { return json({error: error.message}, {status: 400}); diff --git a/examples/legacy-customer-account-flow/app/routes/account_.logout.tsx b/examples/legacy-customer-account-flow/app/routes/account_.logout.tsx index ef1ecb269e..8981fc3993 100644 --- a/examples/legacy-customer-account-flow/app/routes/account_.logout.tsx +++ b/examples/legacy-customer-account-flow/app/routes/account_.logout.tsx @@ -17,11 +17,7 @@ export async function action({request, context}: ActionFunctionArgs) { return json({error: 'Method not allowed'}, {status: 405}); } - return redirect('/', { - headers: { - 'Set-Cookie': await session.commit(), - }, - }); + return redirect('/'); } export default function Logout() { diff --git a/examples/legacy-customer-account-flow/app/routes/account_.register.tsx b/examples/legacy-customer-account-flow/app/routes/account_.register.tsx index 325300744f..2bfa62a39e 100644 --- a/examples/legacy-customer-account-flow/app/routes/account_.register.tsx +++ b/examples/legacy-customer-account-flow/app/routes/account_.register.tsx @@ -90,7 +90,6 @@ export async function action({request, context}: ActionFunctionArgs) { { status: 302, headers: { - 'Set-Cookie': await session.commit(), Location: '/account', }, }, diff --git a/examples/legacy-customer-account-flow/app/routes/account_.reset.$id.$resetToken.tsx b/examples/legacy-customer-account-flow/app/routes/account_.reset.$id.$resetToken.tsx index a116c13797..9838590d1f 100644 --- a/examples/legacy-customer-account-flow/app/routes/account_.reset.$id.$resetToken.tsx +++ b/examples/legacy-customer-account-flow/app/routes/account_.reset.$id.$resetToken.tsx @@ -47,11 +47,7 @@ export async function action({request, context, params}: ActionFunctionArgs) { } session.set('customerAccessToken', customerReset.customerAccessToken); - return redirect('/account', { - headers: { - 'Set-Cookie': await session.commit(), - }, - }); + return redirect('/account'); } catch (error: unknown) { if (error instanceof Error) { return json({error: error.message}, {status: 400}); diff --git a/examples/legacy-customer-account-flow/app/routes/cart.tsx b/examples/legacy-customer-account-flow/app/routes/cart.tsx index 2476219509..831ef58eff 100644 --- a/examples/legacy-customer-account-flow/app/routes/cart.tsx +++ b/examples/legacy-customer-account-flow/app/routes/cart.tsx @@ -80,8 +80,6 @@ export async function action({request, context}: ActionFunctionArgs) { headers.set('Location', redirectTo); } - headers.append('Set-Cookie', await context.session.commit()); - return json( { cart: cartResult, diff --git a/examples/legacy-customer-account-flow/server.ts b/examples/legacy-customer-account-flow/server.ts index 37277075b1..c9116e05ed 100644 --- a/examples/legacy-customer-account-flow/server.ts +++ b/examples/legacy-customer-account-flow/server.ts @@ -82,6 +82,10 @@ export default { const response = await handleRequest(request); + if (session.isPending) { + response.headers.set('Set-Cookie', await session.commit()); + } + if (response.status === 404) { /** * Check for redirects only when there's a 404 from the app. diff --git a/examples/metaobjects/app/root.tsx b/examples/metaobjects/app/root.tsx index 90452ba499..0fdd654673 100644 --- a/examples/metaobjects/app/root.tsx +++ b/examples/metaobjects/app/root.tsx @@ -79,33 +79,26 @@ export async function loader({context}: LoaderFunctionArgs) { }, }); - return defer( - { - cart: cartPromise, - footer: footerPromise, - header: await headerPromise, - isLoggedIn: isLoggedInPromise, - publicStoreDomain, - shop: getShopAnalytics({ - storefront, - publicStorefrontId: env.PUBLIC_STOREFRONT_ID, - }), - consent: { - checkoutDomain: env.PUBLIC_CHECKOUT_DOMAIN, - storefrontAccessToken: env.PUBLIC_STOREFRONT_API_TOKEN, - }, - /***********************************************/ - /********** EXAMPLE UPDATE STARTS ************/ - publictoreSubdomain: context.env.PUBLIC_SHOPIFY_STORE_DOMAIN, - /********** EXAMPLE UPDATE END ************/ - /***********************************************/ - }, - { - headers: { - 'Set-Cookie': await context.session.commit(), - }, + return defer({ + cart: cartPromise, + footer: footerPromise, + header: await headerPromise, + isLoggedIn: isLoggedInPromise, + publicStoreDomain, + shop: getShopAnalytics({ + storefront, + publicStorefrontId: env.PUBLIC_STOREFRONT_ID, + }), + consent: { + checkoutDomain: env.PUBLIC_CHECKOUT_DOMAIN, + storefrontAccessToken: env.PUBLIC_STOREFRONT_API_TOKEN, }, - ); + /***********************************************/ + /********** EXAMPLE UPDATE STARTS ************/ + publictoreSubdomain: context.env.PUBLIC_SHOPIFY_STORE_DOMAIN, + /********** EXAMPLE UPDATE END ************/ + /***********************************************/ + }); } function Layout({children}: {children?: React.ReactNode}) { diff --git a/examples/multipass/app/root.tsx b/examples/multipass/app/root.tsx index c472eecdee..998afd7591 100644 --- a/examples/multipass/app/root.tsx +++ b/examples/multipass/app/root.tsx @@ -250,7 +250,6 @@ async function validateCustomerAccessToken( if (customerAccessTokenExpired) { session.unset('customerAccessToken'); - headers.append('Set-Cookie', await session.commit()); } else { isLoggedIn = true; } diff --git a/examples/multipass/app/routes/account.profile.tsx b/examples/multipass/app/routes/account.profile.tsx index a546e6ff9e..c5c782b75c 100644 --- a/examples/multipass/app/routes/account.profile.tsx +++ b/examples/multipass/app/routes/account.profile.tsx @@ -94,14 +94,7 @@ export async function action({request, context}: ActionFunctionArgs) { ); } - return json( - {error: null, customer: updated.customerUpdate?.customer}, - { - headers: { - 'Set-Cookie': await session.commit(), - }, - }, - ); + return json({error: null, customer: updated.customerUpdate?.customer}); } catch (error: any) { return json({error: error.message, customer: null}, {status: 400}); } diff --git a/examples/multipass/app/routes/account.tsx b/examples/multipass/app/routes/account.tsx index d52e884aae..913cd65a93 100644 --- a/examples/multipass/app/routes/account.tsx +++ b/examples/multipass/app/routes/account.tsx @@ -20,11 +20,7 @@ export async function loader({request, context}: LoaderFunctionArgs) { if (!isLoggedIn) { if (isPrivateRoute || isAccountHome) { session.unset('customerAccessToken'); - return redirect('/account/login', { - headers: { - 'Set-Cookie': await session.commit(), - }, - }); + return redirect('/account/login'); } else { // public subroute such as /account/login... return json({ @@ -67,11 +63,7 @@ export async function loader({request, context}: LoaderFunctionArgs) { // eslint-disable-next-line no-console console.error('There was a problem loading account', error); session.unset('customerAccessToken'); - return redirect('/account/login', { - headers: { - 'Set-Cookie': await session.commit(), - }, - }); + return redirect('/account/login'); } } diff --git a/examples/multipass/app/routes/account_.activate.$id.$activationToken.tsx b/examples/multipass/app/routes/account_.activate.$id.$activationToken.tsx index 69724e9e87..49d1c4106e 100644 --- a/examples/multipass/app/routes/account_.activate.$id.$activationToken.tsx +++ b/examples/multipass/app/routes/account_.activate.$id.$activationToken.tsx @@ -70,11 +70,7 @@ export async function action({request, context, params}: ActionFunctionArgs) { } session.set('customerAccessToken', customerAccessToken); - return redirect('/account', { - headers: { - 'Set-Cookie': await session.commit(), - }, - }); + return redirect('/account'); } catch (error: unknown) { if (error instanceof Error) { return json({error: error.message}, {status: 400}); diff --git a/examples/multipass/app/routes/account_.login.tsx b/examples/multipass/app/routes/account_.login.tsx index 0277df9336..f3537f8ae2 100644 --- a/examples/multipass/app/routes/account_.login.tsx +++ b/examples/multipass/app/routes/account_.login.tsx @@ -54,11 +54,7 @@ export async function action({request, context}: ActionFunctionArgs) { const {customerAccessToken} = customerAccessTokenCreate; session.set('customerAccessToken', customerAccessToken); - return redirect('/account', { - headers: { - 'Set-Cookie': await session.commit(), - }, - }); + return redirect('/account'); } catch (error: unknown) { if (error instanceof Error) { return json({error: error.message}, {status: 400}); diff --git a/examples/multipass/app/routes/account_.logout.tsx b/examples/multipass/app/routes/account_.logout.tsx index ef1ecb269e..8981fc3993 100644 --- a/examples/multipass/app/routes/account_.logout.tsx +++ b/examples/multipass/app/routes/account_.logout.tsx @@ -17,11 +17,7 @@ export async function action({request, context}: ActionFunctionArgs) { return json({error: 'Method not allowed'}, {status: 405}); } - return redirect('/', { - headers: { - 'Set-Cookie': await session.commit(), - }, - }); + return redirect('/'); } export default function Logout() { diff --git a/examples/multipass/app/routes/account_.register.tsx b/examples/multipass/app/routes/account_.register.tsx index 325300744f..2bfa62a39e 100644 --- a/examples/multipass/app/routes/account_.register.tsx +++ b/examples/multipass/app/routes/account_.register.tsx @@ -90,7 +90,6 @@ export async function action({request, context}: ActionFunctionArgs) { { status: 302, headers: { - 'Set-Cookie': await session.commit(), Location: '/account', }, }, diff --git a/examples/multipass/app/routes/account_.reset.$id.$resetToken.tsx b/examples/multipass/app/routes/account_.reset.$id.$resetToken.tsx index a116c13797..9838590d1f 100644 --- a/examples/multipass/app/routes/account_.reset.$id.$resetToken.tsx +++ b/examples/multipass/app/routes/account_.reset.$id.$resetToken.tsx @@ -47,11 +47,7 @@ export async function action({request, context, params}: ActionFunctionArgs) { } session.set('customerAccessToken', customerReset.customerAccessToken); - return redirect('/account', { - headers: { - 'Set-Cookie': await session.commit(), - }, - }); + return redirect('/account'); } catch (error: unknown) { if (error instanceof Error) { return json({error: error.message}, {status: 400}); diff --git a/examples/multipass/server.ts b/examples/multipass/server.ts index a0549d6530..9e5308d278 100644 --- a/examples/multipass/server.ts +++ b/examples/multipass/server.ts @@ -76,6 +76,10 @@ export default { const response = await handleRequest(request); + if (session.isPending) { + response.headers.set('Set-Cookie', await session.commit()); + } + if (response.status === 404) { /** * Check for redirects only when there's a 404 from the app. @@ -100,6 +104,8 @@ export default { * swap out the cookie-based implementation with something else! */ export class AppSession implements HydrogenSession { + public isPending = false; + #sessionStorage; #session; @@ -139,10 +145,12 @@ export class AppSession implements HydrogenSession { } get unset() { + this.isPending = true; return this.#session.unset; } get set() { + this.isPending = true; return this.#session.set; } @@ -151,6 +159,7 @@ export class AppSession implements HydrogenSession { } commit() { + this.isPending = false; return this.#sessionStorage.commitSession(this.#session); } } diff --git a/examples/partytown/app/root.tsx b/examples/partytown/app/root.tsx index 08a1ea5d5b..e1f15eee76 100644 --- a/examples/partytown/app/root.tsx +++ b/examples/partytown/app/root.tsx @@ -45,19 +45,11 @@ export async function loader(args: LoaderFunctionArgs) { const {env} = args.context; - return defer( - { - ...deferredData, - ...criticalData, - gtmContainerId: env.GTM_CONTAINER_ID, - }, - { - headers: { - ...partytownAtomicHeaders(), - 'Set-Cookie': await args.context.session.commit(), - }, - }, - ); + return defer({ + ...deferredData, + ...criticalData, + gtmContainerId: env.GTM_CONTAINER_ID, + }); } /** diff --git a/examples/subscriptions/app/root.tsx b/examples/subscriptions/app/root.tsx index 02b72d62f7..df9886015a 100644 --- a/examples/subscriptions/app/root.tsx +++ b/examples/subscriptions/app/root.tsx @@ -75,26 +75,18 @@ export async function loader(args: LoaderFunctionArgs) { const {storefront, env} = args.context; - return defer( - { - ...deferredData, - ...criticalData, - shop: getShopAnalytics({ - storefront, - publicStorefrontId: env.PUBLIC_STOREFRONT_ID, - }), - consent: { - checkoutDomain: env.PUBLIC_CHECKOUT_DOMAIN, - storefrontAccessToken: env.PUBLIC_STOREFRONT_API_TOKEN, - }, - }, - - { - headers: { - 'Set-Cookie': await args.context.session.commit(), - }, + return defer({ + ...deferredData, + ...criticalData, + shop: getShopAnalytics({ + storefront, + publicStorefrontId: env.PUBLIC_STOREFRONT_ID, + }), + consent: { + checkoutDomain: env.PUBLIC_CHECKOUT_DOMAIN, + storefrontAccessToken: env.PUBLIC_STOREFRONT_API_TOKEN, }, - ); + }); } /** diff --git a/examples/third-party-queries-caching/server.ts b/examples/third-party-queries-caching/server.ts index 899922ccdc..8b321527d4 100644 --- a/examples/third-party-queries-caching/server.ts +++ b/examples/third-party-queries-caching/server.ts @@ -109,6 +109,10 @@ export default { const response = await handleRequest(request); + if (session.isPending) { + response.headers.set('Set-Cookie', await session.commit()); + } + if (response.status === 404) { /** * Check for redirects only when there's a 404 from the app. diff --git a/packages/cli/src/lib/setups/i18n/replacers.test.ts b/packages/cli/src/lib/setups/i18n/replacers.test.ts index 94c3ff94ac..f9eae280da 100644 --- a/packages/cli/src/lib/setups/i18n/replacers.test.ts +++ b/packages/cli/src/lib/setups/i18n/replacers.test.ts @@ -236,6 +236,10 @@ describe('i18n replacers', () => { const response = await handleRequest(request); + if (session.isPending) { + response.headers.set("Set-Cookie", await session.commit()); + } + if (response.status === 404) { /** * Check for redirects only when there's a 404 from the app. diff --git a/packages/hydrogen/src/customer/auth.helpers.ts b/packages/hydrogen/src/customer/auth.helpers.ts index 996f799e0f..5cf01365cb 100644 --- a/packages/hydrogen/src/customer/auth.helpers.ts +++ b/packages/hydrogen/src/customer/auth.helpers.ts @@ -202,9 +202,6 @@ export async function checkExpires({ throw new BadRequest( 'Unauthorized', 'Login before querying the Customer Account API.', - { - 'Set-Cookie': await session.commit(), - }, ); } } diff --git a/packages/hydrogen/src/customer/customer.auth-handler.example.jsx b/packages/hydrogen/src/customer/customer.auth-handler.example.jsx index 538b17d475..4dbd7464ea 100644 --- a/packages/hydrogen/src/customer/customer.auth-handler.example.jsx +++ b/packages/hydrogen/src/customer/customer.auth-handler.example.jsx @@ -36,11 +36,19 @@ export default { getLoadContext: () => ({customerAccount}), }); - return handleRequest(request); + const response = await handleRequest(request); + + if (session.isPending) { + response.headers.set('Set-Cookie', await session.commit()); + } + + return response; }, }; class AppSession { + isPending = false; + static async init(request, secrets) { const storage = createCookieSessionStorage({ cookie: { @@ -70,14 +78,17 @@ class AppSession { } unset(key) { + this.isPending = true; this.session.unset(key); } set(key, value) { + this.isPending = true; this.session.set(key, value); } commit() { + this.isPending = false; return this.sessionStorage.commitSession(this.session); } } @@ -102,14 +113,7 @@ export async function loader({context}) { } `); - return json( - {customer: data.customer}, - { - headers: { - 'Set-Cookie': await context.session.commit(), - }, - }, - ); + return json({customer: data.customer}); } export function ErrorBoundary() { diff --git a/packages/hydrogen/src/customer/customer.auth-handler.example.tsx b/packages/hydrogen/src/customer/customer.auth-handler.example.tsx index 48c070fa4a..a7e01db439 100644 --- a/packages/hydrogen/src/customer/customer.auth-handler.example.tsx +++ b/packages/hydrogen/src/customer/customer.auth-handler.example.tsx @@ -42,14 +42,22 @@ export default { build: remixBuild, mode: process.env.NODE_ENV, /* Inject the customer account client in the Remix context */ - getLoadContext: () => ({customerAccount}), + getLoadContext: () => ({session, customerAccount}), }); - return handleRequest(request); + const response = await handleRequest(request); + + if (session.isPending) { + response.headers.set('Set-Cookie', await session.commit()); + } + + return response; }, }; class AppSession implements HydrogenSession { + public isPending = false; + constructor( private sessionStorage: SessionStorage, private session: Session, @@ -84,18 +92,38 @@ class AppSession implements HydrogenSession { } unset(key: string) { + this.isPending = true; this.session.unset(key); } set(key: string, value: any) { + this.isPending = true; this.session.set(key, value); } commit() { + this.isPending = false; return this.sessionStorage.commitSession(this.session); } } +// In env.d.ts +import type {CustomerAccount, HydrogenSessionData} from '@shopify/hydrogen'; +declare module '@shopify/remix-oxygen' { + /** + * Declare local additions to the Remix loader context. + */ + interface AppLoadContext { + customerAccount: CustomerAccount; + session: AppSession; + } + + /** + * Declare local additions to the Remix session data. + */ + interface SessionData extends HydrogenSessionData {} +} + ///////////////////////////////// // In a route import { @@ -118,14 +146,7 @@ export async function loader({context}: LoaderFunctionArgs) { } `); - return json( - {customer: data.customer}, - { - headers: { - 'Set-Cookie': await context.session.commit(), - }, - }, - ); + return json({customer: data.customer}); } export function ErrorBoundary() { diff --git a/packages/hydrogen/src/customer/customer.example.jsx b/packages/hydrogen/src/customer/customer.example.jsx index e9836441cf..55085ee4be 100644 --- a/packages/hydrogen/src/customer/customer.example.jsx +++ b/packages/hydrogen/src/customer/customer.example.jsx @@ -28,11 +28,19 @@ export default { getLoadContext: () => ({customerAccount}), }); - return handleRequest(request); + const response = await handleRequest(request); + + if (session.isPending) { + response.headers.set('Set-Cookie', await session.commit()); + } + + return response; }, }; class AppSession { + isPending = false; + static async init(request, secrets) { const storage = createCookieSessionStorage({ cookie: { @@ -62,14 +70,17 @@ class AppSession { } unset(key) { + this.isPending = true; this.session.unset(key); } set(key, value) { + this.isPending = true; this.session.set(key, value); } commit() { + this.isPending = false; return this.sessionStorage.commitSession(this.session); } } diff --git a/packages/hydrogen/src/customer/customer.example.tsx b/packages/hydrogen/src/customer/customer.example.tsx index 1f5b4420cc..9a06f43989 100644 --- a/packages/hydrogen/src/customer/customer.example.tsx +++ b/packages/hydrogen/src/customer/customer.example.tsx @@ -37,11 +37,19 @@ export default { getLoadContext: () => ({customerAccount}), }); - return handleRequest(request); + const response = await handleRequest(request); + + if (session.isPending) { + response.headers.set('Set-Cookie', await session.commit()); + } + + return response; }, }; class AppSession implements HydrogenSession { + public isPending = false; + constructor( private sessionStorage: SessionStorage, private session: Session, @@ -76,14 +84,17 @@ class AppSession implements HydrogenSession { } unset(key: string) { + this.isPending = true; this.session.unset(key); } set(key: string, value: any) { + this.isPending = true; this.session.set(key, value); } commit() { + this.isPending = false; return this.sessionStorage.commitSession(this.session); } } diff --git a/packages/hydrogen/src/customer/customer.opt-out-handler.example.jsx b/packages/hydrogen/src/customer/customer.opt-out-handler.example.jsx index 12245478bf..e9e4d29ba7 100644 --- a/packages/hydrogen/src/customer/customer.opt-out-handler.example.jsx +++ b/packages/hydrogen/src/customer/customer.opt-out-handler.example.jsx @@ -1,3 +1,100 @@ +import {createCustomerAccountClient} from '@shopify/hydrogen'; +import * as remixBuild from '@remix-run/dev/server-build'; +import { + createRequestHandler, + createCookieSessionStorage, +} from '@shopify/remix-oxygen'; + +// In server.ts +export default { + async fetch(request, env, executionContext) { + const session = await AppSession.init(request, [env.SESSION_SECRET]); + + function customAuthStatusHandler() { + return new Response('Customer is not login', { + status: 401, + }); + } + + /* Create a Customer API client with your credentials and options */ + const customerAccount = createCustomerAccountClient({ + /* Runtime utility in serverless environments */ + waitUntil: (p) => executionContext.waitUntil(p), + /* Public Customer Account API client ID for your store */ + customerAccountId: env.PUBLIC_CUSTOMER_ACCOUNT_ID, + /* Public account URL for your store */ + customerAccountUrl: env.PUBLIC_CUSTOMER_ACCOUNT_URL, + request, + session, + customAuthStatusHandler, + }); + + const handleRequest = createRequestHandler({ + build: remixBuild, + mode: process.env.NODE_ENV, + /* Inject the customer account client in the Remix context */ + getLoadContext: () => ({customerAccount}), + }); + + const response = await handleRequest(request); + + if (session.isPending) { + response.headers.set('Set-Cookie', await session.commit()); + } + + return response; + }, +}; + +class AppSession { + isPending = false; + + static async init(request, secrets) { + const storage = createCookieSessionStorage({ + cookie: { + name: 'session', + httpOnly: true, + path: '/', + sameSite: 'lax', + secrets, + }, + }); + + const session = await storage.getSession(request.headers.get('Cookie')); + + return new this(storage, session); + } + + get(key) { + return this.session.get(key); + } + + destroy() { + return this.sessionStorage.destroySession(this.session); + } + + flash(key, value) { + this.session.flash(key, value); + } + + unset(key) { + this.isPending = true; + this.session.unset(key); + } + + set(key, value) { + this.isPending = true; + this.session.set(key, value); + } + + commit() { + this.isPending = false; + return this.sessionStorage.commitSession(this.session); + } +} + +///////////////////////////////// +// In a route import { useLoaderData, useRouteError, @@ -24,14 +121,7 @@ export async function loader({context}) { `, ); - return json( - {customer: data.customer}, - { - headers: { - 'Set-Cookie': await context.session.commit(), - }, - }, - ); + return json({customer: data.customer}); } export function ErrorBoundary() { @@ -53,7 +143,8 @@ export function ErrorBoundary() { } } -export default function () { +// this should be an default export +export function Route() { const {customer} = useLoaderData(); return ( diff --git a/packages/hydrogen/src/customer/customer.opt-out-handler.example.tsx b/packages/hydrogen/src/customer/customer.opt-out-handler.example.tsx index 9cb22b2486..2960c3ff61 100644 --- a/packages/hydrogen/src/customer/customer.opt-out-handler.example.tsx +++ b/packages/hydrogen/src/customer/customer.opt-out-handler.example.tsx @@ -1,29 +1,63 @@ -import type {CustomerAccount} from '@shopify/hydrogen'; -import {type HydrogenSession} from '@shopify/hydrogen'; import { + createCustomerAccountClient, + type HydrogenSession, +} from '@shopify/hydrogen'; +import * as remixBuild from '@remix-run/dev/server-build'; +import { + createRequestHandler, createCookieSessionStorage, type SessionStorage, type Session, } from '@shopify/remix-oxygen'; -import { - useLoaderData, - useRouteError, - isRouteErrorResponse, - useLocation, -} from '@remix-run/react'; -import {type LoaderFunctionArgs, json} from '@shopify/remix-oxygen'; -declare module '@shopify/remix-oxygen' { - /** - * Declare local additions to the Remix loader context. - */ - export interface AppLoadContext { - customerAccount: CustomerAccount; - session: AppSession; - } -} +// In server.ts +export default { + async fetch( + request: Request, + env: Record, + executionContext: ExecutionContext, + ) { + const session = await AppSession.init(request, [env.SESSION_SECRET]); + + function customAuthStatusHandler() { + return new Response('Customer is not login', { + status: 401, + }); + } + + /* Create a Customer API client with your credentials and options */ + const customerAccount = createCustomerAccountClient({ + /* Runtime utility in serverless environments */ + waitUntil: (p) => executionContext.waitUntil(p), + /* Public Customer Account API client ID for your store */ + customerAccountId: env.PUBLIC_CUSTOMER_ACCOUNT_ID, + /* Public account URL for your store */ + customerAccountUrl: env.PUBLIC_CUSTOMER_ACCOUNT_URL, + request, + session, + customAuthStatusHandler, + }); + + const handleRequest = createRequestHandler({ + build: remixBuild, + mode: process.env.NODE_ENV, + /* Inject the customer account client in the Remix context */ + getLoadContext: () => ({customerAccount}), + }); + + const response = await handleRequest(request); + + if (session.isPending) { + response.headers.set('Set-Cookie', await session.commit()); + } + + return response; + }, +}; class AppSession implements HydrogenSession { + public isPending = false; + constructor( private sessionStorage: SessionStorage, private session: Session, @@ -58,18 +92,31 @@ class AppSession implements HydrogenSession { } unset(key: string) { + this.isPending = true; this.session.unset(key); } set(key: string, value: any) { + this.isPending = true; this.session.set(key, value); } commit() { + this.isPending = false; return this.sessionStorage.commitSession(this.session); } } +///////////////////////////////// +// In a route +import { + useLoaderData, + useRouteError, + isRouteErrorResponse, + useLocation, +} from '@remix-run/react'; +import {type LoaderFunctionArgs, json} from '@shopify/remix-oxygen'; + export async function loader({context}: LoaderFunctionArgs) { if (!(await context.customerAccount.isLoggedIn())) { throw new Response('Customer is not login', { @@ -77,25 +124,18 @@ export async function loader({context}: LoaderFunctionArgs) { }); } - const {data} = await context.customerAccount.query<{ - customer: {firstName: string; lastName: string}; - }>(`#graphql + const {data} = await context.customerAccount.query( + `#graphql query getCustomer { customer { firstName lastName } } - `); - - return json( - {customer: data.customer}, - { - headers: { - 'Set-Cookie': await context.session.commit(), - }, - }, + `, ); + + return json({customer: data.customer}); } export function ErrorBoundary() { @@ -117,7 +157,8 @@ export function ErrorBoundary() { } } -export default function () { +// this should be an default export +export function Route() { const {customer} = useLoaderData(); return ( diff --git a/packages/hydrogen/src/customer/customer.test.ts b/packages/hydrogen/src/customer/customer.test.ts index 2894391f78..20f889d02e 100644 --- a/packages/hydrogen/src/customer/customer.test.ts +++ b/packages/hydrogen/src/customer/customer.test.ts @@ -98,7 +98,6 @@ describe('customer', () => { ); expect(response.status).toBe(302); - expect(response.headers.get('Set-Cookie')).toBe('cookie'); const url = new URL(response.headers.get('location')!); expect(url.origin).toBe('https://customer-api'); @@ -185,7 +184,6 @@ describe('customer', () => { const response = await customer.logout(); expect(response.status).toBe(302); - expect(response.headers.get('Set-Cookie')).toBe('cookie'); const url = new URL(response.headers.get('location')!); expect(url.origin).toBe('https://customer-api'); @@ -515,9 +513,6 @@ describe('customer', () => { expect(response.status).toBe(302); expect(response.headers.get('location')).toBe('/account'); - expect(response.headers.get('Set-Cookie')).toStrictEqual( - expect.any(String), - ); expect(session.set).toHaveBeenCalledWith( CUSTOMER_ACCOUNT_SESSION_KEY, @@ -565,9 +560,6 @@ describe('customer', () => { expect(response.status).toBe(302); expect(response.headers.get('location')).toBe(redirectPath); - expect(response.headers.get('Set-Cookie')).toStrictEqual( - expect.any(String), - ); expect(session.set).toHaveBeenCalledWith( CUSTOMER_ACCOUNT_SESSION_KEY, diff --git a/packages/hydrogen/src/customer/customer.ts b/packages/hydrogen/src/customer/customer.ts index af210ea547..25a6e0971f 100644 --- a/packages/hydrogen/src/customer/customer.ts +++ b/packages/hydrogen/src/customer/customer.ts @@ -166,9 +166,6 @@ export function createCustomerAccountClient({ clearSession(session); const authFailResponse = authStatusHandler(); - if (authFailResponse instanceof Response) { - authFailResponse.headers.set('Set-Cookie', await session.commit()); - } throw authFailResponse; } @@ -378,11 +375,7 @@ export function createCustomerAccountClient({ loginUrl.searchParams.append('code_challenge', challenge); loginUrl.searchParams.append('code_challenge_method', 'S256'); - return redirect(loginUrl.toString(), { - headers: { - 'Set-Cookie': await session.commit(), - }, - }); + return redirect(loginUrl.toString()); }, logout: async (options?: LogoutOptions) => { @@ -406,11 +399,7 @@ export function createCustomerAccountClient({ clearSession(session); - return redirect(logoutUrl, { - headers: { - 'Set-Cookie': await session.commit(), - }, - }); + return redirect(logoutUrl); }, isLoggedIn, handleAuthStatus, @@ -430,9 +419,6 @@ export function createCustomerAccountClient({ throw new BadRequest( 'Unauthorized', 'No code or state parameter found in the redirect URL.', - { - 'Set-Cookie': await session.commit(), - }, ); } @@ -442,9 +428,6 @@ export function createCustomerAccountClient({ throw new BadRequest( 'Unauthorized', 'The session state does not match the state parameter. Make sure that the session is configured correctly and passed to `createCustomerAccountClient`.', - { - 'Set-Cookie': await session.commit(), - }, ); } @@ -543,11 +526,7 @@ export function createCustomerAccountClient({ await exchangeForStorefrontCustomerAccessToken(); - return redirect(redirectPath || DEFAULT_REDIRECT_PATH, { - headers: { - 'Set-Cookie': await session.commit(), - }, - }); + return redirect(redirectPath || DEFAULT_REDIRECT_PATH); }, UNSTABLE_setBuyer: setBuyer, UNSTABLE_getBuyer: getBuyer, diff --git a/packages/hydrogen/src/hydrogen.d.ts b/packages/hydrogen/src/hydrogen.d.ts index 4ed414475f..de87038069 100644 --- a/packages/hydrogen/src/hydrogen.d.ts +++ b/packages/hydrogen/src/hydrogen.d.ts @@ -33,6 +33,7 @@ export interface HydrogenSession< commit: () => ReturnType< SessionStorage['commitSession'] >; + isPending?: boolean; } declare global { diff --git a/rfc/cart.md b/rfc/cart.md index 705b7bd0bf..7b6f719827 100644 --- a/rfc/cart.md +++ b/rfc/cart.md @@ -68,7 +68,6 @@ export async function action({request, context}) { // The Cart ID may change after each mutation. We need to update it each time in the session. session.set('cartId', cartId); - headers.set('Set-Cookie', await session.commit()); const {cart, errors} = result; return json({cart, errors}, {status, headers}); diff --git a/templates/skeleton/app/lib/session.ts b/templates/skeleton/app/lib/session.ts index dec9ebfd74..de71699ead 100644 --- a/templates/skeleton/app/lib/session.ts +++ b/templates/skeleton/app/lib/session.ts @@ -11,6 +11,8 @@ import { * swap out the cookie-based implementation with something else! */ export class AppSession implements HydrogenSession { + public isPending = false; + #sessionStorage; #session; @@ -50,10 +52,12 @@ export class AppSession implements HydrogenSession { } get unset() { + this.isPending = true; return this.#session.unset; } get set() { + this.isPending = true; return this.#session.set; } @@ -62,6 +66,7 @@ export class AppSession implements HydrogenSession { } commit() { + this.isPending = false; return this.#sessionStorage.commitSession(this.#session); } } diff --git a/templates/skeleton/app/root.tsx b/templates/skeleton/app/root.tsx index 5da14e0016..126e7d0e28 100644 --- a/templates/skeleton/app/root.tsx +++ b/templates/skeleton/app/root.tsx @@ -65,26 +65,19 @@ export async function loader(args: LoaderFunctionArgs) { const {storefront, env} = args.context; - return defer( - { - ...deferredData, - ...criticalData, - publicStoreDomain: env.PUBLIC_STORE_DOMAIN, - shop: getShopAnalytics({ - storefront, - publicStorefrontId: env.PUBLIC_STOREFRONT_ID, - }), - consent: { - checkoutDomain: env.PUBLIC_CHECKOUT_DOMAIN, - storefrontAccessToken: env.PUBLIC_STOREFRONT_API_TOKEN, - }, - }, - { - headers: { - 'Set-Cookie': await args.context.session.commit(), - }, + return defer({ + ...deferredData, + ...criticalData, + publicStoreDomain: env.PUBLIC_STORE_DOMAIN, + shop: getShopAnalytics({ + storefront, + publicStorefrontId: env.PUBLIC_STOREFRONT_ID, + }), + consent: { + checkoutDomain: env.PUBLIC_CHECKOUT_DOMAIN, + storefrontAccessToken: env.PUBLIC_STOREFRONT_API_TOKEN, }, - ); + }); } /** diff --git a/templates/skeleton/app/routes/account.$.tsx b/templates/skeleton/app/routes/account.$.tsx index ca52f2b68e..53543f62be 100644 --- a/templates/skeleton/app/routes/account.$.tsx +++ b/templates/skeleton/app/routes/account.$.tsx @@ -4,9 +4,5 @@ import {redirect, type LoaderFunctionArgs} from '@shopify/remix-oxygen'; export async function loader({context}: LoaderFunctionArgs) { await context.customerAccount.handleAuthStatus(); - return redirect('/account', { - headers: { - 'Set-Cookie': await context.session.commit(), - }, - }); + return redirect('/account'); } diff --git a/templates/skeleton/app/routes/account.addresses.tsx b/templates/skeleton/app/routes/account.addresses.tsx index 2075c50cf4..b45bced01f 100644 --- a/templates/skeleton/app/routes/account.addresses.tsx +++ b/templates/skeleton/app/routes/account.addresses.tsx @@ -37,14 +37,7 @@ export const meta: MetaFunction = () => { export async function loader({context}: LoaderFunctionArgs) { await context.customerAccount.handleAuthStatus(); - return json( - {}, - { - headers: { - 'Set-Cookie': await context.session.commit(), - }, - }, - ); + return json({}); } export async function action({request, context}: ActionFunctionArgs) { @@ -67,9 +60,6 @@ export async function action({request, context}: ActionFunctionArgs) { {error: {[addressId]: 'Unauthorized'}}, { status: 401, - headers: { - 'Set-Cookie': await context.session.commit(), - }, }, ); } @@ -121,27 +111,17 @@ export async function action({request, context}: ActionFunctionArgs) { throw new Error('Customer address create failed.'); } - return json( - { - error: null, - createdAddress: data?.customerAddressCreate?.customerAddress, - defaultAddress, - }, - { - headers: { - 'Set-Cookie': await context.session.commit(), - }, - }, - ); + return json({ + error: null, + createdAddress: data?.customerAddressCreate?.customerAddress, + defaultAddress, + }); } catch (error: unknown) { if (error instanceof Error) { return json( {error: {[addressId]: error.message}}, { status: 400, - headers: { - 'Set-Cookie': await context.session.commit(), - }, }, ); } @@ -149,9 +129,6 @@ export async function action({request, context}: ActionFunctionArgs) { {error: {[addressId]: error}}, { status: 400, - headers: { - 'Set-Cookie': await context.session.commit(), - }, }, ); } @@ -183,27 +160,17 @@ export async function action({request, context}: ActionFunctionArgs) { throw new Error('Customer address update failed.'); } - return json( - { - error: null, - updatedAddress: address, - defaultAddress, - }, - { - headers: { - 'Set-Cookie': await context.session.commit(), - }, - }, - ); + return json({ + error: null, + updatedAddress: address, + defaultAddress, + }); } catch (error: unknown) { if (error instanceof Error) { return json( {error: {[addressId]: error.message}}, { status: 400, - headers: { - 'Set-Cookie': await context.session.commit(), - }, }, ); } @@ -211,9 +178,6 @@ export async function action({request, context}: ActionFunctionArgs) { {error: {[addressId]: error}}, { status: 400, - headers: { - 'Set-Cookie': await context.session.commit(), - }, }, ); } @@ -241,23 +205,13 @@ export async function action({request, context}: ActionFunctionArgs) { throw new Error('Customer address delete failed.'); } - return json( - {error: null, deletedAddress: addressId}, - { - headers: { - 'Set-Cookie': await context.session.commit(), - }, - }, - ); + return json({error: null, deletedAddress: addressId}); } catch (error: unknown) { if (error instanceof Error) { return json( {error: {[addressId]: error.message}}, { status: 400, - headers: { - 'Set-Cookie': await context.session.commit(), - }, }, ); } @@ -265,9 +219,6 @@ export async function action({request, context}: ActionFunctionArgs) { {error: {[addressId]: error}}, { status: 400, - headers: { - 'Set-Cookie': await context.session.commit(), - }, }, ); } @@ -278,9 +229,6 @@ export async function action({request, context}: ActionFunctionArgs) { {error: {[addressId]: 'Method not allowed'}}, { status: 405, - headers: { - 'Set-Cookie': await context.session.commit(), - }, }, ); } @@ -291,9 +239,6 @@ export async function action({request, context}: ActionFunctionArgs) { {error: error.message}, { status: 400, - headers: { - 'Set-Cookie': await context.session.commit(), - }, }, ); } @@ -301,9 +246,6 @@ export async function action({request, context}: ActionFunctionArgs) { {error}, { status: 400, - headers: { - 'Set-Cookie': await context.session.commit(), - }, }, ); } diff --git a/templates/skeleton/app/routes/account.orders.$id.tsx b/templates/skeleton/app/routes/account.orders.$id.tsx index 29af53a73c..18e9f596ce 100644 --- a/templates/skeleton/app/routes/account.orders.$id.tsx +++ b/templates/skeleton/app/routes/account.orders.$id.tsx @@ -40,20 +40,13 @@ export async function loader({params, context}: LoaderFunctionArgs) { firstDiscount?.__typename === 'PricingPercentageValue' && firstDiscount?.percentage; - return json( - { - order, - lineItems, - discountValue, - discountPercentage, - fulfillmentStatus, - }, - { - headers: { - 'Set-Cookie': await context.session.commit(), - }, - }, - ); + return json({ + order, + lineItems, + discountValue, + discountPercentage, + fulfillmentStatus, + }); } export default function OrderRoute() { diff --git a/templates/skeleton/app/routes/account.orders._index.tsx b/templates/skeleton/app/routes/account.orders._index.tsx index 2b41dfc0e2..76ae6a63a2 100644 --- a/templates/skeleton/app/routes/account.orders._index.tsx +++ b/templates/skeleton/app/routes/account.orders._index.tsx @@ -34,14 +34,7 @@ export async function loader({request, context}: LoaderFunctionArgs) { throw Error('Customer orders not found'); } - return json( - {customer: data.customer}, - { - headers: { - 'Set-Cookie': await context.session.commit(), - }, - }, - ); + return json({customer: data.customer}); } export default function Orders() { diff --git a/templates/skeleton/app/routes/account.profile.tsx b/templates/skeleton/app/routes/account.profile.tsx index 942a9981a7..2068e8fae5 100644 --- a/templates/skeleton/app/routes/account.profile.tsx +++ b/templates/skeleton/app/routes/account.profile.tsx @@ -26,14 +26,7 @@ export const meta: MetaFunction = () => { export async function loader({context}: LoaderFunctionArgs) { await context.customerAccount.handleAuthStatus(); - return json( - {}, - { - headers: { - 'Set-Cookie': await context.session.commit(), - }, - }, - ); + return json({}); } export async function action({request, context}: ActionFunctionArgs) { @@ -75,25 +68,15 @@ export async function action({request, context}: ActionFunctionArgs) { throw new Error('Customer profile update failed.'); } - return json( - { - error: null, - customer: data?.customerUpdate?.customer, - }, - { - headers: { - 'Set-Cookie': await context.session.commit(), - }, - }, - ); + return json({ + error: null, + customer: data?.customerUpdate?.customer, + }); } catch (error: any) { return json( {error: error.message, customer: null}, { status: 400, - headers: { - 'Set-Cookie': await context.session.commit(), - }, }, ); } diff --git a/templates/skeleton/app/routes/account.tsx b/templates/skeleton/app/routes/account.tsx index 574c2ea664..583e62c549 100644 --- a/templates/skeleton/app/routes/account.tsx +++ b/templates/skeleton/app/routes/account.tsx @@ -20,7 +20,6 @@ export async function loader({context}: LoaderFunctionArgs) { { headers: { 'Cache-Control': 'no-cache, no-store, must-revalidate', - 'Set-Cookie': await context.session.commit(), }, }, ); diff --git a/templates/skeleton/app/routes/cart.tsx b/templates/skeleton/app/routes/cart.tsx index 3c6eb69b65..12b475f071 100644 --- a/templates/skeleton/app/routes/cart.tsx +++ b/templates/skeleton/app/routes/cart.tsx @@ -68,8 +68,6 @@ export async function action({request, context}: ActionFunctionArgs) { headers.set('Location', redirectTo); } - headers.append('Set-Cookie', await context.session.commit()); - return json( { cart: cartResult, diff --git a/templates/skeleton/server.ts b/templates/skeleton/server.ts index ff1ce2ba87..448553607c 100644 --- a/templates/skeleton/server.ts +++ b/templates/skeleton/server.ts @@ -96,6 +96,10 @@ export default { const response = await handleRequest(request); + if (session.isPending) { + response.headers.set('Set-Cookie', await session.commit()); + } + if (response.status === 404) { /** * Check for redirects only when there's a 404 from the app.