@faststore/core 4.1.1 → 4.1.2-dev.1
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/.turbo/turbo-generate.log +3 -3
- package/.turbo/turbo-test.log +29 -27
- package/CHANGELOG.md +12 -0
- package/index.ts +1 -1
- package/package.json +6 -6
- package/src/pages/api/graphql.ts +2 -2
- package/src/pages/pvt/account/403.tsx +10 -0
- package/src/sdk/account/useRefreshToken.ts +12 -0
- package/src/sdk/session/index.ts +3 -7
- package/src/utils/isLocalHost.ts +33 -0
- package/test/utils/isLocalHost.browser.test.ts +37 -0
- package/test/utils/isLocalHost.test.ts +38 -0
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
|
|
2
|
-
> @faststore/core@4.1.
|
|
2
|
+
> @faststore/core@4.1.2-dev.0 generate /home/runner/work/faststore/faststore/packages/core
|
|
3
3
|
> pnpm run gen-types && pnpm run cache-graphql
|
|
4
4
|
|
|
5
5
|
|
|
6
|
-
> @faststore/core@4.1.
|
|
6
|
+
> @faststore/core@4.1.2-dev.0 gen-types /home/runner/work/faststore/faststore/packages/core
|
|
7
7
|
> node ../cli/bin/run generate-types .
|
|
8
8
|
|
|
9
9
|
[33m[STARTED][39m Parse Configuration
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
[32m[COMPLETED][39m Generate to /home/runner/work/faststore/faststore/packages/core/@generated/
|
|
20
20
|
[32m[COMPLETED][39m Generate outputs
|
|
21
21
|
|
|
22
|
-
> @faststore/core@4.1.
|
|
22
|
+
> @faststore/core@4.1.2-dev.0 cache-graphql /home/runner/work/faststore/faststore/packages/core
|
|
23
23
|
> node ../cli/bin/run cache-graphql --config=./discovery.config.default.js --queries=./@generated/persisted-documents.json
|
|
24
24
|
|
|
25
25
|
[Info] - Config file location: /home/runner/work/faststore/faststore/packages/core/discovery.config.default.js
|
package/.turbo/turbo-test.log
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
|
|
2
|
-
> @faststore/core@4.1.
|
|
2
|
+
> @faststore/core@4.1.2-dev.0 test /home/runner/work/faststore/faststore/packages/core
|
|
3
3
|
> vitest run
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
[1m[46m RUN [49m[22m [36mv4.0.7 [39m[90m/home/runner/work/faststore/faststore/packages/core[39m
|
|
7
7
|
|
|
8
|
-
[32m✓[39m [30m[43m node [49m[39m test/utils/
|
|
9
|
-
[32m✓[39m [30m[43m node [49m[39m test/utils/
|
|
10
|
-
[32m✓[39m [30m[43m node [49m[39m test/sdk/localization/bindingSelector.test.ts [2m([22m[2m18 tests[22m[2m)[22m[32m
|
|
8
|
+
[32m✓[39m [30m[43m node [49m[39m test/utils/localization/bindingPaths.test.ts [2m([22m[2m71 tests[22m[2m)[22m[32m 107[2mms[22m[39m
|
|
9
|
+
[32m✓[39m [30m[43m node [49m[39m test/utils/match-url.test.ts [2m([22m[2m10 tests[22m[2m)[22m[32m 20[2mms[22m[39m
|
|
10
|
+
[32m✓[39m [30m[43m node [49m[39m test/sdk/localization/bindingSelector.test.ts [2m([22m[2m18 tests[22m[2m)[22m[32m 30[2mms[22m[39m
|
|
11
|
+
[32m✓[39m [30m[46m browser [49m[39m test/utils/isLocalHost.browser.test.ts [2m([22m[2m3 tests[22m[2m)[22m[32m 18[2mms[22m[39m
|
|
11
12
|
[90mstderr[2m | test/sdk/localization/store-url.browser.test.ts
|
|
12
13
|
[22m[39mError in optimistic validation: TypeError: Cannot read properties of undefined (reading 'read')
|
|
13
14
|
at validateCart [90m(/home/runner/work/faststore/faststore/packages/core/[39msrc/sdk/cart/index.ts:119:27[90m)[39m
|
|
@@ -18,31 +19,32 @@
|
|
|
18
19
|
[90mstderr[2m | test/sdk/localization/store-url.browser.test.ts
|
|
19
20
|
[22m[39mError in optimistic validation: ReferenceError: Cannot access '__vite_ssr_import_4__' before initialization
|
|
20
21
|
at getSettings [90m(/home/runner/work/faststore/faststore/packages/core/[39msrc/sdk/localization/useLocalizationConfig.tsx:126:45[90m)[39m
|
|
21
|
-
at validateSession [90m(/home/runner/work/faststore/faststore/packages/core/[39msrc/sdk/session/index.ts:
|
|
22
|
+
at validateSession [90m(/home/runner/work/faststore/faststore/packages/core/[39msrc/sdk/session/index.ts:140:22[90m)[39m
|
|
22
23
|
at onValidate [90m(/home/runner/work/faststore/faststore/packages/core/[39msrc/sdk/useStore.ts:18:20[90m)[39m
|
|
23
24
|
at a (/home/runner/work/faststore/faststore/packages/sdk/dist/es/index.mjs:353:23)
|
|
24
25
|
[90m at processTicksAndRejections (node:internal/process/task_queues:103:5)[39m
|
|
25
26
|
|
|
26
|
-
[32m✓[39m [30m[
|
|
27
|
-
[32m✓[39m [30m[
|
|
28
|
-
[32m✓[39m [30m[43m node [49m[39m test/utils/cookieCacheBusting.test.ts [2m([22m[2m10 tests[22m[2m)[22m[32m
|
|
29
|
-
[32m✓[39m [30m[43m node [49m[39m test/sdk/
|
|
30
|
-
[32m✓[39m [30m[43m node [49m[39m test/utils/multipleTemplates.test.ts [2m([22m[2m8 tests[22m[2m)[22m[32m
|
|
31
|
-
[32m✓[39m [30m[43m node [49m[39m test/utils/clearCookies.test.ts [2m([22m[2m20 tests[22m[2m)[22m[32m
|
|
32
|
-
[32m✓[39m [30m[43m node [49m[39m test/server/cms/global.test.ts [2m([22m[2m3 tests[22m[2m)[22m[32m
|
|
33
|
-
[32m✓[39m [30m[43m node [49m[39m test/server/content/service.test.ts [2m([22m[2m5 tests[22m[2m)[22m[32m
|
|
34
|
-
[32m✓[39m [30m[43m node [49m[39m test/utils/getRequestHostname.test.ts [2m([22m[2m12 tests[22m[2m)[22m[32m
|
|
35
|
-
[32m✓[39m [30m[43m node [49m[39m test/sdk/localization/store-url.test.ts [2m([22m[2m1 test[22m[2m)[22m[32m
|
|
36
|
-
[32m✓[39m [30m[43m node [49m[39m test/utils/validateSessionRefreshToken.test.ts [2m([22m[2m6 tests[22m[2m)[22m[32m
|
|
37
|
-
[32m✓[39m [30m[43m node [49m[39m test/pages/api/preview.test.ts [2m([22m[2m2 tests[22m[2m)[22m[32m
|
|
38
|
-
[32m✓[39m [30m[43m node [49m[39m test/server/cms/index.test.ts [2m([22m[2m2 tests[22m[2m)[22m[32m
|
|
39
|
-
[32m✓[39m [30m[43m node [49m[39m test/
|
|
40
|
-
|
|
41
|
-
[33m[2m✓[22m[39m should
|
|
42
|
-
[33m[2m✓[22m[39m should
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
[2m
|
|
46
|
-
[2m
|
|
47
|
-
[2m
|
|
27
|
+
[32m✓[39m [30m[46m browser [49m[39m test/sdk/localization/store-url.browser.test.ts [2m([22m[2m3 tests[22m[2m)[22m[32m 18[2mms[22m[39m
|
|
28
|
+
[32m✓[39m [30m[43m node [49m[39m test/sdk/localization/useBindingSelector.test.tsx [2m([22m[2m12 tests[22m[2m)[22m[32m 25[2mms[22m[39m
|
|
29
|
+
[32m✓[39m [30m[43m node [49m[39m test/utils/cookieCacheBusting.test.ts [2m([22m[2m10 tests[22m[2m)[22m[32m 48[2mms[22m[39m
|
|
30
|
+
[32m✓[39m [30m[43m node [49m[39m test/sdk/search/useSearchHistory.test.ts [2m([22m[2m13 tests[22m[2m)[22m[32m 104[2mms[22m[39m
|
|
31
|
+
[32m✓[39m [30m[43m node [49m[39m test/utils/multipleTemplates.test.ts [2m([22m[2m8 tests[22m[2m)[22m[32m 15[2mms[22m[39m
|
|
32
|
+
[32m✓[39m [30m[43m node [49m[39m test/utils/clearCookies.test.ts [2m([22m[2m20 tests[22m[2m)[22m[32m 101[2mms[22m[39m
|
|
33
|
+
[32m✓[39m [30m[43m node [49m[39m test/server/cms/global.test.ts [2m([22m[2m3 tests[22m[2m)[22m[32m 13[2mms[22m[39m
|
|
34
|
+
[32m✓[39m [30m[43m node [49m[39m test/server/content/service.test.ts [2m([22m[2m5 tests[22m[2m)[22m[32m 17[2mms[22m[39m
|
|
35
|
+
[32m✓[39m [30m[43m node [49m[39m test/utils/getRequestHostname.test.ts [2m([22m[2m12 tests[22m[2m)[22m[32m 24[2mms[22m[39m
|
|
36
|
+
[32m✓[39m [30m[43m node [49m[39m test/sdk/localization/store-url.test.ts [2m([22m[2m1 test[22m[2m)[22m[32m 6[2mms[22m[39m
|
|
37
|
+
[32m✓[39m [30m[43m node [49m[39m test/utils/validateSessionRefreshToken.test.ts [2m([22m[2m6 tests[22m[2m)[22m[32m 11[2mms[22m[39m
|
|
38
|
+
[32m✓[39m [30m[43m node [49m[39m test/pages/api/preview.test.ts [2m([22m[2m2 tests[22m[2m)[22m[32m 16[2mms[22m[39m
|
|
39
|
+
[32m✓[39m [30m[43m node [49m[39m test/server/cms/index.test.ts [2m([22m[2m2 tests[22m[2m)[22m[32m 17[2mms[22m[39m
|
|
40
|
+
[32m✓[39m [30m[43m node [49m[39m test/utils/isLocalHost.test.ts [2m([22m[2m7 tests[22m[2m)[22m[32m 8[2mms[22m[39m
|
|
41
|
+
[32m✓[39m [30m[43m node [49m[39m test/server/index.test.ts [2m([22m[2m7 tests[22m[2m)[22m[33m 1496[2mms[22m[39m
|
|
42
|
+
[33m[2m✓[22m[39m should return a valid merged GraphQL schema [33m 567[2mms[22m[39m
|
|
43
|
+
[33m[2m✓[22m[39m should exist with its plugins [33m 503[2mms[22m[39m
|
|
44
|
+
[33m[2m✓[22m[39m should handle options and execute [33m 400[2mms[22m[39m
|
|
45
|
+
|
|
46
|
+
[2m Test Files [22m [1m[32m19 passed[39m[22m[90m (19)[39m
|
|
47
|
+
[2m Tests [22m [1m[32m213 passed[39m[22m[90m (213)[39m
|
|
48
|
+
[2m Start at [22m 17:39:14
|
|
49
|
+
[2m Duration [22m 14.78s[2m (transform 5.74s, setup 0ms, collect 16.26s, tests 2.09s, environment 11.06s, prepare 673ms)[22m
|
|
48
50
|
|
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,18 @@
|
|
|
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
|
+
## 4.1.2-dev.1 (2026-05-19)
|
|
7
|
+
|
|
8
|
+
### Bug Fixes
|
|
9
|
+
|
|
10
|
+
- bypass refresh-token flow on localhost in 403 page and useRefreshToken hook ([#3325](https://github.com/vtex/faststore/issues/3325)) ([d48571a](https://github.com/vtex/faststore/commit/d48571a15176497b019bae1462617c3159459421)), closes [#3285](https://github.com/vtex/faststore/issues/3285) [#3285](https://github.com/vtex/faststore/issues/3285)
|
|
11
|
+
|
|
12
|
+
## 4.1.2-dev.0 (2026-05-15)
|
|
13
|
+
|
|
14
|
+
### Bug Fixes
|
|
15
|
+
|
|
16
|
+
- bump version ([4fbfa64](https://github.com/vtex/faststore/commit/4fbfa649afc57218dbf8020abc7333c05b0b034f))
|
|
17
|
+
|
|
6
18
|
## 4.1.1 (2026-05-15)
|
|
7
19
|
|
|
8
20
|
### Bug Fixes
|
package/index.ts
CHANGED
|
@@ -27,7 +27,7 @@ export { default as ProductShelfSection } from './src/components/sections/Produc
|
|
|
27
27
|
export { default as RegionBarSection } from './src/components/sections/RegionBar'
|
|
28
28
|
export { default as Section } from './src/components/sections/Section'
|
|
29
29
|
|
|
30
|
-
// Delivery Promise
|
|
30
|
+
// Delivery Promise
|
|
31
31
|
export {
|
|
32
32
|
PICKUP_IN_POINT_FACET_VALUE,
|
|
33
33
|
PICKUP_POINT_FACET_KEY,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@faststore/core",
|
|
3
|
-
"version": "4.1.1",
|
|
3
|
+
"version": "4.1.2-dev.1",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -70,11 +70,11 @@
|
|
|
70
70
|
"style-loader": "^3.3.1",
|
|
71
71
|
"swr": "^2.2.5",
|
|
72
72
|
"use-sync-external-store": "^1.6.0",
|
|
73
|
-
"@faststore/api": "4.1.1",
|
|
74
|
-
"@faststore/
|
|
75
|
-
"@faststore/
|
|
76
|
-
"@faststore/
|
|
77
|
-
"@faststore/
|
|
73
|
+
"@faststore/api": "4.1.2-dev.1",
|
|
74
|
+
"@faststore/diagnostics": "4.1.2-dev.1",
|
|
75
|
+
"@faststore/lighthouse": "4.1.2-dev.1",
|
|
76
|
+
"@faststore/sdk": "4.1.2-dev.1",
|
|
77
|
+
"@faststore/ui": "4.1.2-dev.1"
|
|
78
78
|
},
|
|
79
79
|
"devDependencies": {
|
|
80
80
|
"@cypress/code-coverage": "^3.12.1",
|
package/src/pages/api/graphql.ts
CHANGED
|
@@ -10,6 +10,7 @@ import type { NextApiHandler, NextApiRequest } from 'next'
|
|
|
10
10
|
import discoveryConfig from 'discovery.config'
|
|
11
11
|
import { getJWTAutCookie } from 'src/utils/getCookie'
|
|
12
12
|
import { getRequestHostname } from 'src/utils/getRequestHostname'
|
|
13
|
+
import { isLocalHost } from 'src/utils/isLocalHost'
|
|
13
14
|
import { shouldForceRefreshTokenForValidateSession } from 'src/utils/validateSessionRefreshToken'
|
|
14
15
|
import { execute } from '../../server'
|
|
15
16
|
|
|
@@ -142,8 +143,7 @@ const handler: NextApiHandler = async (request, response) => {
|
|
|
142
143
|
// value is used to cache bust the request if there is a VtexIdclientAutCookie
|
|
143
144
|
const { operation, variables, query, v: value } = parseRequest(request)
|
|
144
145
|
|
|
145
|
-
const
|
|
146
|
-
const isLocal = hostname === 'localhost' || hostname === '127.0.0.1'
|
|
146
|
+
const isLocal = isLocalHost(getRequestHostname(request.headers.host))
|
|
147
147
|
|
|
148
148
|
if (
|
|
149
149
|
!isLocal &&
|
|
@@ -23,6 +23,8 @@ import { useRefreshToken } from 'src/sdk/account/useRefreshToken'
|
|
|
23
23
|
import PageProvider from 'src/sdk/overrides/PageProvider'
|
|
24
24
|
import { execute } from 'src/server'
|
|
25
25
|
import { injectGlobalSections } from 'src/server/cms/global'
|
|
26
|
+
import { getRequestHostname } from 'src/utils/getRequestHostname'
|
|
27
|
+
import { isLocalHost } from 'src/utils/isLocalHost'
|
|
26
28
|
import { withLocaleValidationSSR } from 'src/utils/localization/withLocaleValidation'
|
|
27
29
|
import { getMyAccountRedirect } from 'src/utils/myAccountRedirect'
|
|
28
30
|
|
|
@@ -137,10 +139,18 @@ const getServerSidePropsBase: GetServerSideProps<
|
|
|
137
139
|
const fromPage =
|
|
138
140
|
typeof context.query.from === 'string' ? context.query.from : ''
|
|
139
141
|
|
|
142
|
+
// The refresh-token round-trip is unreachable from localhost (cross-origin
|
|
143
|
+
// POST that drops the `vid_rt` cookie), and forcing it would clear the
|
|
144
|
+
// manually injected `VtexIdclientAutCookie_<account>`. Render the static
|
|
145
|
+
// 403 view instead so developers can keep testing logged-in scenarios
|
|
146
|
+
// regardless of the `experimental.refreshToken` flag.
|
|
147
|
+
const isLocal = isLocalHost(getRequestHostname(context.req.headers.host))
|
|
148
|
+
|
|
140
149
|
return {
|
|
141
150
|
props: {
|
|
142
151
|
globalSections: globalSectionsResult,
|
|
143
152
|
needsRefreshToken:
|
|
153
|
+
!isLocal &&
|
|
144
154
|
(statusCode === 401 || statusCode === 403) &&
|
|
145
155
|
storeConfig.experimental?.refreshToken,
|
|
146
156
|
fromPage,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { useEffect, useState } from 'react'
|
|
2
|
+
import { isLocalHostBrowser } from 'src/utils/isLocalHost'
|
|
2
3
|
import { logoutAndClearSession, sessionStore } from '../session'
|
|
3
4
|
import { isRefreshTokenSuccessful, refreshTokenRequest } from './refreshToken'
|
|
4
5
|
|
|
@@ -12,6 +13,17 @@ export const useRefreshToken = (
|
|
|
12
13
|
const handleRefreshTokenAndUpdateSession = async () => {
|
|
13
14
|
if (!needsRefreshToken) return
|
|
14
15
|
|
|
16
|
+
// The refresh-token endpoint lives on the production origin and relies on
|
|
17
|
+
// the `vid_rt` cookie scoped to that origin. From localhost the request is
|
|
18
|
+
// cross-origin, so the cookie is not sent and the refresh always fails —
|
|
19
|
+
// which would also wipe the manually injected `VtexIdclientAutCookie_<account>`
|
|
20
|
+
// via `logoutAndClearSession`. Bail out and fall back to the static 403
|
|
21
|
+
// view so developers can keep testing logged-in flows.
|
|
22
|
+
if (isLocalHostBrowser()) {
|
|
23
|
+
setShouldShow403(true)
|
|
24
|
+
return
|
|
25
|
+
}
|
|
26
|
+
|
|
15
27
|
const currentSession = sessionStore.read() ?? sessionStore.readInitial()
|
|
16
28
|
|
|
17
29
|
try {
|
package/src/sdk/session/index.ts
CHANGED
|
@@ -8,6 +8,7 @@ import type {
|
|
|
8
8
|
ValidateSessionMutationVariables,
|
|
9
9
|
} from '@generated/graphql'
|
|
10
10
|
import deepEqual from 'fast-deep-equal'
|
|
11
|
+
import { isLocalHostBrowser } from 'src/utils/isLocalHost'
|
|
11
12
|
import { filterChannel } from 'src/utils/utilities'
|
|
12
13
|
import storeConfig from '../../../discovery.config'
|
|
13
14
|
import {
|
|
@@ -21,11 +22,6 @@ import { createValidationStore, useStore } from '../useStore'
|
|
|
21
22
|
import { getPostalCode } from '../userLocation/index'
|
|
22
23
|
import { RELOAD_AFTER_LOGOUT_KEY, SESSION_READY_KEY } from './storageKeys'
|
|
23
24
|
|
|
24
|
-
const isLocalEnvironment = (): boolean =>
|
|
25
|
-
typeof window !== 'undefined' &&
|
|
26
|
-
(window.location.hostname === 'localhost' ||
|
|
27
|
-
window.location.hostname === '127.0.0.1')
|
|
28
|
-
|
|
29
25
|
const isReloadAfterLogoutPending = (): boolean => {
|
|
30
26
|
try {
|
|
31
27
|
return (
|
|
@@ -129,7 +125,7 @@ export const validateSession = async (session: Session) => {
|
|
|
129
125
|
// On failure (logoutAndClearSession already triggered), bail out.
|
|
130
126
|
// Skipped in local environments where the refresh token infrastructure is unavailable.
|
|
131
127
|
if (
|
|
132
|
-
!
|
|
128
|
+
!isLocalHostBrowser() &&
|
|
133
129
|
storeConfig.experimental?.refreshToken &&
|
|
134
130
|
isRefreshAfterExpired(session)
|
|
135
131
|
) {
|
|
@@ -200,7 +196,7 @@ export const validateSession = async (session: Session) => {
|
|
|
200
196
|
return data.validateSession
|
|
201
197
|
} catch (error) {
|
|
202
198
|
const shouldRefreshToken =
|
|
203
|
-
!
|
|
199
|
+
!isLocalHostBrowser() &&
|
|
204
200
|
error?.status === 401 &&
|
|
205
201
|
storeConfig.experimental?.refreshToken
|
|
206
202
|
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hostnames that represent a local development environment.
|
|
3
|
+
*
|
|
4
|
+
* These are the only hosts where the FastStore app skips the refresh-token
|
|
5
|
+
* flow so developers can drive the app with a manually injected
|
|
6
|
+
* `VtexIdclientAutCookie_<account>` cookie regardless of feature flags.
|
|
7
|
+
*/
|
|
8
|
+
const LOCAL_HOSTNAMES = new Set(['localhost', '127.0.0.1'])
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Returns `true` when the given hostname corresponds to a local development
|
|
12
|
+
* environment (`localhost` / `127.0.0.1`).
|
|
13
|
+
*
|
|
14
|
+
* Refresh-token and other production-only flows MUST be bypassed on these
|
|
15
|
+
* hostnames so that developers can test logged-in scenarios with manually
|
|
16
|
+
* injected cookies, independent of any experimental flag.
|
|
17
|
+
*
|
|
18
|
+
* The input MUST be a bare hostname (no port). Use {@link getRequestHostname}
|
|
19
|
+
* on server-side request headers before calling this helper.
|
|
20
|
+
*/
|
|
21
|
+
export function isLocalHost(hostname: string | null | undefined): boolean {
|
|
22
|
+
if (!hostname) return false
|
|
23
|
+
return LOCAL_HOSTNAMES.has(hostname.toLowerCase())
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Browser-side wrapper that reads `window.location.hostname`. Returns `false`
|
|
28
|
+
* when `window` is not available (SSR/Node).
|
|
29
|
+
*/
|
|
30
|
+
export function isLocalHostBrowser(): boolean {
|
|
31
|
+
if (typeof window === 'undefined') return false
|
|
32
|
+
return isLocalHost(window.location.hostname)
|
|
33
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { afterEach, describe, expect, it } from 'vitest'
|
|
2
|
+
import { isLocalHostBrowser } from '../../src/utils/isLocalHost'
|
|
3
|
+
|
|
4
|
+
const originalLocation = window.location
|
|
5
|
+
|
|
6
|
+
function stubHostname(hostname: string) {
|
|
7
|
+
// jsdom forbids assigning to `window.location` directly, so we redefine the
|
|
8
|
+
// descriptor with a minimal stub that exposes the hostname we want to test.
|
|
9
|
+
Object.defineProperty(window, 'location', {
|
|
10
|
+
configurable: true,
|
|
11
|
+
value: { ...originalLocation, hostname },
|
|
12
|
+
})
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
describe('isLocalHostBrowser', () => {
|
|
16
|
+
afterEach(() => {
|
|
17
|
+
Object.defineProperty(window, 'location', {
|
|
18
|
+
configurable: true,
|
|
19
|
+
value: originalLocation,
|
|
20
|
+
})
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
it('returns true when window.location.hostname is "localhost"', () => {
|
|
24
|
+
stubHostname('localhost')
|
|
25
|
+
expect(isLocalHostBrowser()).toBe(true)
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
it('returns true when window.location.hostname is "127.0.0.1"', () => {
|
|
29
|
+
stubHostname('127.0.0.1')
|
|
30
|
+
expect(isLocalHostBrowser()).toBe(true)
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
it('returns false for a production hostname', () => {
|
|
34
|
+
stubHostname('brandless.fast.store')
|
|
35
|
+
expect(isLocalHostBrowser()).toBe(false)
|
|
36
|
+
})
|
|
37
|
+
})
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
import { isLocalHost } from '../../src/utils/isLocalHost'
|
|
3
|
+
|
|
4
|
+
describe('isLocalHost', () => {
|
|
5
|
+
it('returns true for "localhost"', () => {
|
|
6
|
+
expect(isLocalHost('localhost')).toBe(true)
|
|
7
|
+
})
|
|
8
|
+
|
|
9
|
+
it('returns true for "127.0.0.1"', () => {
|
|
10
|
+
expect(isLocalHost('127.0.0.1')).toBe(true)
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
it('is case-insensitive', () => {
|
|
14
|
+
expect(isLocalHost('LOCALHOST')).toBe(true)
|
|
15
|
+
expect(isLocalHost('Localhost')).toBe(true)
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
it('returns false for a production hostname', () => {
|
|
19
|
+
expect(isLocalHost('brandless.fast.store')).toBe(false)
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
it('returns false for IPv6 loopback (not in the bypass list)', () => {
|
|
23
|
+
// We intentionally bypass only the two canonical local hosts. IPv6
|
|
24
|
+
// loopback (`::1`) and other loopback variants are out of scope.
|
|
25
|
+
expect(isLocalHost('::1')).toBe(false)
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
it('returns false for inputs that still carry a port', () => {
|
|
29
|
+
// Callers MUST normalize the hostname via getRequestHostname first.
|
|
30
|
+
expect(isLocalHost('localhost:3000')).toBe(false)
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
it('returns false for null/undefined/empty input', () => {
|
|
34
|
+
expect(isLocalHost(null)).toBe(false)
|
|
35
|
+
expect(isLocalHost(undefined)).toBe(false)
|
|
36
|
+
expect(isLocalHost('')).toBe(false)
|
|
37
|
+
})
|
|
38
|
+
})
|