@akinon/next 1.91.0-rc.1 → 1.91.0-rc.3

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/CHANGELOG.md CHANGED
@@ -1,5 +1,68 @@
1
1
  # @akinon/next
2
2
 
3
+ ## 1.91.0-rc.3
4
+
5
+ ### Minor Changes
6
+
7
+ - 5dfeea04: ZERO-2801: Revert ZERO-2801
8
+ - 823d82f9: ZERO-3393: Enhance error handling in checkout middleware to ensure errors are checked for existence before processing
9
+ - 63774a6a: ZERO-3351: Add commerce redirection ignore list functionality and related utility
10
+ - 2d9b2b2c: ZERO-2816: Add segment to headers
11
+ - 5e1feca6: Revert "ZERO-3286: Add notFound handling for chunk URLs starting with \_next"
12
+ - d8fad39f: ZERO-3370: include plugins test to build stage
13
+ - 40a46853: ZERO-3182: Optimize basket update mutation with optimistic update
14
+ - 68bbcb27: ZERO-3393: Fix error handling in checkout middleware to check for errors array length
15
+ - 25524867: ZERO-3391: Add subdomain support to setLocale function
16
+ - f49bb74f: ZERO-3097: Add setCookie to logging in payment redirection middlewares
17
+ - e9541a13: ZERO-2816: Add headers to url
18
+ - 9b7d0de6: ZERO-3393: Improve error handling in checkout middleware to support both object and array error formats
19
+ - 72fd4d67: ZERO-3084: Fix URL search parameters encoding in default middleware
20
+ - c53ef7b9: ZERO-2668: The Link component has been updated to improve the logic for handling href values. Previously, if the href was not a string or started with 'http', it would return the href as is. Now, if the href is not provided, it will default to '#' to prevent any potential errors. Additionally, if the href is a string and does not start with 'http', it will be formatted with the locale and pathname, based on the localeUrlStrategy and defaultLocaleValue. This ensures that the correct href is generated based on the localization settings.
21
+ - f8e4cac6: ZERO-3343: restrict root hostname to only locale subdomains
22
+ - 64699d3f: ZERO-2761: Fix invalid import for plugin module
23
+ - 832bee36: ZERO-3343: add domain to cookie for subdomain locale strategy
24
+ - e974d8e8: ZERO-3406: Fix rc build
25
+ - 28a59d49: ZERO-3400: refactor cookie domain logic using fallback host
26
+ - 8feabe9a: ZERO-3343: add custom NextAuth options support
27
+ - bf354de4: ZERO-3321: add babel compiler for akinon/next test
28
+ - 7727ae55: ZERO-3073: Refactor basket page to use server-side data fetching and simplify component structure
29
+ - d552629f: ZERO-3182: Refactor basketApi to use invalidatesTags and comment out onQueryStarted logic
30
+ - 448adefb: ZERO-3321: move csp test to akinon-next
31
+ - 17f87524: ZERO-2816: Make the incoming currency lowercase
32
+ - 65d3b862: ZERO-3054: Update headers in appFetch
33
+ - bbe18b9f: ZERO-2575: Fix build error
34
+ - 17bfadc4: ZERO-3275: Disable OpenTelemetry monitoring in production environment
35
+ - 4920742c: Disable getCachedTranslations
36
+ - b6e5b624: ZERO-3257: Enhance locale middleware to redirect using existing or default locale and support 303 status for POST requests
37
+ - 6c3629c2: ZERO-3321: fix jest tests in akinon-next for standalone projects
38
+ - 6bc260be: ZERO-3295: update default tailwind content list
39
+ - 7e56d6b6: ZERO-2841: Update api tagTypes
40
+ - dfaceffd: ZERO-3356: Add useLoyaltyAvailability hook and update checkout state management
41
+ - 33377cfd: ZERO-3267: Refactor import statement for ROUTES in error-page component
42
+ - 43c182ee: ZERO-3054: Update Redis variable checks to conditionally include CACHE_SECRET
43
+ - 943a239e: ZERO-3370: add allowJs in akinon-next test tsconfig
44
+ - 068dc394: ZERO-3343: update get-root-hostname logic
45
+ - 942490f2: ZERO-3295: move third party tailwind content list to akinon-next
46
+ - eeb20bea: Revert "ZERO-3054: Refactor cache handler to use custom Redis handler and implement key hashing"
47
+ - 3bf63c8a: ZERO-3286: Add notFound handling for chunk URLs starting with \_next
48
+ - 9be2c081: ZERO-3243: Improve basket update query handling with optimistic updates
49
+ - f2c92d5c: ZERO-2816: Update cookie name
50
+ - 7bd3d992: ZERO-2801: Refactor locale middleware to handle single locale configuration
51
+ - b6d5bda2: ZERO-3343: update changeset config
52
+ - fdd255ee: ZERO-3054: Refactor cache handler to use custom Redis handler and implement key hashing
53
+ - acf03209: ZERO-3321: remove babel config
54
+ - 387356b6: ZERO-3323: Refactor locale filtering logic in URL matcher regex
55
+ - 49eeebfa: ZERO-2909: Add deleteCollectionItem query to wishlistApi
56
+ - 3f9b8d7e: ZERO-2761: Update plugins.js for akinon-next
57
+ - b2ee69b9: ZERO-3321: delete unnecessary files
58
+ - 0cabbda3: ZERO-3370: replace inline monorepo check with reusable utility
59
+
60
+ ## 1.91.0-rc.2
61
+
62
+ ### Minor Changes
63
+
64
+ - 2552486: ZERO-3391: Add subdomain support to setLocale function
65
+
3
66
  ## 1.91.0-rc.1
4
67
 
5
68
  ## 1.91.0-rc.0
package/api/auth.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { NextApiRequest, NextApiResponse } from 'next';
2
- import NextAuth, { Session } from 'next-auth';
2
+ import NextAuth, { Session, NextAuthOptions } from 'next-auth';
3
3
  import CredentialProvider from 'next-auth/providers/credentials';
4
4
  import { ROUTES } from 'routes';
5
5
  import { URLS, user } from '../data/urls';
@@ -7,6 +7,8 @@ import Settings from 'settings';
7
7
  import { urlLocaleMatcherRegex } from '../utils';
8
8
  import logger from '@akinon/next/utils/log';
9
9
  import { AuthError } from '../types';
10
+ import getRootHostname from '../utils/get-root-hostname';
11
+ import { LocaleUrlStrategy } from '../localization';
10
12
 
11
13
  async function getCurrentUser(sessionId: string, currency = '') {
12
14
  const headers = {
@@ -40,7 +42,15 @@ async function getCurrentUser(sessionId: string, currency = '') {
40
42
  };
41
43
  }
42
44
 
43
- const nextAuthOptions = (req: NextApiRequest, res: NextApiResponse) => {
45
+ type CustomNextAuthOptions = (
46
+ req: NextApiRequest,
47
+ res: NextApiResponse
48
+ ) => Partial<NextAuthOptions>;
49
+
50
+ const defaultNextAuthOptions = (
51
+ req: NextApiRequest,
52
+ res: NextApiResponse
53
+ ): NextAuthOptions => {
44
54
  return {
45
55
  providers: [
46
56
  CredentialProvider({
@@ -150,7 +160,19 @@ const nextAuthOptions = (req: NextApiRequest, res: NextApiResponse) => {
150
160
 
151
161
  if (sessionId) {
152
162
  const maxAge = 30 * 24 * 60 * 60; // 30 days in seconds
153
- const cookieOptions = `Path=/; HttpOnly; Secure; Max-Age=${maxAge}`;
163
+ const { localeUrlStrategy } = Settings.localization;
164
+
165
+ const fallbackHost =
166
+ req.headers['x-forwarded-host']?.toString() ||
167
+ req.headers.host?.toString();
168
+ const hostname =
169
+ process.env.NEXT_PUBLIC_URL || `https://${fallbackHost}`;
170
+ const rootHostname =
171
+ localeUrlStrategy === LocaleUrlStrategy.Subdomain
172
+ ? getRootHostname(hostname)
173
+ : null;
174
+ const domainOption = rootHostname ? `Domain=${rootHostname};` : '';
175
+ const cookieOptions = `Path=/; HttpOnly; Secure; Max-Age=${maxAge}; ${domainOption}`;
154
176
 
155
177
  res.setHeader('Set-Cookie', [
156
178
  `osessionid=${sessionId}; ${cookieOptions}`,
@@ -253,8 +275,36 @@ const nextAuthOptions = (req: NextApiRequest, res: NextApiResponse) => {
253
275
  };
254
276
  };
255
277
 
256
- const Auth = (req, res) => {
257
- return NextAuth(req, res, nextAuthOptions(req, res));
278
+ const Auth = (
279
+ req: NextApiRequest,
280
+ res: NextApiResponse,
281
+ customOptions?: CustomNextAuthOptions
282
+ ) => {
283
+ const baseOptions = defaultNextAuthOptions(req, res);
284
+ const customOptionsResult = customOptions ? customOptions(req, res) : {};
285
+
286
+ const mergedOptions = {
287
+ ...baseOptions,
288
+ ...customOptionsResult,
289
+ providers: [
290
+ ...baseOptions.providers,
291
+ ...(customOptionsResult.providers || [])
292
+ ],
293
+ callbacks: {
294
+ ...baseOptions.callbacks,
295
+ ...customOptionsResult.callbacks
296
+ },
297
+ events: {
298
+ ...baseOptions.events,
299
+ ...customOptionsResult.events
300
+ },
301
+ pages: {
302
+ ...baseOptions.pages,
303
+ ...customOptionsResult.pages
304
+ }
305
+ };
306
+
307
+ return NextAuth(req, res, mergedOptions);
258
308
  };
259
309
 
260
310
  export default Auth;
package/api/client.ts CHANGED
@@ -5,6 +5,8 @@ import logger from '../utils/log';
5
5
  import formatCookieString from '../utils/format-cookie-string';
6
6
  import cookieParser from 'set-cookie-parser';
7
7
  import { cookies } from 'next/headers';
8
+ import getRootHostname from '../utils/get-root-hostname';
9
+ import { LocaleUrlStrategy } from '../localization';
8
10
 
9
11
  interface RouteParams {
10
12
  params: {
@@ -190,8 +192,23 @@ async function proxyRequest(...args) {
190
192
  const responseHeaders: any = {};
191
193
 
192
194
  if (filteredCookies.length > 0) {
195
+ const { localeUrlStrategy } = settings.localization;
196
+
197
+ const fallbackHost =
198
+ req.headers.get('x-forwarded-host') || req.headers.get('host');
199
+ const hostname = process.env.NEXT_PUBLIC_URL || `https://${fallbackHost}`;
200
+ const rootHostname =
201
+ localeUrlStrategy === LocaleUrlStrategy.Subdomain
202
+ ? getRootHostname(hostname)
203
+ : null;
204
+
193
205
  responseHeaders['set-cookie'] = filteredCookies
194
- .map(formatCookieString)
206
+ .map((cookie) => {
207
+ if (!cookie.domain && rootHostname) {
208
+ cookie.domain = rootHostname;
209
+ }
210
+ return formatCookieString(cookie);
211
+ })
195
212
  .join(', ');
196
213
  }
197
214
 
@@ -25,19 +25,33 @@ export const useLocalization = () => {
25
25
  * @param locale Locale value defined in the settings.
26
26
  */
27
27
  const setLocale = (locale: string) => {
28
- const localePath =
29
- locale === defaultLocaleValue &&
30
- localeUrlStrategy !== LocaleUrlStrategy.ShowAllLocales
31
- ? ''
32
- : `/${locale}`;
28
+ const { protocol, hostname, port, search, pathname } = location;
29
+ const pathnameWithoutLocale = pathname.replace(urlLocaleMatcherRegex, '');
33
30
 
34
- const pathnameWithoutLocale = location.pathname.replace(
35
- urlLocaleMatcherRegex,
36
- ''
37
- );
31
+ let targetUrl;
32
+
33
+ if (localeUrlStrategy === LocaleUrlStrategy.Subdomain) {
34
+ const hostParts = hostname.split('.');
35
+ const subDomain = hostParts[0];
36
+ const isSubdomainLocale = locales.some((loc) => loc.value === subDomain);
37
+ const baseDomain = isSubdomainLocale
38
+ ? hostParts.slice(1).join('.')
39
+ : hostname;
40
+
41
+ const formattedPort = port ? `:${port}` : '';
42
+
43
+ targetUrl = `${protocol}//${locale}.${baseDomain}${formattedPort}${pathnameWithoutLocale}${search}`;
44
+ } else {
45
+ const shouldHideLocale =
46
+ locale === defaultLocaleValue &&
47
+ localeUrlStrategy !== LocaleUrlStrategy.ShowAllLocales;
48
+ const localePath = shouldHideLocale ? '' : `/${locale}`;
49
+
50
+ targetUrl = `${localePath}${pathnameWithoutLocale}${search}`;
51
+ }
38
52
 
39
53
  // router.push is not used because reloading the page also clears the client side cache.
40
- location.href = `${localePath}${pathnameWithoutLocale}${location.search}`;
54
+ location.href = targetUrl;
41
55
  };
42
56
 
43
57
  /**
@@ -19,6 +19,8 @@ import withLocale from './locale';
19
19
  import logger from '../utils/log';
20
20
  import { user } from '../data/urls';
21
21
  import { getUrlPathWithLocale } from '../utils/localization';
22
+ import getRootHostname from '../utils/get-root-hostname';
23
+ import { LocaleUrlStrategy } from '../localization';
22
24
 
23
25
  const withPzDefault =
24
26
  (middleware: NextMiddleware) =>
@@ -296,7 +298,7 @@ const withPzDefault =
296
298
  !req.middlewareParams.found &&
297
299
  Settings.customNotFoundEnabled
298
300
  ) {
299
- let pathname = url.pathname
301
+ const pathname = url.pathname
300
302
  .replace(/\/+$/, '')
301
303
  .split('/');
302
304
  url.pathname = url.pathname.replace(
@@ -341,6 +343,21 @@ const withPzDefault =
341
343
  middlewareResult = NextResponse.rewrite(url);
342
344
  }
343
345
 
346
+ const { localeUrlStrategy } =
347
+ Settings.localization;
348
+
349
+ const fallbackHost =
350
+ req.headers.get('x-forwarded-host') ||
351
+ req.headers.get('host');
352
+ const hostname =
353
+ process.env.NEXT_PUBLIC_URL ||
354
+ `https://${fallbackHost}`;
355
+ const rootHostname =
356
+ localeUrlStrategy ===
357
+ LocaleUrlStrategy.Subdomain
358
+ ? getRootHostname(hostname)
359
+ : null;
360
+
344
361
  if (
345
362
  !url.pathname.startsWith(`/${currency}/orders`)
346
363
  ) {
@@ -350,6 +367,7 @@ const withPzDefault =
350
367
  ? locale
351
368
  : defaultLocaleValue,
352
369
  {
370
+ domain: rootHostname,
353
371
  sameSite: 'none',
354
372
  secure: true,
355
373
  expires: new Date(
@@ -358,10 +376,12 @@ const withPzDefault =
358
376
  }
359
377
  );
360
378
  }
379
+
361
380
  middlewareResult.cookies.set(
362
381
  'pz-currency',
363
382
  currency,
364
383
  {
384
+ domain: rootHostname,
365
385
  sameSite: 'none',
366
386
  secure: true,
367
387
  expires: new Date(
@@ -410,7 +430,10 @@ const withPzDefault =
410
430
  ).json();
411
431
  middlewareResult.cookies.set(
412
432
  'csrftoken',
413
- csrf_token
433
+ csrf_token,
434
+ {
435
+ domain: rootHostname
436
+ }
414
437
  );
415
438
  }
416
439
  } catch (error) {
@@ -13,7 +13,8 @@ const getMatchedLocale = (pathname: string, req: PzNextRequest) => {
13
13
  const { localeUrlStrategy, defaultLocaleValue } = settings.localization;
14
14
 
15
15
  if (localeUrlStrategy === LocaleUrlStrategy.Subdomain) {
16
- const host = req.headers.get('x-forwarded-host');
16
+ const host =
17
+ req.headers.get('x-forwarded-host') || req.headers.get('host') || '';
17
18
 
18
19
  if (host) {
19
20
  const subDomain = host.split('.')[0] || '';
@@ -44,7 +45,9 @@ const withLocale =
44
45
  try {
45
46
  const url = req.nextUrl.clone();
46
47
  const matchedLocale = getMatchedLocale(url.pathname, req);
47
- let { localeUrlStrategy, defaultLocaleValue, redirectToDefaultLocale } =
48
+ let { localeUrlStrategy } = settings.localization;
49
+
50
+ const { defaultLocaleValue, redirectToDefaultLocale } =
48
51
  settings.localization;
49
52
 
50
53
  localeUrlStrategy =
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@akinon/next",
3
3
  "description": "Core package for Project Zero Next",
4
- "version": "1.91.0-rc.1",
4
+ "version": "1.91.0-rc.3",
5
5
  "private": false,
6
6
  "license": "MIT",
7
7
  "bin": {
@@ -34,7 +34,11 @@
34
34
  "set-cookie-parser": "2.6.0"
35
35
  },
36
36
  "devDependencies": {
37
- "@akinon/eslint-plugin-projectzero": "1.91.0-rc.1",
37
+ "@akinon/eslint-plugin-projectzero": "1.91.0-rc.3",
38
+ "@babel/core": "7.26.10",
39
+ "@babel/preset-env": "7.26.9",
40
+ "@babel/preset-typescript": "7.27.0",
41
+ "@types/jest": "29.5.14",
38
42
  "@types/react-redux": "7.1.30",
39
43
  "@types/set-cookie-parser": "2.4.7",
40
44
  "@typescript-eslint/eslint-plugin": "6.7.4",
@@ -0,0 +1,16 @@
1
+ const defaultPlugins = require('../plugins');
2
+
3
+ const getAkinonNextContent = (plugins = defaultPlugins) => {
4
+ return [
5
+ `./node_modules/@akinon/next/components/**/*.{js,ts,jsx,tsx}`,
6
+ `../../node_modules/@akinon/next/components/**/*.{js,ts,jsx,tsx}`,
7
+ ...plugins
8
+ .map((plugin) => [
9
+ `./node_modules/@akinon/${plugin}/**/*.{js,ts,jsx,tsx}`,
10
+ `../../node_modules/@akinon/${plugin}/**/*.{js,ts,jsx,tsx}`
11
+ ])
12
+ .flat()
13
+ ];
14
+ };
15
+
16
+ module.exports = getAkinonNextContent;
@@ -0,0 +1,28 @@
1
+ import Settings from 'settings';
2
+
3
+ export default function getRootHostname(
4
+ url: string | undefined
5
+ ): string | null {
6
+ if (!url) return null;
7
+
8
+ try {
9
+ const urlObj = new URL(url);
10
+ const hostname = urlObj.hostname;
11
+ const parts = hostname.split('.');
12
+
13
+ const firstPart = parts[0];
14
+
15
+ const isLocale = Settings.localization.locales.some(
16
+ (locale) => locale.value === firstPart
17
+ );
18
+
19
+ if (isLocale) {
20
+ const hostnameAfterLocale = parts.slice(1).join('.');
21
+ return `.${hostnameAfterLocale}`;
22
+ }
23
+
24
+ return `.${hostname}`;
25
+ } catch {
26
+ return null;
27
+ }
28
+ }