@faststore/core 3.99.0-dev.2 → 3.99.0-dev.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/.next/BUILD_ID +1 -1
- package/.next/build-manifest.json +38 -38
- package/.next/cache/.tsbuildinfo +1 -1
- package/.next/cache/config.json +3 -3
- package/.next/cache/webpack/client-production/0.pack +0 -0
- package/.next/cache/webpack/client-production/index.pack +0 -0
- package/.next/cache/webpack/server-production/0.pack +0 -0
- package/.next/cache/webpack/server-production/index.pack +0 -0
- package/.next/prerender-manifest.js +1 -1
- package/.next/prerender-manifest.json +1 -1
- package/.next/react-loadable-manifest.json +6 -6
- package/.next/routes-manifest.json +1 -1
- package/.next/server/chunks/1741.js +2 -2
- package/.next/server/chunks/5133.js +1 -1
- package/.next/server/chunks/5212.js +1 -1
- package/.next/server/chunks/7121.js +1 -1
- package/.next/server/chunks/8971.js +5 -4
- package/.next/server/functions-config-manifest.json +1 -1
- package/.next/server/middleware-build-manifest.js +1 -1
- package/.next/server/middleware-react-loadable-manifest.js +1 -1
- package/.next/server/pages/api/graphql.js +3 -3
- package/.next/server/pages/en-US/404.html +2 -2
- package/.next/server/pages/en-US/500.html +2 -2
- package/.next/server/pages/en-US/checkout.html +2 -2
- package/.next/server/pages/en-US/login.html +2 -2
- package/.next/server/pages/en-US/s.html +2 -2
- package/.next/server/pages/en-US.html +2 -2
- package/.next/server/pages/pvt/account/403.js +1 -1
- package/.next/server/pages-manifest.json +1 -1
- package/.next/static/chunks/2221-3b8af0bc108994c0.js +2 -0
- package/.next/static/chunks/{2927.5a79877943a6bf7c.js → 2927.23bae2c79f0ac0f3.js} +1 -1
- package/.next/static/chunks/{UIToast.19a8664c01a00d3a.js → UIToast.de15325248043ce5.js} +1 -1
- package/.next/static/chunks/pages/_app-87b58cddac61876b.js +1 -0
- package/.next/static/chunks/pages/pvt/account/{403-d44b70591d2c86dc.js → 403-f3cb4fbbbb801a00.js} +1 -1
- package/.next/static/chunks/{webpack-fc37ce1526507eaf.js → webpack-da79ff7bed51ca28.js} +1 -1
- package/.next/static/{OZ8Lk7JM4bzozJAoz6cqt → ezu8OJFco4zOT-ZPSNxeP}/_buildManifest.js +1 -1
- package/.next/trace +143 -142
- package/.turbo/turbo-build.log +14 -14
- package/.turbo/turbo-test.log +10 -9
- package/@generated/gql.ts +2 -2
- package/@generated/graphql.ts +6 -1
- package/@generated/persisted-documents.json +1 -1
- package/@generated/schema.graphql +2 -0
- package/CHANGELOG.md +13 -0
- package/discovery.config.default.js +1 -0
- package/package.json +7 -7
- package/src/components/account/MyAccountDrawer/OrganizationDrawer/useReloadAfterLogoutReturn.ts +6 -1
- package/src/pages/api/graphql.ts +6 -22
- package/src/sdk/account/useRefreshToken.ts +3 -14
- package/src/sdk/cart/index.ts +12 -2
- package/src/sdk/session/index.ts +96 -58
- package/src/sdk/session/storageKeys.ts +2 -0
- package/src/sdk/useStore.ts +7 -1
- package/src/utils/validateSessionRefreshToken.ts +29 -0
- package/test/utils/validateSessionRefreshToken.test.ts +69 -0
- package/.next/static/chunks/2221-b085ff74b21a2c2c.js +0 -2
- package/.next/static/chunks/pages/_app-be3fecd37c6b273c.js +0 -1
- /package/.next/static/{OZ8Lk7JM4bzozJAoz6cqt → ezu8OJFco4zOT-ZPSNxeP}/_ssgManifest.js +0 -0
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,23 +1,23 @@
|
|
|
1
1
|
|
|
2
|
-
> @faststore/core@3.99.0-dev.
|
|
2
|
+
> @faststore/core@3.99.0-dev.3 prebuild /home/runner/work/faststore/faststore/packages/core
|
|
3
3
|
> na run partytown && na run generate
|
|
4
4
|
|
|
5
5
|
|
|
6
|
-
> @faststore/core@3.99.0-dev.
|
|
6
|
+
> @faststore/core@3.99.0-dev.3 partytown /home/runner/work/faststore/faststore/packages/core
|
|
7
7
|
> partytown copylib ./public/~partytown
|
|
8
8
|
|
|
9
9
|
Partytown lib copied to: /home/runner/work/faststore/faststore/packages/core/public/~partytown
|
|
10
10
|
|
|
11
|
-
> @faststore/core@3.99.0-dev.
|
|
11
|
+
> @faststore/core@3.99.0-dev.3 generate /home/runner/work/faststore/faststore/packages/core
|
|
12
12
|
> na run generate:schema && na run generate:codegen && na run format:generated
|
|
13
13
|
|
|
14
14
|
|
|
15
|
-
> @faststore/core@3.99.0-dev.
|
|
15
|
+
> @faststore/core@3.99.0-dev.3 generate:schema /home/runner/work/faststore/faststore/packages/core
|
|
16
16
|
> tsx src/server/generator/generateGraphQLSchemaFile.ts
|
|
17
17
|
|
|
18
18
|
Schema GraphQL file generated successfully
|
|
19
19
|
|
|
20
|
-
> @faststore/core@3.99.0-dev.
|
|
20
|
+
> @faststore/core@3.99.0-dev.3 generate:codegen /home/runner/work/faststore/faststore/packages/core
|
|
21
21
|
> graphql-codegen
|
|
22
22
|
|
|
23
23
|
[STARTED] Parse Configuration
|
|
@@ -37,11 +37,11 @@ Running lifecycle hook "afterStart" scripts...
|
|
|
37
37
|
[CLI] Loading Documents
|
|
38
38
|
[CLI] Generating output
|
|
39
39
|
|
|
40
|
-
> @faststore/core@3.99.0-dev.
|
|
40
|
+
> @faststore/core@3.99.0-dev.3 format:generated /home/runner/work/faststore/faststore/packages/core
|
|
41
41
|
> prettier --write "@generated/**/*.{ts,js,tsx,jsx,json}" --loglevel error
|
|
42
42
|
|
|
43
43
|
|
|
44
|
-
> @faststore/core@3.99.0-dev.
|
|
44
|
+
> @faststore/core@3.99.0-dev.3 build /home/runner/work/faststore/faststore/packages/core
|
|
45
45
|
> next build
|
|
46
46
|
|
|
47
47
|
⚠ No build cache found. Please configure build caching for faster rebuilds. Read more: https://nextjs.org/docs/messages/no-cache
|
|
@@ -67,15 +67,15 @@ Warning: Dynamic Content not found for the page: home. Refer to the Dynamic Cont
|
|
|
67
67
|
Collecting build traces ...
|
|
68
68
|
|
|
69
69
|
Route (pages) Size First Load JS
|
|
70
|
-
┌ ● / 7.39 kB
|
|
70
|
+
┌ ● / 7.39 kB 166 kB
|
|
71
71
|
├ └ css/02259c549b2179f2.css 3.1 kB
|
|
72
72
|
├ /_app 0 B 109 kB
|
|
73
73
|
├ ● /[...slug] 2.55 kB 175 kB
|
|
74
74
|
├ ● /[slug]/p 97.3 kB 255 kB
|
|
75
75
|
├ ├ css/e68c313e4ce2fd1b.css 6.34 kB
|
|
76
76
|
├ └ css/9b4c21dcd3bc4a17.css 16.7 kB
|
|
77
|
-
├ ○ /404 1.58 kB
|
|
78
|
-
├ ● /500 1.57 kB
|
|
77
|
+
├ ○ /404 1.58 kB 160 kB
|
|
78
|
+
├ ● /500 1.57 kB 160 kB
|
|
79
79
|
├ λ /api/fs/logout 0 B 109 kB
|
|
80
80
|
├ λ /api/graphql 0 B 109 kB
|
|
81
81
|
├ λ /api/health/live 0 B 109 kB
|
|
@@ -85,13 +85,13 @@ Route (pages) Size First Load JS
|
|
|
85
85
|
├ ● /login 1.7 kB 160 kB
|
|
86
86
|
├ λ /pvt/account 245 B 109 kB
|
|
87
87
|
├ ● /pvt/account/[...unknown] 286 B 109 kB
|
|
88
|
-
├ λ /pvt/account/403 2.
|
|
88
|
+
├ λ /pvt/account/403 2.98 kB 161 kB
|
|
89
89
|
├ └ css/094ad5cc00c74e64.css 4.82 kB
|
|
90
90
|
├ λ /pvt/account/404 2.18 kB 160 kB
|
|
91
91
|
├ └ css/72f154a00f1cf621.css 4.88 kB
|
|
92
92
|
├ λ /pvt/account/orders 9.89 kB 168 kB
|
|
93
93
|
├ └ css/4b2af1aaeb961465.css 14.4 kB
|
|
94
|
-
├ λ /pvt/account/orders/[id] 13.6 kB
|
|
94
|
+
├ λ /pvt/account/orders/[id] 13.6 kB 172 kB
|
|
95
95
|
├ └ css/482e6fa149885bc2.css 13.9 kB
|
|
96
96
|
├ λ /pvt/account/profile 1.99 kB 160 kB
|
|
97
97
|
├ └ css/5cd37b7508dd5190.css 4.56 kB
|
|
@@ -103,8 +103,8 @@ Route (pages) Size First Load JS
|
|
|
103
103
|
+ First Load JS shared by all 118 kB
|
|
104
104
|
├ chunks/framework-d514426edf885c68.js 45.4 kB
|
|
105
105
|
├ chunks/main-edeb2c6a5c48bb3f.js 33.2 kB
|
|
106
|
-
├ chunks/pages/_app-
|
|
107
|
-
├ chunks/webpack-
|
|
106
|
+
├ chunks/pages/_app-87b58cddac61876b.js 26.6 kB
|
|
107
|
+
├ chunks/webpack-da79ff7bed51ca28.js 3.88 kB
|
|
108
108
|
└ css/8a513f4b536b0ab0.css 9.18 kB
|
|
109
109
|
|
|
110
110
|
λ (Server) server-side renders at runtime (uses getInitialProps or getServerSideProps)
|
package/.turbo/turbo-test.log
CHANGED
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
|
|
2
|
-
> @faststore/core@3.99.0-dev.
|
|
2
|
+
> @faststore/core@3.99.0-dev.3 test /home/runner/work/faststore/faststore/packages/core
|
|
3
3
|
> jest
|
|
4
4
|
|
|
5
|
-
PASS test/utils/multipleTemplates.test.ts (
|
|
6
|
-
PASS test/utils/clearCookies.test.ts (
|
|
7
|
-
PASS test/utils/cookieCacheBusting.test.ts (15.
|
|
5
|
+
PASS test/utils/multipleTemplates.test.ts (13.623 s)
|
|
6
|
+
PASS test/utils/clearCookies.test.ts (13.117 s)
|
|
7
|
+
PASS test/utils/cookieCacheBusting.test.ts (15.41 s)
|
|
8
8
|
PASS test/server/cms/global.test.ts
|
|
9
|
-
PASS test/
|
|
10
|
-
PASS test/server/index.test.ts (
|
|
9
|
+
PASS test/utils/validateSessionRefreshToken.test.ts
|
|
10
|
+
PASS test/server/cms/index.test.ts (5.883 s)
|
|
11
|
+
PASS test/server/index.test.ts (12.391 s)
|
|
11
12
|
|
|
12
|
-
Test Suites:
|
|
13
|
-
Tests:
|
|
13
|
+
Test Suites: 7 passed, 7 total
|
|
14
|
+
Tests: 58 passed, 58 total
|
|
14
15
|
Snapshots: 0 total
|
|
15
|
-
Time: 27.
|
|
16
|
+
Time: 27.436 s
|
|
16
17
|
Ran all test suites.
|
package/@generated/gql.ts
CHANGED
|
@@ -62,7 +62,7 @@ const documents = {
|
|
|
62
62
|
types.ProcessOrderAuthorizationMutationDocument,
|
|
63
63
|
'\n query ValidateUser {\n validateUser {\n isValid\n }\n }\n':
|
|
64
64
|
types.ValidateUserDocument,
|
|
65
|
-
'\n mutation ValidateCartMutation($cart: IStoreCart!, $session: IStoreSession!) {\n validateCart(cart: $cart, session: $session) {\n order {\n orderNumber\n acceptedOffer {\n ...CartItem\n }\n shouldSplitItem\n }\n messages {\n ...CartMessage\n }\n }\n }\n\n fragment CartMessage on StoreCartMessage {\n text\n status\n }\n\n fragment CartItem on StoreOffer {\n seller {\n identifier\n }\n quantity\n price\n priceWithTaxes\n listPrice\n listPriceWithTaxes\n itemOffered {\n ...CartProductItem\n }\n }\n\n fragment CartProductItem on StoreProduct {\n sku\n name\n unitMultiplier\n image {\n url\n alternateName\n }\n brand {\n name\n }\n isVariantOf {\n productGroupID\n name\n skuVariants {\n activeVariations\n slugsMap\n availableVariations\n }\n }\n gtin\n additionalProperty {\n propertyID\n name\n value\n valueReference\n }\n }\n':
|
|
65
|
+
'\n mutation ValidateCartMutation($cart: IStoreCart!, $session: IStoreSession!) {\n validateCart(cart: $cart, session: $session) {\n order {\n orderNumber\n acceptedOffer {\n ...CartItem\n }\n shouldSplitItem\n }\n messages {\n ...CartMessage\n }\n }\n }\n\n fragment CartMessage on StoreCartMessage {\n text\n status\n }\n\n fragment CartItem on StoreOffer {\n seller {\n identifier\n }\n quantity\n price\n priceWithTaxes\n listPrice\n listPriceWithTaxes\n isGift\n itemOffered {\n ...CartProductItem\n }\n }\n\n fragment CartProductItem on StoreProduct {\n sku\n name\n unitMultiplier\n image {\n url\n alternateName\n }\n brand {\n name\n }\n isVariantOf {\n productGroupID\n name\n skuVariants {\n activeVariations\n slugsMap\n availableVariations\n }\n }\n gtin\n additionalProperty {\n propertyID\n name\n value\n valueReference\n }\n }\n':
|
|
66
66
|
types.ValidateCartMutationDocument,
|
|
67
67
|
'\n query ClientPickupPointsQuery(\n $geoCoordinates: IStoreGeoCoordinates\n ) {\n pickupPoints(geoCoordinates: $geoCoordinates) {\n pickupPointDistances {\n pickupId\n distance\n pickupName\n isActive\n address {\n city\n state\n number\n postalCode\n street\n }\n }\n }\n }\n':
|
|
68
68
|
types.ClientPickupPointsQueryDocument,
|
|
@@ -250,7 +250,7 @@ export function gql(
|
|
|
250
250
|
* The gql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
|
251
251
|
*/
|
|
252
252
|
export function gql(
|
|
253
|
-
source: '\n mutation ValidateCartMutation($cart: IStoreCart!, $session: IStoreSession!) {\n validateCart(cart: $cart, session: $session) {\n order {\n orderNumber\n acceptedOffer {\n ...CartItem\n }\n shouldSplitItem\n }\n messages {\n ...CartMessage\n }\n }\n }\n\n fragment CartMessage on StoreCartMessage {\n text\n status\n }\n\n fragment CartItem on StoreOffer {\n seller {\n identifier\n }\n quantity\n price\n priceWithTaxes\n listPrice\n listPriceWithTaxes\n itemOffered {\n ...CartProductItem\n }\n }\n\n fragment CartProductItem on StoreProduct {\n sku\n name\n unitMultiplier\n image {\n url\n alternateName\n }\n brand {\n name\n }\n isVariantOf {\n productGroupID\n name\n skuVariants {\n activeVariations\n slugsMap\n availableVariations\n }\n }\n gtin\n additionalProperty {\n propertyID\n name\n value\n valueReference\n }\n }\n'
|
|
253
|
+
source: '\n mutation ValidateCartMutation($cart: IStoreCart!, $session: IStoreSession!) {\n validateCart(cart: $cart, session: $session) {\n order {\n orderNumber\n acceptedOffer {\n ...CartItem\n }\n shouldSplitItem\n }\n messages {\n ...CartMessage\n }\n }\n }\n\n fragment CartMessage on StoreCartMessage {\n text\n status\n }\n\n fragment CartItem on StoreOffer {\n seller {\n identifier\n }\n quantity\n price\n priceWithTaxes\n listPrice\n listPriceWithTaxes\n isGift\n itemOffered {\n ...CartProductItem\n }\n }\n\n fragment CartProductItem on StoreProduct {\n sku\n name\n unitMultiplier\n image {\n url\n alternateName\n }\n brand {\n name\n }\n isVariantOf {\n productGroupID\n name\n skuVariants {\n activeVariations\n slugsMap\n availableVariations\n }\n }\n gtin\n additionalProperty {\n propertyID\n name\n value\n valueReference\n }\n }\n'
|
|
254
254
|
): typeof import('./graphql').ValidateCartMutationDocument
|
|
255
255
|
/**
|
|
256
256
|
* The gql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
package/@generated/graphql.ts
CHANGED
|
@@ -1348,6 +1348,8 @@ export type StoreMarketingData = {
|
|
|
1348
1348
|
export type StoreOffer = {
|
|
1349
1349
|
/** Offer item availability. */
|
|
1350
1350
|
availability: Scalars['String']['output']
|
|
1351
|
+
/** Whether this offer is a gift (e.g. free promotional item). */
|
|
1352
|
+
isGift: Maybe<Scalars['Boolean']['output']>
|
|
1351
1353
|
/** Offer item condition. */
|
|
1352
1354
|
itemCondition: Scalars['String']['output']
|
|
1353
1355
|
/** Information on the item being offered. */
|
|
@@ -3143,6 +3145,7 @@ export type ValidateCartMutationMutation = {
|
|
|
3143
3145
|
priceWithTaxes: number
|
|
3144
3146
|
listPrice: number
|
|
3145
3147
|
listPriceWithTaxes: number
|
|
3148
|
+
isGift: boolean | null
|
|
3146
3149
|
seller: { identifier: string }
|
|
3147
3150
|
itemOffered: {
|
|
3148
3151
|
sku: string
|
|
@@ -3181,6 +3184,7 @@ export type CartItemFragment = {
|
|
|
3181
3184
|
priceWithTaxes: number
|
|
3182
3185
|
listPrice: number
|
|
3183
3186
|
listPriceWithTaxes: number
|
|
3187
|
+
isGift: boolean | null
|
|
3184
3188
|
seller: { identifier: string }
|
|
3185
3189
|
itemOffered: {
|
|
3186
3190
|
sku: string
|
|
@@ -4390,6 +4394,7 @@ export const CartItemFragmentDoc = new TypedDocumentString(
|
|
|
4390
4394
|
priceWithTaxes
|
|
4391
4395
|
listPrice
|
|
4392
4396
|
listPriceWithTaxes
|
|
4397
|
+
isGift
|
|
4393
4398
|
itemOffered {
|
|
4394
4399
|
...CartProductItem
|
|
4395
4400
|
}
|
|
@@ -4536,7 +4541,7 @@ export const ValidateUserDocument = {
|
|
|
4536
4541
|
export const ValidateCartMutationDocument = {
|
|
4537
4542
|
__meta__: {
|
|
4538
4543
|
operationName: 'ValidateCartMutation',
|
|
4539
|
-
operationHash: '
|
|
4544
|
+
operationHash: '32c15f8888ca34f223def7972b7f19090808435a',
|
|
4540
4545
|
},
|
|
4541
4546
|
} as unknown as TypedDocumentString<
|
|
4542
4547
|
ValidateCartMutationMutation,
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"e2b06da6840614d3c72768e56579b9d3b8e80802": "mutation CancelOrderMutation($data: IUserOrderCancel!) { cancelOrder(data: $data) { data } }",
|
|
11
11
|
"8c25d37c8d6e7c20ab21bb8a4f4e6a2fe320ea8d": "mutation ProcessOrderAuthorizationMutation($data: IProcessOrderAuthorization!) { processOrderAuthorization(data: $data) { isPendingForOtherAuthorizer ruleForAuthorization { dimensionId orderAuthorizationId rule { authorizationData { authorizers { authorizationDate email id type } requireAllApprovals } authorizedEmails doId id isUserAuthorized isUserNextAuthorizer name notification priority scoreInterval { accept deny } status timeout trigger { condition { conditionType description expression greatherThan lessThan } effect { description effectType funcPath } } } } } }",
|
|
12
12
|
"32f99c73c3de958b64d6bece1afe800469f54548": "query ValidateUser { validateUser { isValid } }",
|
|
13
|
-
"
|
|
13
|
+
"32c15f8888ca34f223def7972b7f19090808435a": "fragment CartItem on StoreOffer { isGift itemOffered { ...CartProductItem } listPrice listPriceWithTaxes price priceWithTaxes quantity seller { identifier } } fragment CartMessage on StoreCartMessage { status text } fragment CartProductItem on StoreProduct { additionalProperty { name propertyID value valueReference } brand { name } gtin image { alternateName url } isVariantOf { name productGroupID skuVariants { activeVariations availableVariations slugsMap } } name sku unitMultiplier } mutation ValidateCartMutation($cart: IStoreCart!, $session: IStoreSession!) { validateCart(cart: $cart, session: $session) { messages { ...CartMessage } order { acceptedOffer { ...CartItem } orderNumber shouldSplitItem } } }",
|
|
14
14
|
"3fa04e88c811fcb5ece7206fd5aa745bdbc143a8": "query ClientPickupPointsQuery($geoCoordinates: IStoreGeoCoordinates) { pickupPoints(geoCoordinates: $geoCoordinates) { pickupPointDistances { address { city number postalCode state street } distance isActive pickupId pickupName } } }",
|
|
15
15
|
"feb7005103a859e2bc8cf2360d568806fd88deba": "mutation SubscribeToNewsletter($data: IPersonNewsletter!) { subscribeToNewsletter(data: $data) { id } }",
|
|
16
16
|
"dc912e7272e3d9f5ced206837df87f544d39d0a5": "query ClientProductCountQuery($term: String) { productCount(term: $term) { total } }",
|
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,19 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
+
# 3.99.0-dev.4 (2026-04-28)
|
|
7
|
+
|
|
8
|
+
### Bug Fixes
|
|
9
|
+
|
|
10
|
+
- Refactor token refresh logic in GraphQL API handler - SFS-3104 ([#3263](https://github.com/vtex/faststore/issues/3263)) ([695ab6a](https://github.com/vtex/faststore/commit/695ab6a9413eb53ef457cfc890c74e41c29c1ab8))
|
|
11
|
+
|
|
12
|
+
# 3.99.0-dev.3 (2026-04-23)
|
|
13
|
+
|
|
14
|
+
### Features
|
|
15
|
+
|
|
16
|
+
- add isGift field to StoreOffer type and resolvers - SFS-3040 ([#3220](https://github.com/vtex/faststore/issues/3220)) ([e10fc92](https://github.com/vtex/faststore/commit/e10fc92633a5296a5b03286028d31d9c280561c6))
|
|
17
|
+
- Enhance session management with logout - SFS-3105 ([#3268](https://github.com/vtex/faststore/issues/3268)) ([9468e32](https://github.com/vtex/faststore/commit/9468e32a3531ca3c91c7f82a34ac1c750c6a26ef))
|
|
18
|
+
|
|
6
19
|
# [3.99.0-dev.2](https://github.com/vtex/faststore/compare/v3.99.0-dev.1...v3.99.0-dev.2) (2026-04-21)
|
|
7
20
|
|
|
8
21
|
**Note:** Version bump only for package @faststore/core
|
|
@@ -148,6 +148,7 @@ module.exports = {
|
|
|
148
148
|
enableRedirects: false,
|
|
149
149
|
enableSearchSSR: false,
|
|
150
150
|
enableFaststoreMyAccount: false,
|
|
151
|
+
useIsGiftFromOrderForm: false,
|
|
151
152
|
graphqlCacheControl: {
|
|
152
153
|
maxAge: 0, // 0 disables cache, 5 * 60 enable cache control maxAge 5 minutes
|
|
153
154
|
staleWhileRevalidate: 60 * 60, // 1 hour
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@faststore/core",
|
|
3
|
-
"version": "3.99.0-dev.
|
|
3
|
+
"version": "3.99.0-dev.4",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -51,11 +51,11 @@
|
|
|
51
51
|
"@envelop/graphql-jit": "^8.0.3",
|
|
52
52
|
"@envelop/parser-cache": "^6.0.2",
|
|
53
53
|
"@envelop/validation-cache": "^6.0.2",
|
|
54
|
-
"@faststore/api": "3.
|
|
55
|
-
"@faststore/graphql-utils": "^3.
|
|
56
|
-
"@faststore/lighthouse": "3.
|
|
57
|
-
"@faststore/sdk": "3.99.0-dev.
|
|
58
|
-
"@faststore/ui": "3.99.0-dev.
|
|
54
|
+
"@faststore/api": "3.99.0-dev.4",
|
|
55
|
+
"@faststore/graphql-utils": "^3.99.0-dev.4",
|
|
56
|
+
"@faststore/lighthouse": "3.99.0-dev.4",
|
|
57
|
+
"@faststore/sdk": "3.99.0-dev.4",
|
|
58
|
+
"@faststore/ui": "3.99.0-dev.4",
|
|
59
59
|
"@graphql-codegen/cli": "5.0.2",
|
|
60
60
|
"@graphql-codegen/client-preset": "4.2.6",
|
|
61
61
|
"@graphql-codegen/typescript": "4.0.7",
|
|
@@ -124,5 +124,5 @@
|
|
|
124
124
|
"ts-jest": "29.1.1",
|
|
125
125
|
"typescript": "5.3.2"
|
|
126
126
|
},
|
|
127
|
-
"gitHead": "
|
|
127
|
+
"gitHead": "3069a16c7894cfe1f1da44e4dd812080e074d616"
|
|
128
128
|
}
|
package/src/components/account/MyAccountDrawer/OrganizationDrawer/useReloadAfterLogoutReturn.ts
CHANGED
|
@@ -12,7 +12,10 @@ import { useEffect } from 'react'
|
|
|
12
12
|
* We handle both: check the flag on mount (fresh load) and on pageshow (bfcache).
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
import {
|
|
16
|
+
RELOAD_AFTER_LOGOUT_KEY,
|
|
17
|
+
SESSION_READY_KEY,
|
|
18
|
+
} from '../../../../sdk/session/storageKeys'
|
|
16
19
|
|
|
17
20
|
/**
|
|
18
21
|
* Call before redirecting to logout. When the user returns to the store, the app
|
|
@@ -43,6 +46,8 @@ const checkAndReloadIfReturnedFromLogout = (): void => {
|
|
|
43
46
|
try {
|
|
44
47
|
if (sessionStorage.getItem(RELOAD_AFTER_LOGOUT_KEY)) {
|
|
45
48
|
sessionStorage.removeItem(RELOAD_AFTER_LOGOUT_KEY)
|
|
49
|
+
// Pre-set session ready so the reloaded page starts without a skeleton.
|
|
50
|
+
sessionStorage.setItem(SESSION_READY_KEY, 'true')
|
|
46
51
|
setTimeout(forceRefreshWithoutCache, RELOAD_DELAY_MS)
|
|
47
52
|
}
|
|
48
53
|
} catch {
|
package/src/pages/api/graphql.ts
CHANGED
|
@@ -8,7 +8,8 @@ import { parse } from 'cookie'
|
|
|
8
8
|
import type { NextApiHandler, NextApiRequest } from 'next'
|
|
9
9
|
|
|
10
10
|
import discoveryConfig from 'discovery.config'
|
|
11
|
-
import { getJWTAutCookie
|
|
11
|
+
import { getJWTAutCookie } from 'src/utils/getCookie'
|
|
12
|
+
import { shouldForceRefreshTokenForValidateSession } from 'src/utils/validateSessionRefreshToken'
|
|
12
13
|
import { execute } from '../../server'
|
|
13
14
|
|
|
14
15
|
const DEFAULT_MAX_AGE = 5 * 60 // 5 minutes
|
|
@@ -169,27 +170,10 @@ const handler: NextApiHandler = async (request, response) => {
|
|
|
169
170
|
account: discoveryConfig.api.storeId,
|
|
170
171
|
})
|
|
171
172
|
|
|
172
|
-
const
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
const refreshAfterExpired =
|
|
177
|
-
refreshAfterExist && isExpired(Number(variables.session.refreshAfter))
|
|
178
|
-
|
|
179
|
-
const tokenExistAndIsFirstRefreshTokenRequest =
|
|
180
|
-
!!jwt && !refreshAfterExist
|
|
181
|
-
|
|
182
|
-
// when token expired, browser clears the cookie, but we still have the refreshAfter in session and the refresh token cookie
|
|
183
|
-
const tokenNotExistAndRefreshAfterExistAndIsExpired =
|
|
184
|
-
!jwt && !!refreshAfterExist && refreshAfterExpired
|
|
185
|
-
|
|
186
|
-
const tokenExpiredAndRefreshAfterIsNullOrExpired =
|
|
187
|
-
tokenExpired && (!refreshAfterExist || refreshAfterExpired)
|
|
188
|
-
|
|
189
|
-
const shouldRefreshToken =
|
|
190
|
-
tokenExistAndIsFirstRefreshTokenRequest ||
|
|
191
|
-
tokenNotExistAndRefreshAfterExistAndIsExpired ||
|
|
192
|
-
tokenExpiredAndRefreshAfterIsNullOrExpired
|
|
173
|
+
const shouldRefreshToken = shouldForceRefreshTokenForValidateSession({
|
|
174
|
+
jwt,
|
|
175
|
+
sessionRefreshAfter: variables?.session?.refreshAfter,
|
|
176
|
+
})
|
|
193
177
|
|
|
194
178
|
if (shouldRefreshToken) {
|
|
195
179
|
throw new UnauthorizedError(
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useEffect, useState } from 'react'
|
|
2
|
-
import { sessionStore } from '../session'
|
|
2
|
+
import { logoutAndClearSession, sessionStore } from '../session'
|
|
3
3
|
import { isRefreshTokenSuccessful, refreshTokenRequest } from './refreshToken'
|
|
4
4
|
|
|
5
5
|
export const useRefreshToken = (
|
|
@@ -37,23 +37,12 @@ export const useRefreshToken = (
|
|
|
37
37
|
url.searchParams.set('_refresh', Date.now().toString())
|
|
38
38
|
window.location.href = url.toString()
|
|
39
39
|
} else {
|
|
40
|
-
|
|
41
|
-
sessionStore.set({
|
|
42
|
-
...currentSession,
|
|
43
|
-
refreshAfter: String(Math.floor(Date.now() / 1000) + 1 * 60 * 60), // now + 1 hour
|
|
44
|
-
})
|
|
45
|
-
|
|
40
|
+
await logoutAndClearSession(currentSession)
|
|
46
41
|
setShouldShow403(true)
|
|
47
42
|
}
|
|
48
43
|
} catch (error) {
|
|
49
44
|
console.error('Error during refresh token process:', error)
|
|
50
|
-
|
|
51
|
-
// Set refreshAfter to postpone future requests and redirect to login
|
|
52
|
-
sessionStore.set({
|
|
53
|
-
...currentSession,
|
|
54
|
-
refreshAfter: String(Math.floor(Date.now() / 1000) + 1 * 60 * 60), // now + 1 hour
|
|
55
|
-
})
|
|
56
|
-
|
|
45
|
+
await logoutAndClearSession(currentSession)
|
|
57
46
|
setShouldShow403(true)
|
|
58
47
|
}
|
|
59
48
|
}
|
package/src/sdk/cart/index.ts
CHANGED
|
@@ -16,7 +16,11 @@ import { request } from '../graphql/request'
|
|
|
16
16
|
import { sessionStore } from '../session'
|
|
17
17
|
import { createValidationStore, useStore } from '../useStore'
|
|
18
18
|
|
|
19
|
-
export interface CartItem
|
|
19
|
+
export interface CartItem
|
|
20
|
+
extends SDKCartItem,
|
|
21
|
+
Omit<CartItemFragment, 'isGift'> {
|
|
22
|
+
isGift?: boolean | null
|
|
23
|
+
}
|
|
20
24
|
|
|
21
25
|
export interface Cart extends SDKCart<CartItem> {
|
|
22
26
|
messages?: CartMessageFragment[]
|
|
@@ -53,6 +57,7 @@ export const ValidateCartMutation = gql(`
|
|
|
53
57
|
priceWithTaxes
|
|
54
58
|
listPrice
|
|
55
59
|
listPriceWithTaxes
|
|
60
|
+
isGift
|
|
56
61
|
itemOffered {
|
|
57
62
|
...CartProductItem
|
|
58
63
|
}
|
|
@@ -88,7 +93,12 @@ export const ValidateCartMutation = gql(`
|
|
|
88
93
|
}
|
|
89
94
|
`)
|
|
90
95
|
|
|
91
|
-
const isGift = (item: CartItem) =>
|
|
96
|
+
const isGift = (item: CartItem) => {
|
|
97
|
+
if (storeConfig.experimental?.useIsGiftFromOrderForm) {
|
|
98
|
+
return item?.isGift ?? false
|
|
99
|
+
}
|
|
100
|
+
return item.price === 0
|
|
101
|
+
}
|
|
92
102
|
|
|
93
103
|
const getItemId = (item: Pick<CartItem, 'itemOffered' | 'seller' | 'price'>) =>
|
|
94
104
|
[
|
package/src/sdk/session/index.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Session } from '@faststore/sdk'
|
|
2
2
|
import { createSessionStore } from '@faststore/sdk'
|
|
3
|
-
import { useEffect, useMemo,
|
|
3
|
+
import { useEffect, useMemo, useState } from 'react'
|
|
4
4
|
|
|
5
5
|
import { gql } from '@generated'
|
|
6
6
|
import type {
|
|
@@ -17,8 +17,18 @@ import { cartStore } from '../cart'
|
|
|
17
17
|
import { request } from '../graphql/request'
|
|
18
18
|
import { createValidationStore, useStore } from '../useStore'
|
|
19
19
|
import { getPostalCode } from '../userLocation/index'
|
|
20
|
+
import { RELOAD_AFTER_LOGOUT_KEY, SESSION_READY_KEY } from './storageKeys'
|
|
20
21
|
|
|
21
|
-
const
|
|
22
|
+
const isReloadAfterLogoutPending = (): boolean => {
|
|
23
|
+
try {
|
|
24
|
+
return (
|
|
25
|
+
typeof sessionStorage !== 'undefined' &&
|
|
26
|
+
!!sessionStorage.getItem(RELOAD_AFTER_LOGOUT_KEY)
|
|
27
|
+
)
|
|
28
|
+
} catch {
|
|
29
|
+
return false
|
|
30
|
+
}
|
|
31
|
+
}
|
|
22
32
|
|
|
23
33
|
export const mutation = gql(`
|
|
24
34
|
mutation ValidateSession($session: IStoreSession!, $search: String!) {
|
|
@@ -77,7 +87,50 @@ export const mutation = gql(`
|
|
|
77
87
|
}
|
|
78
88
|
`)
|
|
79
89
|
|
|
90
|
+
async function handleRefreshToken(session: Session): Promise<Session | null> {
|
|
91
|
+
const result = await refreshTokenRequest()
|
|
92
|
+
|
|
93
|
+
if (isRefreshTokenSuccessful(result)) {
|
|
94
|
+
return {
|
|
95
|
+
...session,
|
|
96
|
+
refreshAfter: String(
|
|
97
|
+
Math.floor(new Date(result?.refreshAfter).getTime() / 1000)
|
|
98
|
+
),
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
await logoutAndClearSession(session)
|
|
103
|
+
return null
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function isRefreshAfterExpired(session: Session): boolean {
|
|
107
|
+
return (
|
|
108
|
+
!!session.refreshAfter &&
|
|
109
|
+
Math.floor(Date.now() / 1000) > Number(session.refreshAfter)
|
|
110
|
+
)
|
|
111
|
+
}
|
|
112
|
+
|
|
80
113
|
export const validateSession = async (session: Session) => {
|
|
114
|
+
// Skip validation if the page is about to reload after logout — avoids an
|
|
115
|
+
// aborted-fetch TypeError when the forced reload navigates the page away.
|
|
116
|
+
if (isReloadAfterLogoutPending()) {
|
|
117
|
+
return null
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// If the refreshToken is enabled and the refreshAfter is expired, refresh the token.
|
|
121
|
+
// On success, continue to the validation flow with the refreshed session.
|
|
122
|
+
// On failure (logoutAndClearSession already triggered), bail out.
|
|
123
|
+
if (
|
|
124
|
+
storeConfig.experimental?.refreshToken &&
|
|
125
|
+
isRefreshAfterExpired(session)
|
|
126
|
+
) {
|
|
127
|
+
const refreshed = await handleRefreshToken(session)
|
|
128
|
+
if (!refreshed) {
|
|
129
|
+
return null
|
|
130
|
+
}
|
|
131
|
+
session = refreshed
|
|
132
|
+
}
|
|
133
|
+
|
|
81
134
|
// If deliveryPromise is enabled and there is no postalCode in the session
|
|
82
135
|
if (
|
|
83
136
|
storeConfig.deliveryPromise?.enabled &&
|
|
@@ -123,30 +176,16 @@ export const validateSession = async (session: Session) => {
|
|
|
123
176
|
error?.status === 401 && storeConfig.experimental?.refreshToken
|
|
124
177
|
|
|
125
178
|
if (shouldRefreshToken) {
|
|
126
|
-
const
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
const refreshAfter = String(
|
|
130
|
-
Math.floor(new Date(result?.refreshAfter).getTime() / 1000)
|
|
131
|
-
)
|
|
132
|
-
|
|
133
|
-
sessionStore.set({
|
|
134
|
-
...session,
|
|
135
|
-
refreshAfter,
|
|
136
|
-
})
|
|
137
|
-
} else {
|
|
138
|
-
// If the refresh token fails 3x, set the refreshAfter to now + 1 hour
|
|
139
|
-
// so that we can postpone refreshToken request and continue the ValidateSession request
|
|
140
|
-
sessionStore.set({
|
|
141
|
-
...session,
|
|
142
|
-
refreshAfter: String(Math.floor(Date.now() / 1000) + 1 * 60 * 60), // now + 1 hour
|
|
143
|
-
})
|
|
179
|
+
const refreshed = await handleRefreshToken(session)
|
|
180
|
+
if (refreshed) {
|
|
181
|
+
sessionStore.set(refreshed)
|
|
144
182
|
}
|
|
145
183
|
}
|
|
146
184
|
}
|
|
147
185
|
}
|
|
148
186
|
|
|
149
|
-
const [validationStore, onValidate] =
|
|
187
|
+
const [validationStore, onValidate, hasValidatedStore] =
|
|
188
|
+
createValidationStore(validateSession)
|
|
150
189
|
|
|
151
190
|
const defaultStore = createSessionStore(storeConfig.session, onValidate)
|
|
152
191
|
|
|
@@ -162,6 +201,21 @@ export const sessionStore = {
|
|
|
162
201
|
},
|
|
163
202
|
}
|
|
164
203
|
|
|
204
|
+
export async function logoutAndClearSession(session: Session) {
|
|
205
|
+
try {
|
|
206
|
+
await fetch('/api/fs/logout', { method: 'POST' })
|
|
207
|
+
} catch (logoutError) {
|
|
208
|
+
console.error('Failed to call logout endpoint:', logoutError)
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
sessionStore.set({
|
|
212
|
+
...session,
|
|
213
|
+
person: null,
|
|
214
|
+
b2b: null,
|
|
215
|
+
refreshAfter: null,
|
|
216
|
+
})
|
|
217
|
+
}
|
|
218
|
+
|
|
165
219
|
interface SessionOptions {
|
|
166
220
|
filter?: boolean
|
|
167
221
|
}
|
|
@@ -184,7 +238,8 @@ export const useSession = ({ filter }: SessionOptions = { filter: true }) => {
|
|
|
184
238
|
const currentSessionStore = sessionStore.read() ?? sessionStore.readInitial()
|
|
185
239
|
const resultSessionStore = useStore(sessionStore)
|
|
186
240
|
const isValidating = useStore(validationStore)
|
|
187
|
-
const
|
|
241
|
+
const hasValidated = useStore(hasValidatedStore)
|
|
242
|
+
const { isSessionReady } = useSessionReady({ isValidating, hasValidated })
|
|
188
243
|
|
|
189
244
|
let { channel, ...session } = resultSessionStore ?? currentSessionStore
|
|
190
245
|
|
|
@@ -211,12 +266,21 @@ export const useSession = ({ filter }: SessionOptions = { filter: true }) => {
|
|
|
211
266
|
* The session ready state is persisted in sessionStorage to maintain consistency across page navigations
|
|
212
267
|
* and provide faster loading times on subsequent page visits.
|
|
213
268
|
*
|
|
269
|
+
* Uses `hasValidated` (a reactive store value) instead of a ref-based transition tracker to avoid
|
|
270
|
+
* a React 18 automatic batching race condition where `isValidating` goes true→false so fast that
|
|
271
|
+
* the effect never observes the `true` state, leaving `isSessionReady` stuck at false.
|
|
272
|
+
*
|
|
214
273
|
* @param isValidating - Whether the session is currently being validated
|
|
274
|
+
* @param hasValidated - Whether at least one validation cycle has ever completed
|
|
215
275
|
* @returns An object containing the session readiness status
|
|
216
276
|
*/
|
|
217
277
|
export const useSessionReady = ({
|
|
218
278
|
isValidating,
|
|
219
|
-
|
|
279
|
+
hasValidated,
|
|
280
|
+
}: {
|
|
281
|
+
isValidating: boolean
|
|
282
|
+
hasValidated: boolean
|
|
283
|
+
}) => {
|
|
220
284
|
// Initialize with persisted state from sessionStorage
|
|
221
285
|
const [isSessionReady, setIsSessionReady] = useState(() => {
|
|
222
286
|
if (typeof window === 'undefined') return false
|
|
@@ -227,44 +291,18 @@ export const useSessionReady = ({
|
|
|
227
291
|
}
|
|
228
292
|
})
|
|
229
293
|
|
|
230
|
-
// Wait for session to be stable (not validating and has consistent data)
|
|
231
|
-
const hasValidatedRef = useRef(false)
|
|
232
|
-
|
|
233
294
|
useEffect(() => {
|
|
234
|
-
// Only
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
return
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
if (!hasValidatedRef.current) {
|
|
241
|
-
return
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
if (!isValidating) {
|
|
245
|
-
setIsSessionReady(true)
|
|
246
|
-
// Persist the ready state in sessionStorage
|
|
247
|
-
try {
|
|
248
|
-
sessionStorage.setItem(SESSION_READY_KEY, 'true')
|
|
249
|
-
} catch {
|
|
250
|
-
// Ignore storage errors
|
|
251
|
-
}
|
|
252
|
-
return
|
|
253
|
-
}
|
|
295
|
+
// Only mark ready once at least one full validation cycle has completed
|
|
296
|
+
// and validation is not currently in flight.
|
|
297
|
+
if (isValidating || !hasValidated) return
|
|
254
298
|
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
if (!persistedReady) {
|
|
261
|
-
setIsSessionReady(false)
|
|
262
|
-
}
|
|
263
|
-
} catch {
|
|
264
|
-
setIsSessionReady(false)
|
|
265
|
-
}
|
|
299
|
+
setIsSessionReady(true)
|
|
300
|
+
try {
|
|
301
|
+
sessionStorage.setItem(SESSION_READY_KEY, 'true')
|
|
302
|
+
} catch {
|
|
303
|
+
// Ignore storage errors
|
|
266
304
|
}
|
|
267
|
-
}, [isValidating])
|
|
305
|
+
}, [isValidating, hasValidated])
|
|
268
306
|
|
|
269
307
|
return { isSessionReady }
|
|
270
308
|
}
|
package/src/sdk/useStore.ts
CHANGED
|
@@ -9,6 +9,7 @@ type CB<T> = (val: T) => Promise<T | null>
|
|
|
9
9
|
|
|
10
10
|
export const createValidationStore = <T>(cb: CB<T>) => {
|
|
11
11
|
const store = createBaseStore(false)
|
|
12
|
+
const hasValidatedStore = createBaseStore(false)
|
|
12
13
|
|
|
13
14
|
const onValidate = async (val: T) => {
|
|
14
15
|
try {
|
|
@@ -17,8 +18,13 @@ export const createValidationStore = <T>(cb: CB<T>) => {
|
|
|
17
18
|
return await cb(val)
|
|
18
19
|
} finally {
|
|
19
20
|
store.set(false)
|
|
21
|
+
hasValidatedStore.set(true)
|
|
20
22
|
}
|
|
21
23
|
}
|
|
22
24
|
|
|
23
|
-
return [store, onValidate] as [
|
|
25
|
+
return [store, onValidate, hasValidatedStore] as [
|
|
26
|
+
Store<boolean>,
|
|
27
|
+
CB<T>,
|
|
28
|
+
Store<boolean>,
|
|
29
|
+
]
|
|
24
30
|
}
|