@akinon/next 1.89.0-rc.1 → 1.89.0-snapshot-ZERO-3343-20250425070312

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/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,13 @@ 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
+ const rootHostname =
165
+ localeUrlStrategy === LocaleUrlStrategy.Subdomain
166
+ ? getRootHostname(process.env.NEXT_PUBLIC_URL)
167
+ : null;
168
+ const domainOption = rootHostname ? `Domain=${rootHostname};` : '';
169
+ const cookieOptions = `Path=/; HttpOnly; Secure; Max-Age=${maxAge}; ${domainOption}`;
154
170
 
155
171
  res.setHeader('Set-Cookie', [
156
172
  `osessionid=${sessionId}; ${cookieOptions}`,
@@ -253,8 +269,36 @@ const nextAuthOptions = (req: NextApiRequest, res: NextApiResponse) => {
253
269
  };
254
270
  };
255
271
 
256
- const Auth = (req, res) => {
257
- return NextAuth(req, res, nextAuthOptions(req, res));
272
+ const Auth = (
273
+ req: NextApiRequest,
274
+ res: NextApiResponse,
275
+ customOptions?: CustomNextAuthOptions
276
+ ) => {
277
+ const baseOptions = defaultNextAuthOptions(req, res);
278
+ const customOptionsResult = customOptions ? customOptions(req, res) : {};
279
+
280
+ const mergedOptions = {
281
+ ...baseOptions,
282
+ ...customOptionsResult,
283
+ providers: [
284
+ ...baseOptions.providers,
285
+ ...(customOptionsResult.providers || [])
286
+ ],
287
+ callbacks: {
288
+ ...baseOptions.callbacks,
289
+ ...customOptionsResult.callbacks
290
+ },
291
+ events: {
292
+ ...baseOptions.events,
293
+ ...customOptionsResult.events
294
+ },
295
+ pages: {
296
+ ...baseOptions.pages,
297
+ ...customOptionsResult.pages
298
+ }
299
+ };
300
+
301
+ return NextAuth(req, res, mergedOptions);
258
302
  };
259
303
 
260
304
  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,19 @@ async function proxyRequest(...args) {
190
192
  const responseHeaders: any = {};
191
193
 
192
194
  if (filteredCookies.length > 0) {
195
+ const { localeUrlStrategy } = settings.localization;
196
+ const rootHostname =
197
+ localeUrlStrategy === LocaleUrlStrategy.Subdomain
198
+ ? getRootHostname(process.env.NEXT_PUBLIC_URL)
199
+ : null;
200
+
193
201
  responseHeaders['set-cookie'] = filteredCookies
194
- .map(formatCookieString)
202
+ .map((cookie) => {
203
+ if (!cookie.domain && rootHostname) {
204
+ cookie.domain = rootHostname;
205
+ }
206
+ return formatCookieString(cookie);
207
+ })
195
208
  .join(', ');
196
209
  }
197
210
 
@@ -1,8 +1,6 @@
1
1
  import clsx from 'clsx';
2
2
  import { forwardRef, FocusEvent, useState, Ref } from 'react';
3
3
  import { Controller } from 'react-hook-form';
4
-
5
- // @ts-ignore
6
4
  import { PatternFormat, PatternFormatProps } from 'react-number-format';
7
5
  import { InputProps } from '../types';
8
6
  import { twMerge } from 'tailwind-merge';
@@ -10,9 +10,7 @@ type LinkProps = Omit<
10
10
  React.AnchorHTMLAttributes<HTMLAnchorElement>,
11
11
  keyof NextLinkProps
12
12
  > &
13
- NextLinkProps & {
14
- href: string;
15
- };
13
+ NextLinkProps;
16
14
 
17
15
  export const Link = ({ children, href, ...rest }: LinkProps) => {
18
16
  const { locale, defaultLocaleValue, localeUrlStrategy } = useLocalization();
@@ -28,21 +26,19 @@ export const Link = ({ children, href, ...rest }: LinkProps) => {
28
26
  return href;
29
27
  }
30
28
 
31
- if (typeof href === 'string' && !href.startsWith('http')) {
32
- const pathnameWithoutLocale = href.replace(urlLocaleMatcherRegex, '');
33
- const hrefWithLocale = `/${locale}${pathnameWithoutLocale}`;
34
-
35
- if (localeUrlStrategy === LocaleUrlStrategy.ShowAllLocales) {
36
- return hrefWithLocale;
37
- } else if (
38
- localeUrlStrategy === LocaleUrlStrategy.HideDefaultLocale &&
39
- locale !== defaultLocaleValue
40
- ) {
41
- return hrefWithLocale;
42
- }
29
+ const pathnameWithoutLocale = href.replace(urlLocaleMatcherRegex, '');
30
+ const hrefWithLocale = `/${locale}${pathnameWithoutLocale}`;
31
+
32
+ if (localeUrlStrategy === LocaleUrlStrategy.ShowAllLocales) {
33
+ return hrefWithLocale;
34
+ } else if (
35
+ localeUrlStrategy === LocaleUrlStrategy.HideDefaultLocale &&
36
+ locale !== defaultLocaleValue
37
+ ) {
38
+ return hrefWithLocale;
43
39
  }
44
40
 
45
- return href;
41
+ return href || '#';
46
42
  }, [href, defaultLocaleValue, locale, localeUrlStrategy]);
47
43
 
48
44
  return (
@@ -72,13 +72,10 @@ const addRootLayoutProps = async (componentProps: RootLayoutProps) => {
72
72
  const checkRedisVariables = () => {
73
73
  const requiredVariableValues = [
74
74
  process.env.CACHE_HOST,
75
- process.env.CACHE_PORT
75
+ process.env.CACHE_PORT,
76
+ process.env.CACHE_SECRET
76
77
  ];
77
78
 
78
- if (!settings.usePrettyUrlRoute) {
79
- requiredVariableValues.push(process.env.CACHE_SECRET);
80
- }
81
-
82
79
  if (
83
80
  !requiredVariableValues.every((v) => v) &&
84
81
  process.env.NODE_ENV === 'production'
@@ -4,19 +4,17 @@ import { Resource } from '@opentelemetry/resources';
4
4
  import { SemanticResourceAttributes } from '@opentelemetry/semantic-conventions';
5
5
  import { SimpleSpanProcessor } from '@opentelemetry/sdk-trace-node';
6
6
 
7
- if (process.env.NODE_ENV === 'development') {
8
- const sdk = new NodeSDK({
9
- resource: new Resource({
10
- [SemanticResourceAttributes.SERVICE_NAME]: 'pz-next-app'
11
- }),
12
- spanProcessor: new SimpleSpanProcessor(
13
- new OTLPTraceExporter({
14
- url: `${
15
- process.env.PZ_DASHBOARD_URL ?? 'http://localhost:3005'
16
- }/api/traces`
17
- })
18
- )
19
- });
7
+ const sdk = new NodeSDK({
8
+ resource: new Resource({
9
+ [SemanticResourceAttributes.SERVICE_NAME]: 'pz-next-app'
10
+ }),
11
+ spanProcessor: new SimpleSpanProcessor(
12
+ new OTLPTraceExporter({
13
+ url: `${
14
+ process.env.PZ_DASHBOARD_URL ?? 'http://localhost:3005'
15
+ }/api/traces`
16
+ })
17
+ )
18
+ });
20
19
 
21
- sdk.start();
22
- }
20
+ sdk.start();
package/lib/cache.ts CHANGED
@@ -31,8 +31,6 @@ export const CacheKey = {
31
31
  `category_${pk}_${encodeURIComponent(
32
32
  JSON.stringify(searchParams)
33
33
  )}${hashCacheKey(headers)}`,
34
- Basket: (namespace?: string) => `basket${namespace ? `_${namespace}` : ''}`,
35
- AllBaskets: () => 'all_baskets',
36
34
  CategorySlug: (slug: string) => `category_${slug}`,
37
35
  SpecialPage: (
38
36
  pk: number,
@@ -145,8 +145,7 @@ const withCompleteGpay =
145
145
  logger.info('Redirecting to order success page', {
146
146
  middleware: 'complete-gpay',
147
147
  redirectUrlWithLocale,
148
- ip,
149
- setCookie: request.headers.get('set-cookie')
148
+ ip
150
149
  });
151
150
 
152
151
  // Using POST method while redirecting causes an error,
@@ -145,8 +145,7 @@ const withCompleteMasterpass =
145
145
  logger.info('Redirecting to order success page', {
146
146
  middleware: 'complete-masterpass',
147
147
  redirectUrlWithLocale,
148
- ip,
149
- setCookie: request.headers.get('set-cookie')
148
+ ip
150
149
  });
151
150
 
152
151
  // Using POST method while redirecting causes an error,
@@ -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,15 @@ const withPzDefault =
341
343
  middlewareResult = NextResponse.rewrite(url);
342
344
  }
343
345
 
346
+ const { localeUrlStrategy } =
347
+ Settings.localization;
348
+
349
+ const rootHostname =
350
+ localeUrlStrategy ===
351
+ LocaleUrlStrategy.Subdomain
352
+ ? getRootHostname(process.env.NEXT_PUBLIC_URL)
353
+ : null;
354
+
344
355
  if (
345
356
  !url.pathname.startsWith(`/${currency}/orders`)
346
357
  ) {
@@ -350,6 +361,7 @@ const withPzDefault =
350
361
  ? locale
351
362
  : defaultLocaleValue,
352
363
  {
364
+ domain: rootHostname,
353
365
  sameSite: 'none',
354
366
  secure: true,
355
367
  expires: new Date(
@@ -358,10 +370,12 @@ const withPzDefault =
358
370
  }
359
371
  );
360
372
  }
373
+
361
374
  middlewareResult.cookies.set(
362
375
  'pz-currency',
363
376
  currency,
364
377
  {
378
+ domain: rootHostname,
365
379
  sameSite: 'none',
366
380
  secure: true,
367
381
  expires: new Date(
@@ -410,7 +424,10 @@ const withPzDefault =
410
424
  ).json();
411
425
  middlewareResult.cookies.set(
412
426
  'csrftoken',
413
- csrf_token
427
+ csrf_token,
428
+ {
429
+ domain: rootHostname
430
+ }
414
431
  );
415
432
  }
416
433
  } 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 =
@@ -146,8 +146,7 @@ const withRedirectionPayment =
146
146
  logger.info('Redirecting to order success page', {
147
147
  middleware: 'redirection-payment',
148
148
  redirectUrlWithLocale,
149
- ip,
150
- setCookie: request.headers.get('set-cookie')
149
+ ip
151
150
  });
152
151
 
153
152
  // Using POST method while redirecting causes an error,
@@ -145,8 +145,7 @@ const withSavedCardRedirection =
145
145
  logger.info('Redirecting to order success page', {
146
146
  middleware: 'saved-card-redirection',
147
147
  redirectUrlWithLocale,
148
- ip,
149
- setCookie: request.headers.get('set-cookie')
148
+ ip
150
149
  });
151
150
 
152
151
  // Using POST method while redirecting causes an error,
@@ -145,8 +145,7 @@ const withThreeDRedirection =
145
145
  logger.info('Redirecting to order success page', {
146
146
  middleware: 'three-d-redirection',
147
147
  redirectUrlWithLocale,
148
- ip,
149
- setCookie: request.headers.get('set-cookie')
148
+ ip
150
149
  });
151
150
 
152
151
  // Using POST method while redirecting causes an error,
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.89.0-rc.1",
4
+ "version": "1.89.0-snapshot-ZERO-3343-20250425070312",
5
5
  "private": false,
6
6
  "license": "MIT",
7
7
  "bin": {
@@ -20,7 +20,7 @@
20
20
  "@opentelemetry/sdk-trace-node": "1.19.0",
21
21
  "@opentelemetry/semantic-conventions": "1.19.0",
22
22
  "@reduxjs/toolkit": "1.9.7",
23
- "@neshca/cache-handler": "1.9.0",
23
+ "@neshca/cache-handler": "1.5.1",
24
24
  "@sentry/nextjs": "9.5.0",
25
25
  "cross-spawn": "7.0.3",
26
26
  "generic-pool": "3.9.0",
@@ -31,7 +31,7 @@
31
31
  "set-cookie-parser": "2.6.0"
32
32
  },
33
33
  "devDependencies": {
34
- "@akinon/eslint-plugin-projectzero": "1.89.0-rc.1",
34
+ "@akinon/eslint-plugin-projectzero": "1.89.0-snapshot-ZERO-3343-20250425070312",
35
35
  "@types/react-redux": "7.1.30",
36
36
  "@types/set-cookie-parser": "2.4.7",
37
37
  "@typescript-eslint/eslint-plugin": "6.7.4",
@@ -114,7 +114,6 @@ export interface Order {
114
114
  pk: number;
115
115
  name: string;
116
116
  slug: string;
117
- logo: string;
118
117
  [key: string]: any;
119
118
  };
120
119
  }
@@ -43,12 +43,12 @@ const appFetch = async <T>({
43
43
  const requestURL = `${decodeURIComponent(commerceUrl)}${url}`;
44
44
 
45
45
  init.headers = {
46
- cookie: nextCookies.toString(),
47
46
  ...(init.headers ?? {}),
48
47
  ...(ServerVariables.globalHeaders ?? {}),
49
48
  'Accept-Language': currentLocale.apiValue,
50
49
  'x-currency': currency,
51
- 'x-forwarded-for': ip
50
+ 'x-forwarded-for': ip,
51
+ cookie: nextCookies.toString()
52
52
  };
53
53
 
54
54
  init.next = {
@@ -0,0 +1,29 @@
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
+ const isLocale = Settings.localization.locales.some(
15
+ (locale) => locale.value === firstPart
16
+ );
17
+
18
+ if (isLocale && parts.length > 2) {
19
+ const rootDomain = parts.slice(-2).join('.');
20
+ return `.${rootDomain}`;
21
+ } else if (parts.length > 2) {
22
+ return null;
23
+ }
24
+
25
+ return `.${hostname}`;
26
+ } catch {
27
+ return null;
28
+ }
29
+ }
package/utils/index.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import settings from 'settings';
2
2
  import { LocaleUrlStrategy } from '../localization';
3
- import { CDNOptions, ClientRequestOptions } from '../types';
3
+ import { CDNOptions, ClientRequestOptions, Locale } from '../types';
4
4
 
5
5
  export * from './get-currency';
6
6
  export * from './menu-generator';
@@ -155,15 +155,14 @@ export function buildCDNUrl(url: string, config?: CDNOptions) {
155
155
  const { locales, localeUrlStrategy, defaultLocaleValue } =
156
156
  settings.localization;
157
157
 
158
+ const isLocaleExcluded = (locale: Locale) =>
159
+ ![LocaleUrlStrategy.ShowAllLocales, LocaleUrlStrategy.Subdomain].includes(
160
+ localeUrlStrategy
161
+ ) && locale.value !== defaultLocaleValue;
162
+
158
163
  export const urlLocaleMatcherRegex = new RegExp(
159
- `^/(${settings.localization.locales
160
- .filter((l) =>
161
- ![LocaleUrlStrategy.ShowAllLocales, LocaleUrlStrategy.Subdomain].includes(
162
- settings.localization.localeUrlStrategy
163
- )
164
- ? l.value !== settings.localization.defaultLocaleValue
165
- : l
166
- )
164
+ `^/(${locales
165
+ .filter((l) => !isLocaleExcluded(l))
167
166
  .map((l) => l.value)
168
167
  .join('|')})(?=/|$)`
169
168
  );
@@ -1,72 +0,0 @@
1
- import { Cache, CacheKey } from '../../lib/cache';
2
- import { basket } from '../../data/urls';
3
- import { Basket } from '../../types';
4
- import appFetch from '../../utils/app-fetch';
5
- import { ServerVariables } from '../../utils/server-variables';
6
- import logger from '../../utils/log';
7
-
8
- type GetBasketParams = {
9
- locale?: string;
10
- currency?: string;
11
- namespace?: string;
12
- };
13
-
14
- const getBasketDataHandler = ({
15
- locale,
16
- currency,
17
- namespace
18
- }: GetBasketParams) => {
19
- return async function () {
20
- try {
21
- const url = namespace
22
- ? basket.getBasketDetail(namespace)
23
- : basket.getBasket;
24
-
25
- const basketData = await appFetch<{ basket: Basket }>({
26
- url,
27
- locale,
28
- currency,
29
- init: {
30
- headers: {
31
- Accept: 'application/json',
32
- 'Content-Type': 'application/json'
33
- }
34
- }
35
- });
36
-
37
- if (!basketData?.basket) {
38
- logger.warn('Basket data is undefined', {
39
- handler: 'getBasketDataHandler',
40
- namespace
41
- });
42
- }
43
-
44
- return basketData;
45
- } catch (error) {
46
- logger.error('Error fetching basket data', {
47
- handler: 'getBasketDataHandler',
48
- error,
49
- namespace
50
- });
51
- throw error;
52
- }
53
- };
54
- };
55
-
56
- export const getBasketData = async ({
57
- locale = ServerVariables.locale,
58
- currency = ServerVariables.currency,
59
- namespace
60
- }: GetBasketParams = {}) => {
61
- return Cache.wrap(
62
- CacheKey.Basket(namespace),
63
- locale,
64
- getBasketDataHandler({ locale, currency, namespace }),
65
- {
66
- expire: 0,
67
- cache: false
68
- }
69
- );
70
- };
71
-
72
-