@faststore/core 3.95.0 → 3.96.0-dev.17

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.
Files changed (110) hide show
  1. package/.next/BUILD_ID +1 -1
  2. package/.next/build-manifest.json +54 -54
  3. package/.next/cache/.tsbuildinfo +1 -1
  4. package/.next/cache/config.json +3 -3
  5. package/.next/cache/webpack/client-production/0.pack +0 -0
  6. package/.next/cache/webpack/client-production/index.pack +0 -0
  7. package/.next/cache/webpack/server-production/0.pack +0 -0
  8. package/.next/cache/webpack/server-production/index.pack +0 -0
  9. package/.next/prerender-manifest.js +1 -1
  10. package/.next/prerender-manifest.json +1 -1
  11. package/.next/react-loadable-manifest.json +12 -12
  12. package/.next/routes-manifest.json +1 -1
  13. package/.next/server/chunks/7098.js +1 -1
  14. package/.next/server/chunks/948.js +1 -1
  15. package/.next/server/chunks/9563.js +3 -3
  16. package/.next/server/chunks/9630.js +7 -7
  17. package/.next/server/functions-config-manifest.json +1 -1
  18. package/.next/server/middleware-build-manifest.js +1 -1
  19. package/.next/server/middleware-react-loadable-manifest.js +1 -1
  20. package/.next/server/pages/[...slug].js +1 -1
  21. package/.next/server/pages/[slug]/p.js +1 -1
  22. package/.next/server/pages/api/graphql.js +3 -3
  23. package/.next/server/pages/en-US/404.html +1 -1
  24. package/.next/server/pages/en-US/500.html +1 -1
  25. package/.next/server/pages/en-US/checkout.html +1 -1
  26. package/.next/server/pages/en-US/login.html +1 -1
  27. package/.next/server/pages/en-US/s.html +1 -1
  28. package/.next/server/pages/en-US.html +1 -1
  29. package/.next/server/pages/en-US.json +1 -1
  30. package/.next/server/pages/pvt/account/orders/[id].js +1 -1
  31. package/.next/server/pages/s.js +1 -1
  32. package/.next/server/pages-manifest.json +1 -1
  33. package/.next/static/Yo88n9O6FrhtIc3GGfGv9/_buildManifest.js +1 -0
  34. package/.next/static/chunks/{1841.b3a98a2a1886b09c.js → 1841.a1b15f2c88e02d32.js} +1 -1
  35. package/.next/static/chunks/{2500.d6beb5af6ee00a9e.js → 2500.b188ad84b5a0a521.js} +1 -1
  36. package/.next/static/chunks/{2927.23bae2c79f0ac0f3.js → 2927.5a79877943a6bf7c.js} +1 -1
  37. package/.next/static/chunks/{7191-5aa417081c97d46a.js → 7191-e43007cdde83bc1f.js} +1 -1
  38. package/.next/static/chunks/{9173-49a05f6f96710bd1.js → 9173-2ed920b87ee6640e.js} +1 -1
  39. package/.next/static/chunks/{UIToast.de15325248043ce5.js → UIToast.19a8664c01a00d3a.js} +1 -1
  40. package/.next/static/chunks/pages/{404-4b3f1e22c77f849a.js → 404-f04d51a89ab8e8c8.js} +1 -1
  41. package/.next/static/chunks/pages/{500-bb2de0c11524b2b8.js → 500-b3cd1219951eb7d1.js} +1 -1
  42. package/.next/static/chunks/pages/{[...slug]-3579b3b8b8309552.js → [...slug]-28b3f75a1b2b22b8.js} +1 -1
  43. package/.next/static/chunks/pages/[slug]/{p-cf620759e25940f8.js → p-f3b7653e4bd1ba57.js} +1 -1
  44. package/.next/static/chunks/pages/{_app-8e3a3eccc99a6f96.js → _app-38f75642a57fe506.js} +1 -1
  45. package/.next/static/chunks/pages/{checkout-dc07d72b3514c83d.js → checkout-9a253c6c2498d41f.js} +1 -1
  46. package/.next/static/chunks/pages/{index-1f8bf0fb351b90f4.js → index-f522924c892c46dd.js} +1 -1
  47. package/.next/static/chunks/pages/{login-e97ef92769456bf6.js → login-1d364797aaa9da36.js} +1 -1
  48. package/.next/static/chunks/pages/pvt/account/{403-91597100f8956385.js → 403-2105ed93b3e3e58e.js} +1 -1
  49. package/.next/static/chunks/pages/pvt/account/{404-c68124bed7cb7c48.js → 404-2e555481b1a4f63b.js} +1 -1
  50. package/.next/static/chunks/pages/pvt/account/orders/{[id]-68bbe8db444fd60a.js → [id]-2bd9adec59ae1ed3.js} +1 -1
  51. package/.next/static/chunks/pages/pvt/account/{orders-12a215899ae6f3eb.js → orders-4d66173a6e6282a1.js} +1 -1
  52. package/.next/static/chunks/pages/pvt/account/{profile-b6cdbf02d4682544.js → profile-1799ddf1749086aa.js} +1 -1
  53. package/.next/static/chunks/pages/pvt/account/{security-e6289a40e745d3c4.js → security-f2734d61da9099db.js} +1 -1
  54. package/.next/static/chunks/pages/pvt/account/{user-details-fba1822e52e7de26.js → user-details-c8636b99b6725815.js} +1 -1
  55. package/.next/static/chunks/pages/{s-41cffb20de1d2617.js → s-603fab8b09726274.js} +1 -1
  56. package/.next/static/chunks/{webpack-96e371f26b822ac6.js → webpack-3cd689c2fd3d4dd1.js} +1 -1
  57. package/.next/trace +138 -138
  58. package/.turbo/turbo-build.log +17 -17
  59. package/.turbo/turbo-test.log +8 -8
  60. package/@generated/gql.ts +2 -2
  61. package/@generated/graphql.ts +6 -1
  62. package/@generated/persisted-documents.json +1 -1
  63. package/@generated/schema.graphql +3 -0
  64. package/CHANGELOG.md +86 -0
  65. package/cms/faststore/base.jsonc +42 -0
  66. package/cms/faststore/components/cms_component__alert.jsonc +40 -0
  67. package/cms/faststore/components/cms_component__bannernewsletter.jsonc +183 -0
  68. package/cms/faststore/components/cms_component__bannertext.jsonc +51 -0
  69. package/cms/faststore/components/cms_component__breadcrumb.jsonc +21 -0
  70. package/cms/faststore/components/cms_component__cartsidebar.jsonc +122 -0
  71. package/cms/faststore/components/cms_component__children.jsonc +8 -0
  72. package/cms/faststore/components/cms_component__crosssellingshelf.jsonc +50 -0
  73. package/cms/faststore/components/cms_component__emptystate.jsonc +73 -0
  74. package/cms/faststore/components/cms_component__footer.jsonc +238 -0
  75. package/cms/faststore/components/cms_component__hero.jsonc +71 -0
  76. package/cms/faststore/components/cms_component__incentives.jsonc +51 -0
  77. package/cms/faststore/components/cms_component__navbar.jsonc +318 -0
  78. package/cms/faststore/components/cms_component__newsletter.jsonc +133 -0
  79. package/cms/faststore/components/cms_component__productdetails.jsonc +246 -0
  80. package/cms/faststore/components/cms_component__productgallery.jsonc +312 -0
  81. package/cms/faststore/components/cms_component__productshelf.jsonc +117 -0
  82. package/cms/faststore/components/cms_component__producttiles.jsonc +95 -0
  83. package/cms/faststore/components/cms_component__regionbar.jsonc +58 -0
  84. package/cms/faststore/components/cms_component__regionmodal.jsonc +85 -0
  85. package/cms/faststore/components/cms_component__regionpopover.jsonc +97 -0
  86. package/cms/faststore/components/cms_component__search.jsonc +144 -0
  87. package/cms/faststore/pages/cms_content_type__404.jsonc +18 -0
  88. package/cms/faststore/pages/cms_content_type__500.jsonc +18 -0
  89. package/cms/faststore/pages/cms_content_type__globalfootersections.jsonc +73 -0
  90. package/cms/faststore/pages/cms_content_type__globalheadersections.jsonc +73 -0
  91. package/cms/faststore/pages/cms_content_type__globalsections.jsonc +375 -0
  92. package/cms/faststore/pages/cms_content_type__home.jsonc +213 -0
  93. package/cms/faststore/pages/cms_content_type__landingpage.jsonc +108 -0
  94. package/cms/faststore/pages/cms_content_type__login.jsonc +18 -0
  95. package/cms/faststore/pages/cms_content_type__pdp.jsonc +115 -0
  96. package/cms/faststore/pages/cms_content_type__plp.jsonc +130 -0
  97. package/cms/faststore/pages/cms_content_type__search.jsonc +118 -0
  98. package/cms/faststore/schema.json +3859 -0
  99. package/cms/faststore/sections.json +2 -2
  100. package/package.json +15 -8
  101. package/src/components/account/orders/MyAccountOrderDetails/MyAccountDeliveryOptionAccordion/MyAccountDeliveryOptionAccordion.tsx +18 -15
  102. package/src/components/account/orders/MyAccountOrderDetails/MyAccountOrderDetails.tsx +9 -3
  103. package/src/components/account/orders/MyAccountOrderDetails/MyAccountSummaryCard/MyAccountSummaryCard.tsx +0 -1
  104. package/src/pages/api/graphql.ts +26 -5
  105. package/src/pages/pvt/account/orders/[id].tsx +2 -0
  106. package/src/sdk/graphql/request.ts +10 -1
  107. package/src/utils/cookieCacheBusting.ts +141 -0
  108. package/test/utils/cookieCacheBusting.test.ts +121 -0
  109. package/.next/static/n8Y8ofpptOjFUsgm9BTVP/_buildManifest.js +0 -1
  110. /package/.next/static/{n8Y8ofpptOjFUsgm9BTVP → Yo88n9O6FrhtIc3GGfGv9}/_ssgManifest.js +0 -0
@@ -959,7 +959,7 @@
959
959
  "after": {
960
960
  "type": "integer",
961
961
  "title": "After",
962
- "default": "0",
962
+ "default": 0,
963
963
  "description": "Initial pagination item"
964
964
  },
965
965
  "sort": {
@@ -1122,7 +1122,7 @@
1122
1122
  "after": {
1123
1123
  "title": "After",
1124
1124
  "type": "integer",
1125
- "default": "0",
1125
+ "default": 0,
1126
1126
  "description": "Initial pagination item"
1127
1127
  },
1128
1128
  "sort": {
package/package.json CHANGED
@@ -1,8 +1,12 @@
1
1
  {
2
2
  "name": "@faststore/core",
3
- "version": "3.95.0",
3
+ "version": "3.96.0-dev.17",
4
4
  "license": "MIT",
5
- "repository": "vtex/faststore",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "https://github.com/vtex/faststore",
8
+ "directory": "packages/core"
9
+ },
6
10
  "browserslist": "supports es6-module and not dead",
7
11
  "exports": {
8
12
  ".": "./index.ts",
@@ -36,6 +40,9 @@
36
40
  "pnpm": "9.15.1",
37
41
  "yarn": "1.22.22"
38
42
  },
43
+ "publishConfig": {
44
+ "access": "public"
45
+ },
39
46
  "sideEffects": false,
40
47
  "dependencies": {
41
48
  "@antfu/ni": "^0.21.12",
@@ -44,11 +51,11 @@
44
51
  "@envelop/graphql-jit": "^8.0.3",
45
52
  "@envelop/parser-cache": "^6.0.2",
46
53
  "@envelop/validation-cache": "^6.0.2",
47
- "@faststore/api": "3.95.0",
48
- "@faststore/graphql-utils": "^3.95.0",
49
- "@faststore/lighthouse": "3.95.0",
50
- "@faststore/sdk": "3.95.0",
51
- "@faststore/ui": "3.95.0",
54
+ "@faststore/api": "3.96.0-dev.17",
55
+ "@faststore/graphql-utils": "^3.96.0-dev.17",
56
+ "@faststore/lighthouse": "3.96.0-dev.17",
57
+ "@faststore/sdk": "3.96.0-dev.17",
58
+ "@faststore/ui": "3.96.0-dev.17",
52
59
  "@graphql-codegen/cli": "5.0.2",
53
60
  "@graphql-codegen/client-preset": "4.2.6",
54
61
  "@graphql-codegen/typescript": "4.0.7",
@@ -116,5 +123,5 @@
116
123
  "ts-jest": "29.1.1",
117
124
  "typescript": "5.3.2"
118
125
  },
119
- "gitHead": "a9697d86117f2027c4580b8c26e70d27aa1a089d"
126
+ "gitHead": "303ea1a81126ab23f136c241a18f00c681c574d8"
120
127
  }
@@ -69,21 +69,24 @@ function MyAccountDeliveryOptionAccordion({
69
69
  contact={contact}
70
70
  />
71
71
  <MyAccountDeliveryOptionAccordionProducts>
72
- {deliveryOption.items?.map((item, index) => (
73
- <MyAccountDeliveryOptionAccordionProduct
74
- key={index}
75
- image={item.imageUrl || ''}
76
- quantity={item.quantity}
77
- name={item.name}
78
- field={
79
- customFields?.find((cf) => cf.id === item.uniqueId)
80
- ?.fields?.[0]
81
- }
82
- price={formatPrice(item.price ?? 0, currencyCode)}
83
- tax={formatPrice(item.tax ?? 0, currencyCode)}
84
- total={formatPrice(item.total ?? 0, currencyCode)}
85
- />
86
- ))}
72
+ {deliveryOption.items?.map((item, index) => {
73
+ const tax = (item.tax ?? 0) + (item.taxPriceTagsTotal ?? 0)
74
+ return (
75
+ <MyAccountDeliveryOptionAccordionProduct
76
+ key={index}
77
+ image={item.imageUrl || ''}
78
+ quantity={item.quantity}
79
+ name={item.name}
80
+ field={
81
+ customFields?.find((cf) => cf.id === item.uniqueId)
82
+ ?.fields?.[0]
83
+ }
84
+ price={formatPrice(item.sellingPrice ?? 0, currencyCode)}
85
+ tax={formatPrice(tax, currencyCode)}
86
+ total={formatPrice(item.total ?? 0, currencyCode)}
87
+ />
88
+ )
89
+ })}
87
90
  </MyAccountDeliveryOptionAccordionProducts>
88
91
  </MyAccountAccordionPanel>
89
92
  </MyAccountAccordionItem>
@@ -8,7 +8,11 @@ import MyAccountOrderedByCard from './MyAccountOrderedByCard'
8
8
  import MyAccountPaymentCard from './MyAccountPaymentCard'
9
9
  import MyAccountSummaryCard from './MyAccountSummaryCard'
10
10
 
11
- import type { ServerOrderDetailsQueryQuery } from '@generated/graphql'
11
+ import type {
12
+ ServerOrderDetailsQueryQuery,
13
+ UserOrderDeliveryOption,
14
+ UserOrderDeliveryOptionsData,
15
+ } from '@generated/graphql'
12
16
  import type { OrderStatusKey } from 'src/utils/userOrderStatus'
13
17
  import MyAccountStatusBadge from '../../components/MyAccountStatusBadge'
14
18
  import MyAccountMoreInformationCard from './MyAccountMoreInformationCard'
@@ -72,7 +76,9 @@ export default function MyAccountOrderDetails({
72
76
  />
73
77
 
74
78
  <MyAccountDeliveryCard
75
- deliveryOptionsData={order.deliveryOptionsData}
79
+ deliveryOptionsData={
80
+ order.deliveryOptionsData as UserOrderDeliveryOptionsData
81
+ }
76
82
  fields={
77
83
  order?.customFields?.find((field) => field.type === 'address')
78
84
  ?.fields || []
@@ -99,7 +105,7 @@ export default function MyAccountOrderDetails({
99
105
  {order.deliveryOptionsData.deliveryOptions.map((option) => (
100
106
  <MyAccountDeliveryOptionAccordion
101
107
  key={option.friendlyDeliveryOptionName}
102
- deliveryOption={option}
108
+ deliveryOption={option as UserOrderDeliveryOption}
103
109
  contact={order.deliveryOptionsData.contact}
104
110
  currencyCode={order.storePreferencesData.currencyCode}
105
111
  customFields={order.customFields.filter(
@@ -1,6 +1,5 @@
1
1
  import MyAccountCard from 'src/components/account/components/MyAccountCard'
2
2
  import { useFormatPrice } from 'src/components/account/utils/useFormatPrice'
3
- import { useSession } from 'src/sdk/session'
4
3
 
5
4
  // Interface for order totals (items, shipping, discounts)
6
5
  // TODO: Use type from API
@@ -4,6 +4,7 @@ import {
4
4
  isFastStoreError,
5
5
  stringifyCacheControl,
6
6
  } from '@faststore/api'
7
+ import { parse } from 'cookie'
7
8
  import type { NextApiHandler, NextApiRequest } from 'next'
8
9
 
9
10
  import discoveryConfig from 'discovery.config'
@@ -34,7 +35,7 @@ const replaceSetCookieDomain = (request: NextApiRequest, setCookie: string) => {
34
35
 
35
36
  const parseRequest = (request: NextApiRequest) => {
36
37
  try {
37
- const { operationName, operationHash, variables, query } =
38
+ const { operationName, operationHash, variables, query, v } =
38
39
  request.method === 'POST'
39
40
  ? request.body
40
41
  : {
@@ -45,6 +46,7 @@ const parseRequest = (request: NextApiRequest) => {
45
46
  ? request.query.variables
46
47
  : ''
47
48
  ),
49
+ v: request.query.v,
48
50
  query: undefined,
49
51
  }
50
52
 
@@ -56,6 +58,7 @@ const parseRequest = (request: NextApiRequest) => {
56
58
  },
57
59
  },
58
60
  variables,
61
+ v,
59
62
  // Do not allow queries in production, only for devMode so we can use graphql tools
60
63
  // like introspection etc. In production, we only accept known queries for better
61
64
  // security
@@ -68,6 +71,17 @@ const parseRequest = (request: NextApiRequest) => {
68
71
  }
69
72
  }
70
73
 
74
+ /**
75
+ * Checks if there is any cookie that starts with 'VtexIdclientAutCookie'
76
+ * in the request headers
77
+ */
78
+ const hasVtexIdclientAutCookie = (request: NextApiRequest): boolean => {
79
+ const cookies = parse(request.headers.cookie ?? '')
80
+ return Object.keys(cookies).some((cookieName) =>
81
+ cookieName.startsWith('VtexIdclientAutCookie')
82
+ )
83
+ }
84
+
71
85
  const handler: NextApiHandler = async (request, response) => {
72
86
  if (request.method !== 'POST' && request.method !== 'GET') {
73
87
  response.status(405).end()
@@ -76,7 +90,8 @@ const handler: NextApiHandler = async (request, response) => {
76
90
  }
77
91
 
78
92
  try {
79
- const { operation, variables, query } = parseRequest(request)
93
+ // value is used to cache bust the request if there is a VtexIdclientAutCookie
94
+ const { operation, variables, query, v: value } = parseRequest(request)
80
95
 
81
96
  if (
82
97
  operation.__meta__.operationName === 'ValidateSession' &&
@@ -129,7 +144,7 @@ const handler: NextApiHandler = async (request, response) => {
129
144
  const { data, errors, extensions } = await execute(
130
145
  {
131
146
  operation,
132
- variables,
147
+ variables: { ...variables, ...(value ? { v: value } : {}) },
133
148
  query,
134
149
  },
135
150
  { headers: request.headers }
@@ -145,8 +160,13 @@ const handler: NextApiHandler = async (request, response) => {
145
160
  return
146
161
  }
147
162
 
163
+ const hasAuthCookie = hasVtexIdclientAutCookie(request)
164
+
148
165
  if (extensions.cacheControl) {
149
- const cacheControl = stringifyCacheControl(extensions.cacheControl)
166
+ const cacheControl = stringifyCacheControl(
167
+ extensions.cacheControl,
168
+ hasAuthCookie
169
+ )
150
170
  response.setHeader('cache-control', cacheControl)
151
171
  } else if (
152
172
  request.method === 'GET' &&
@@ -167,9 +187,10 @@ const handler: NextApiHandler = async (request, response) => {
167
187
  .staleWhileRevalidate
168
188
  : DEFAULT_STALE_WHILE_REVALIDATE // 1 hour
169
189
 
190
+ const scope = hasAuthCookie ? 'private' : 'public'
170
191
  response.setHeader(
171
192
  'cache-control',
172
- `public, s-maxage=${maxAge}, stale-while-revalidate=${staleWhileRevalidate}`
193
+ `${scope}, s-maxage=${maxAge}, stale-while-revalidate=${staleWhileRevalidate}`
173
194
  )
174
195
  } else {
175
196
  response.setHeader('cache-control', 'no-cache, no-store')
@@ -211,8 +211,10 @@ const query = gql(`
211
211
  name
212
212
  quantity
213
213
  price
214
+ sellingPrice
214
215
  imageUrl
215
216
  tax
217
+ taxPriceTagsTotal
216
218
  total
217
219
  }
218
220
  }
@@ -1,3 +1,5 @@
1
+ import { getClientCacheBustingValue } from 'src/utils/cookieCacheBusting'
2
+
1
3
  export type RequestOptions = Omit<BaseRequestOptions, 'operation' | 'variables'>
2
4
  export type Operation = {
3
5
  __meta__?: Record<string, any>
@@ -12,6 +14,8 @@ export interface BaseRequestOptions<V = any> {
12
14
  operation: Operation
13
15
  variables: V
14
16
  fetchOptions?: RequestInit
17
+ cacheBusting?: boolean
18
+ value?: string | null
15
19
  }
16
20
 
17
21
  const DEFAULT_HEADERS_BY_VERB: Record<string, Record<string, string>> = {
@@ -25,10 +29,14 @@ export const request = async <Query = unknown, Variables = unknown>(
25
29
  variables: Variables,
26
30
  options?: RequestOptions
27
31
  ) => {
32
+ // Get cache busting value based on cookie changes
33
+ const value = getClientCacheBustingValue()
34
+
28
35
  const { data, errors } = await baseRequest<Variables, Query>('/api/graphql', {
29
36
  ...options,
30
37
  variables,
31
38
  operation,
39
+ value,
32
40
  })
33
41
 
34
42
  if (errors?.length) {
@@ -41,7 +49,7 @@ export const request = async <Query = unknown, Variables = unknown>(
41
49
  /* This piece of code was taken out of @faststore/graphql-utils */
42
50
  const baseRequest = async <V = any, D = any>(
43
51
  endpoint: string,
44
- { operation, variables, fetchOptions }: BaseRequestOptions<V>
52
+ { operation, variables, fetchOptions, value }: BaseRequestOptions<V>
45
53
  ): Promise<GraphQLResponse<D>> => {
46
54
  const { operationName, operationHash } = operation['__meta__']
47
55
 
@@ -58,6 +66,7 @@ const baseRequest = async <V = any, D = any>(
58
66
  operationName,
59
67
  operationHash,
60
68
  ...(method === 'GET' && { variables: JSON.stringify(variables) }),
69
+ ...(method === 'GET' && value && { v: value }),
61
70
  })
62
71
 
63
72
  const body =
@@ -0,0 +1,141 @@
1
+ import discoveryConfig from 'discovery.config'
2
+
3
+ export const STORAGE_KEY_AUTH_COOKIE_VALUE = 'faststore_auth_cookie_value'
4
+ export const STORAGE_KEY_CACHE_BUST_LAST_VALUE =
5
+ 'faststore_cache_bust_last_value'
6
+
7
+ /**
8
+ * Gets the VtexIdclientAutCookie value from browser
9
+ * Note: vtex_segment is httpOnly and cannot be accessed via JavaScript,
10
+ * so we only monitor VtexIdclientAutCookie_<account>
11
+ */
12
+ const getAuthCookieValue = (): string | null => {
13
+ if (typeof document === 'undefined') {
14
+ return null
15
+ }
16
+
17
+ const account = discoveryConfig.api.storeId
18
+ const authCookieName = `VtexIdclientAutCookie_${account}`
19
+ const cookies = document.cookie.split(';').map((cookie) => cookie.trim())
20
+ const authCookie = cookies.find((c) => c.startsWith(`${authCookieName}=`))
21
+
22
+ if (!authCookie) {
23
+ return null
24
+ }
25
+
26
+ return authCookie.split('=').slice(1).join('=')
27
+ }
28
+
29
+ /**
30
+ * Gets the stored auth cookie value from sessionStorage (client-side only)
31
+ */
32
+ const getStoredAuthCookieValue = (): string | null => {
33
+ if (typeof sessionStorage === 'undefined') {
34
+ return null
35
+ }
36
+
37
+ try {
38
+ return sessionStorage.getItem(STORAGE_KEY_AUTH_COOKIE_VALUE)
39
+ } catch {
40
+ return null
41
+ }
42
+ }
43
+
44
+ /**
45
+ * Stores the auth cookie value in sessionStorage (client-side only)
46
+ */
47
+ const storeAuthCookieValue = (value: string | null): void => {
48
+ if (typeof sessionStorage === 'undefined') {
49
+ return
50
+ }
51
+
52
+ try {
53
+ if (value === null) {
54
+ sessionStorage.removeItem(STORAGE_KEY_AUTH_COOKIE_VALUE)
55
+ } else {
56
+ sessionStorage.setItem(STORAGE_KEY_AUTH_COOKIE_VALUE, value)
57
+ }
58
+ } catch {
59
+ // Ignore storage errors
60
+ }
61
+ }
62
+
63
+ /**
64
+ * Gets the last cache busting value from sessionStorage (client-side only)
65
+ */
66
+ const getLastValue = (): string | null => {
67
+ if (typeof sessionStorage === 'undefined') {
68
+ return null
69
+ }
70
+
71
+ try {
72
+ return sessionStorage.getItem(STORAGE_KEY_CACHE_BUST_LAST_VALUE)
73
+ } catch {
74
+ return null
75
+ }
76
+ }
77
+
78
+ /**
79
+ * Stores the last cache busting value in sessionStorage (client-side only)
80
+ */
81
+ const storeLastValue = (value: string): void => {
82
+ if (typeof sessionStorage === 'undefined') {
83
+ return
84
+ }
85
+
86
+ try {
87
+ sessionStorage.setItem(STORAGE_KEY_CACHE_BUST_LAST_VALUE, value)
88
+ } catch {
89
+ // Ignore storage errors
90
+ }
91
+ }
92
+
93
+ /**
94
+ * Clears all cache busting related data from sessionStorage (client-side only)
95
+ */
96
+ const clearStorage = (): void => {
97
+ if (typeof sessionStorage === 'undefined') {
98
+ return
99
+ }
100
+
101
+ try {
102
+ sessionStorage.removeItem(STORAGE_KEY_AUTH_COOKIE_VALUE)
103
+ sessionStorage.removeItem(STORAGE_KEY_CACHE_BUST_LAST_VALUE)
104
+ } catch {
105
+ // Ignore storage errors
106
+ }
107
+ }
108
+
109
+ /**
110
+ * Gets cache busting value for client-side based on auth cookie changes
111
+ */
112
+ export const getClientCacheBustingValue = (): string | null => {
113
+ const currentAuthCookieValue = getAuthCookieValue()
114
+
115
+ // Guard clause: if auth cookie doesn't exist, clear storage and don't proceed with cache busting logic
116
+ if (currentAuthCookieValue === null) {
117
+ clearStorage()
118
+ return null
119
+ }
120
+
121
+ const storedAuthCookieValue = getStoredAuthCookieValue()
122
+
123
+ // If auth cookie changed, update stored value and return new timestamp
124
+ if (currentAuthCookieValue !== storedAuthCookieValue) {
125
+ storeAuthCookieValue(currentAuthCookieValue)
126
+ const timestamp = Date.now().toString()
127
+ storeLastValue(timestamp)
128
+ return timestamp
129
+ }
130
+
131
+ // Auth cookie hasn't changed, return last value or create one if it doesn't exist
132
+ const lastValue = getLastValue()
133
+ if (lastValue) {
134
+ return lastValue
135
+ }
136
+
137
+ // Fallback: if no last value, create one
138
+ const timestamp = Date.now().toString()
139
+ storeLastValue(timestamp)
140
+ return timestamp
141
+ }
@@ -0,0 +1,121 @@
1
+ /**
2
+ * @jest-environment jsdom
3
+ */
4
+
5
+ jest.mock('discovery.config', () => ({
6
+ __esModule: true,
7
+ default: {
8
+ api: {
9
+ storeId: 'store',
10
+ },
11
+ },
12
+ }))
13
+
14
+ import {
15
+ getClientCacheBustingValue,
16
+ STORAGE_KEY_AUTH_COOKIE_VALUE,
17
+ STORAGE_KEY_CACHE_BUST_LAST_VALUE,
18
+ } from '../../src/utils/cookieCacheBusting'
19
+
20
+ const setAuthCookie = (value: string) => {
21
+ document.cookie = `VtexIdclientAutCookie_store=${value}; path=/`
22
+ }
23
+
24
+ const clearAllCookies = () => {
25
+ const cookies = document.cookie
26
+ .split(';')
27
+ .map((c) => c.trim())
28
+ .filter(Boolean)
29
+
30
+ for (const cookie of cookies) {
31
+ const [name] = cookie.split('=')
32
+ document.cookie = `${name}=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/`
33
+ }
34
+ }
35
+
36
+ describe('cookieCacheBusting', () => {
37
+ beforeEach(() => {
38
+ clearAllCookies()
39
+ sessionStorage.clear()
40
+ jest.restoreAllMocks()
41
+ })
42
+
43
+ it('should clear storage and return null when auth cookie is missing', () => {
44
+ const removeItemSpy = jest.spyOn(Storage.prototype, 'removeItem')
45
+
46
+ const result = getClientCacheBustingValue()
47
+
48
+ expect(result).toBeNull()
49
+ expect(removeItemSpy).toHaveBeenCalledWith(STORAGE_KEY_AUTH_COOKIE_VALUE)
50
+ expect(removeItemSpy).toHaveBeenCalledWith(
51
+ STORAGE_KEY_CACHE_BUST_LAST_VALUE
52
+ )
53
+ })
54
+
55
+ it('should return a new timestamp and persist values when auth cookie changed', () => {
56
+ setAuthCookie('token-1')
57
+
58
+ const nowSpy = jest.spyOn(Date, 'now').mockReturnValue(1700000000000)
59
+ const setItemSpy = jest.spyOn(Storage.prototype, 'setItem')
60
+
61
+ const result = getClientCacheBustingValue()
62
+
63
+ expect(result).toBe('1700000000000')
64
+ expect(nowSpy).toHaveBeenCalled()
65
+ expect(setItemSpy).toHaveBeenCalledWith(
66
+ STORAGE_KEY_AUTH_COOKIE_VALUE,
67
+ 'token-1'
68
+ )
69
+ expect(setItemSpy).toHaveBeenCalledWith(
70
+ STORAGE_KEY_CACHE_BUST_LAST_VALUE,
71
+ '1700000000000'
72
+ )
73
+ })
74
+
75
+ it('should return last cached value when auth cookie is the same and last value exists', () => {
76
+ setAuthCookie('token-1')
77
+ sessionStorage.setItem(STORAGE_KEY_AUTH_COOKIE_VALUE, 'token-1')
78
+ sessionStorage.setItem(STORAGE_KEY_CACHE_BUST_LAST_VALUE, 'prev')
79
+
80
+ const nowSpy = jest.spyOn(Date, 'now')
81
+ const setItemSpy = jest.spyOn(Storage.prototype, 'setItem')
82
+
83
+ const result = getClientCacheBustingValue()
84
+
85
+ expect(result).toBe('prev')
86
+ expect(nowSpy).not.toHaveBeenCalled()
87
+ expect(setItemSpy).not.toHaveBeenCalled()
88
+ })
89
+
90
+ it('should create and persist a new last value when auth cookie is the same but last value is missing', () => {
91
+ setAuthCookie('token-1')
92
+ sessionStorage.setItem(STORAGE_KEY_AUTH_COOKIE_VALUE, 'token-1')
93
+
94
+ const nowSpy = jest.spyOn(Date, 'now').mockReturnValue(1700000000123)
95
+ const setItemSpy = jest.spyOn(Storage.prototype, 'setItem')
96
+
97
+ const result = getClientCacheBustingValue()
98
+
99
+ expect(result).toBe('1700000000123')
100
+ expect(nowSpy).toHaveBeenCalled()
101
+ expect(setItemSpy).toHaveBeenCalledWith(
102
+ STORAGE_KEY_CACHE_BUST_LAST_VALUE,
103
+ '1700000000123'
104
+ )
105
+ expect(setItemSpy).not.toHaveBeenCalledWith(
106
+ STORAGE_KEY_AUTH_COOKIE_VALUE,
107
+ 'token-1'
108
+ )
109
+ })
110
+
111
+ it('should keep "=" characters in cookie value', () => {
112
+ setAuthCookie('a=b=c')
113
+
114
+ jest.spyOn(Date, 'now').mockReturnValue(1700000000456)
115
+
116
+ const result = getClientCacheBustingValue()
117
+
118
+ expect(result).toBe('1700000000456')
119
+ expect(sessionStorage.getItem(STORAGE_KEY_AUTH_COOKIE_VALUE)).toBe('a=b=c')
120
+ })
121
+ })
@@ -1 +0,0 @@
1
- self.__BUILD_MANIFEST=function(s,c,t,a,e,u,n){return{__rewrites:{afterFiles:[],beforeFiles:[],fallback:[]},"/":[s,c,t,e,"static/css/02259c549b2179f2.css","static/chunks/pages/index-1f8bf0fb351b90f4.js"],"/404":[s,c,t,a,"static/chunks/pages/404-4b3f1e22c77f849a.js"],"/500":[s,c,t,a,"static/chunks/pages/500-bb2de0c11524b2b8.js"],"/_error":["static/chunks/pages/_error-2b0148be56a716e9.js"],"/checkout":[s,c,t,"static/chunks/pages/checkout-dc07d72b3514c83d.js"],"/login":[s,c,t,a,"static/chunks/pages/login-e97ef92769456bf6.js"],"/pvt/account":["static/chunks/pages/pvt/account-65fefcc699344bdb.js"],"/pvt/account/403":[s,c,t,"static/css/0fae3d432331aae9.css","static/chunks/pages/pvt/account/403-91597100f8956385.js"],"/pvt/account/404":[s,c,t,"static/css/0fc6b2ff69142c6a.css","static/chunks/pages/pvt/account/404-c68124bed7cb7c48.js"],"/pvt/account/orders":[s,c,t,"static/css/40a294d0a24ad01d.css","static/chunks/pages/pvt/account/orders-12a215899ae6f3eb.js"],"/pvt/account/orders/[id]":[s,c,t,"static/css/5eecefd2c6deeee4.css","static/chunks/pages/pvt/account/orders/[id]-68bbe8db444fd60a.js"],"/pvt/account/profile":[s,c,t,"static/css/47f1b4e8de15d314.css","static/chunks/pages/pvt/account/profile-b6cdbf02d4682544.js"],"/pvt/account/security":[s,c,t,"static/css/973dd40d4773e8cd.css","static/chunks/pages/pvt/account/security-e6289a40e745d3c4.js"],"/pvt/account/user-details":[s,c,t,"static/css/05c399956ff24b77.css","static/chunks/pages/pvt/account/user-details-fba1822e52e7de26.js"],"/pvt/account/[...unknown]":["static/chunks/pages/pvt/account/[...unknown]-f80f645594d2740c.js"],"/s":[s,c,t,u,n,a,"static/chunks/pages/s-41cffb20de1d2617.js"],"/[slug]/p":[s,"static/chunks/1208-9118a8f14ecf05c8.js",c,t,"static/chunks/7522-9010743f5d7e8aca.js",e,"static/css/a6a4ebbe01adbbad.css","static/chunks/pages/[slug]/p-cf620759e25940f8.js"],"/[...slug]":[s,c,t,u,n,"static/chunks/pages/[...slug]-3579b3b8b8309552.js"],sortedPages:["/","/404","/500","/_app","/_error","/checkout","/login","/pvt/account","/pvt/account/403","/pvt/account/404","/pvt/account/orders","/pvt/account/orders/[id]","/pvt/account/profile","/pvt/account/security","/pvt/account/user-details","/pvt/account/[...unknown]","/s","/[slug]/p","/[...slug]"]}}("static/chunks/6031-b0b111f48a7d504d.js","static/css/9d403a1bee48c9fc.css","static/chunks/9173-49a05f6f96710bd1.js","static/css/2980acad3f8e1028.css","static/css/837662922091162f.css","static/css/31380ebc6e671486.css","static/chunks/7191-5aa417081c97d46a.js"),self.__BUILD_MANIFEST_CB&&self.__BUILD_MANIFEST_CB();