@akinon/next 1.93.0 → 1.95.0-rc.54

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 (52) hide show
  1. package/CHANGELOG.md +1252 -24
  2. package/__tests__/next-config.test.ts +1 -10
  3. package/__tests__/redirect.test.ts +319 -0
  4. package/api/image-proxy.ts +75 -0
  5. package/api/similar-product-list.ts +84 -0
  6. package/api/similar-products.ts +120 -0
  7. package/components/accordion.tsx +20 -5
  8. package/components/file-input.tsx +65 -3
  9. package/components/input.tsx +2 -0
  10. package/components/link.tsx +16 -12
  11. package/components/modal.tsx +32 -16
  12. package/components/plugin-module.tsx +35 -3
  13. package/components/selected-payment-option-view.tsx +11 -0
  14. package/data/client/checkout.ts +25 -4
  15. package/data/server/basket.ts +72 -0
  16. package/data/server/category.ts +48 -28
  17. package/data/server/flatpage.ts +16 -12
  18. package/data/server/landingpage.ts +16 -12
  19. package/data/server/list.ts +23 -13
  20. package/data/server/product.ts +66 -39
  21. package/data/server/special-page.ts +16 -12
  22. package/data/urls.ts +7 -2
  23. package/hocs/server/with-segment-defaults.tsx +5 -2
  24. package/hooks/use-localization.ts +2 -3
  25. package/hooks/use-loyalty-availability.ts +21 -0
  26. package/instrumentation/node.ts +15 -13
  27. package/jest.config.js +7 -1
  28. package/lib/cache.ts +2 -0
  29. package/middlewares/checkout-provider.ts +1 -1
  30. package/middlewares/complete-gpay.ts +6 -2
  31. package/middlewares/complete-masterpass.ts +7 -2
  32. package/middlewares/default.ts +232 -183
  33. package/middlewares/index.ts +3 -1
  34. package/middlewares/locale.ts +9 -1
  35. package/middlewares/redirection-payment.ts +6 -2
  36. package/middlewares/saved-card-redirection.ts +7 -2
  37. package/middlewares/three-d-redirection.ts +7 -2
  38. package/middlewares/url-redirection.ts +9 -15
  39. package/middlewares/wallet-complete-redirection.ts +203 -0
  40. package/package.json +3 -3
  41. package/plugins.d.ts +10 -0
  42. package/plugins.js +4 -1
  43. package/redux/middlewares/checkout.ts +15 -2
  44. package/redux/reducers/checkout.ts +9 -1
  45. package/sentry/index.ts +54 -17
  46. package/types/commerce/order.ts +1 -0
  47. package/types/index.ts +42 -1
  48. package/utils/app-fetch.ts +7 -2
  49. package/utils/index.ts +34 -10
  50. package/utils/redirect-ignore.ts +35 -0
  51. package/utils/redirect.ts +31 -6
  52. package/with-pz-config.js +1 -5
@@ -0,0 +1,203 @@
1
+ import { NextFetchEvent, NextMiddleware, NextResponse } from 'next/server';
2
+ import Settings from 'settings';
3
+ import { Buffer } from 'buffer';
4
+ import logger from '../utils/log';
5
+ import { getUrlPathWithLocale } from '../utils/localization';
6
+ import { PzNextRequest } from '.';
7
+
8
+ const streamToString = async (stream: ReadableStream<Uint8Array> | null) => {
9
+ if (stream) {
10
+ const chunks = [];
11
+ let result = '';
12
+
13
+ try {
14
+ for await (const chunk of stream as any) {
15
+ chunks.push(Buffer.from(chunk));
16
+ }
17
+
18
+ result = Buffer.concat(chunks).toString('utf-8');
19
+ } catch (error) {
20
+ logger.error('Error while reading body stream', {
21
+ middleware: 'wallet-complete-redirection',
22
+ error
23
+ });
24
+ }
25
+
26
+ return result;
27
+ }
28
+ return null;
29
+ };
30
+
31
+ const withWalletCompleteRedirection =
32
+ (middleware: NextMiddleware) =>
33
+ async (req: PzNextRequest, event: NextFetchEvent) => {
34
+ const url = req.nextUrl.clone();
35
+ const ip = req.headers.get('x-forwarded-for') ?? '';
36
+ const sessionId = req.cookies.get('osessionid');
37
+
38
+ if (url.search.indexOf('WalletCompletePage') === -1) {
39
+ return middleware(req, event);
40
+ }
41
+
42
+ const requestUrl = `${Settings.commerceUrl}/orders/checkout/${url.search}`;
43
+ const requestHeaders = {
44
+ 'X-Requested-With': 'XMLHttpRequest',
45
+ 'Content-Type': 'application/x-www-form-urlencoded',
46
+ Cookie: req.headers.get('cookie') ?? '',
47
+ 'x-currency': req.cookies.get('pz-currency')?.value ?? '',
48
+ 'x-forwarded-for': ip
49
+ };
50
+
51
+ try {
52
+ const existingBody = await streamToString(req.body);
53
+ const queryParams = new URLSearchParams(url.search);
54
+ const bodyParams = new URLSearchParams();
55
+
56
+ if (existingBody) {
57
+ try {
58
+ const existingParams = new URLSearchParams(existingBody);
59
+
60
+ existingParams.forEach((value, key) => {
61
+ bodyParams.append(key, value);
62
+ });
63
+ } catch {
64
+ logger.error('Error parsing existing body as URL-encoded data', {
65
+ middleware: 'wallet-complete-redirection',
66
+ existingBody,
67
+ ip
68
+ });
69
+ }
70
+ }
71
+
72
+ queryParams.forEach((value, key) => {
73
+ bodyParams.append(key, value);
74
+ });
75
+
76
+ const body = bodyParams.toString();
77
+
78
+ if (!sessionId) {
79
+ logger.warn(
80
+ 'Make sure that the SESSION_COOKIE_SAMESITE environment variable is set to None in Commerce.',
81
+ {
82
+ middleware: 'wallet-complete-redirection',
83
+ ip
84
+ }
85
+ );
86
+
87
+ return NextResponse.redirect(
88
+ `${url.origin}${getUrlPathWithLocale(
89
+ '/orders/checkout/',
90
+ req.cookies.get('pz-locale')?.value
91
+ )}`,
92
+ 303
93
+ );
94
+ }
95
+
96
+ const request = await fetch(requestUrl, {
97
+ method: 'POST',
98
+ headers: requestHeaders,
99
+ body
100
+ });
101
+
102
+ logger.info('Complete wallet payment request', {
103
+ requestUrl,
104
+ status: request.status,
105
+ requestHeaders,
106
+ ip
107
+ });
108
+
109
+ const response = await request.json();
110
+
111
+ const { context_list: contextList, errors } = response;
112
+ const redirectionContext = contextList?.find(
113
+ (context) => context.page_context?.redirect_url
114
+ );
115
+ const redirectUrl = redirectionContext?.page_context?.redirect_url;
116
+
117
+ if (errors && Object.keys(errors).length) {
118
+ logger.error('Error while completing wallet payment', {
119
+ middleware: 'wallet-complete-redirection',
120
+ errors,
121
+ requestHeaders,
122
+ ip
123
+ });
124
+
125
+ return NextResponse.redirect(
126
+ `${url.origin}${getUrlPathWithLocale(
127
+ '/orders/checkout/',
128
+ req.cookies.get('pz-locale')?.value
129
+ )}`,
130
+ {
131
+ status: 303,
132
+ headers: {
133
+ 'Set-Cookie': `pz-pos-error=${JSON.stringify(errors)}; path=/;`
134
+ }
135
+ }
136
+ );
137
+ }
138
+
139
+ logger.info('Order success page context list', {
140
+ middleware: 'wallet-complete-redirection',
141
+ contextList,
142
+ ip
143
+ });
144
+
145
+ if (!redirectUrl) {
146
+ logger.warn(
147
+ 'No redirection url for order success page found in page_context. Redirecting to checkout page.',
148
+ {
149
+ middleware: 'wallet-complete-redirection',
150
+ requestHeaders,
151
+ response: JSON.stringify(response),
152
+ ip
153
+ }
154
+ );
155
+
156
+ const redirectUrlWithLocale = `${url.origin}${getUrlPathWithLocale(
157
+ '/orders/checkout/',
158
+ req.cookies.get('pz-locale')?.value
159
+ )}`;
160
+
161
+ return NextResponse.redirect(redirectUrlWithLocale, 303);
162
+ }
163
+
164
+ const redirectUrlWithLocale = `${url.origin}${getUrlPathWithLocale(
165
+ redirectUrl,
166
+ req.cookies.get('pz-locale')?.value
167
+ )}`;
168
+
169
+ logger.info('Redirecting to order success page', {
170
+ middleware: 'wallet-complete-redirection',
171
+ redirectUrlWithLocale,
172
+ ip
173
+ });
174
+
175
+ // Using POST method while redirecting causes an error,
176
+ // So we use 303 status code to change the method to GET
177
+ const nextResponse = NextResponse.redirect(redirectUrlWithLocale, 303);
178
+
179
+ nextResponse.headers.set(
180
+ 'Set-Cookie',
181
+ request.headers.get('set-cookie') ?? ''
182
+ );
183
+
184
+ return nextResponse;
185
+ } catch (error) {
186
+ logger.error('Error while completing wallet payment', {
187
+ middleware: 'wallet-complete-redirection',
188
+ error,
189
+ requestHeaders,
190
+ ip
191
+ });
192
+
193
+ return NextResponse.redirect(
194
+ `${url.origin}${getUrlPathWithLocale(
195
+ '/orders/checkout/',
196
+ req.cookies.get('pz-locale')?.value
197
+ )}`,
198
+ 303
199
+ );
200
+ }
201
+ };
202
+
203
+ export default withWalletCompleteRedirection;
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.93.0",
4
+ "version": "1.95.0-rc.54",
5
5
  "private": false,
6
6
  "license": "MIT",
7
7
  "bin": {
@@ -17,13 +17,13 @@
17
17
  "test": "jest"
18
18
  },
19
19
  "dependencies": {
20
- "@neshca/cache-handler": "1.5.1",
21
20
  "@opentelemetry/exporter-trace-otlp-http": "0.46.0",
22
21
  "@opentelemetry/resources": "1.19.0",
23
22
  "@opentelemetry/sdk-node": "0.46.0",
24
23
  "@opentelemetry/sdk-trace-node": "1.19.0",
25
24
  "@opentelemetry/semantic-conventions": "1.19.0",
26
25
  "@reduxjs/toolkit": "1.9.7",
26
+ "@neshca/cache-handler": "1.9.0",
27
27
  "@sentry/nextjs": "9.5.0",
28
28
  "cross-spawn": "7.0.3",
29
29
  "generic-pool": "3.9.0",
@@ -34,7 +34,7 @@
34
34
  "set-cookie-parser": "2.6.0"
35
35
  },
36
36
  "devDependencies": {
37
- "@akinon/eslint-plugin-projectzero": "1.93.0",
37
+ "@akinon/eslint-plugin-projectzero": "1.95.0-rc.54",
38
38
  "@babel/core": "7.26.10",
39
39
  "@babel/preset-env": "7.26.9",
40
40
  "@babel/preset-typescript": "7.27.0",
package/plugins.d.ts CHANGED
@@ -36,3 +36,13 @@ declare module '@akinon/pz-iyzico-saved-card' {
36
36
  }
37
37
 
38
38
  declare module '@akinon/pz-apple-pay' {}
39
+
40
+ declare module '@akinon/pz-flow-payment' {}
41
+
42
+ declare module '@akinon/pz-similar-products' {
43
+ export const SimilarProductsModal: any;
44
+ export const SimilarProductsFilterSidebar: any;
45
+ export const SimilarProductsResultsGrid: any;
46
+ export const SimilarProductsPlugin: any;
47
+ export const SimilarProductsButtonPlugin: any;
48
+ }
package/plugins.js CHANGED
@@ -15,5 +15,8 @@ module.exports = [
15
15
  'pz-saved-card',
16
16
  'pz-tabby-extension',
17
17
  'pz-apple-pay',
18
- 'pz-tamara-extension'
18
+ 'pz-tamara-extension',
19
+ 'pz-hepsipay',
20
+ 'pz-flow-payment',
21
+ 'pz-similar-products'
19
22
  ];
@@ -20,7 +20,8 @@ import {
20
20
  setShippingOptions,
21
21
  setHepsipayAvailability,
22
22
  setWalletPaymentData,
23
- setPayOnDeliveryOtpModalActive
23
+ setPayOnDeliveryOtpModalActive,
24
+ setUnavailablePaymentOptions
24
25
  } from '../../redux/reducers/checkout';
25
26
  import { RootState, TypedDispatch } from 'redux/store';
26
27
  import { checkoutApi } from '../../data/client/checkout';
@@ -50,7 +51,11 @@ export const errorMiddleware: Middleware = ({ dispatch }: MiddlewareParams) => {
50
51
  const result: CheckoutResult = next(action);
51
52
  const errors = result?.payload?.errors;
52
53
 
53
- if (errors) {
54
+ if (
55
+ !!errors &&
56
+ ((typeof errors === 'object' && Object.keys(errors).length > 0) ||
57
+ (Array.isArray(errors) && errors.length > 0))
58
+ ) {
54
59
  dispatch(setErrors(errors));
55
60
  }
56
61
 
@@ -176,6 +181,14 @@ export const contextListMiddleware: Middleware = ({
176
181
  dispatch(setPaymentOptions(context.page_context.payment_options));
177
182
  }
178
183
 
184
+ if (context.page_context.unavailable_options) {
185
+ dispatch(
186
+ setUnavailablePaymentOptions(
187
+ context.page_context.unavailable_options
188
+ )
189
+ );
190
+ }
191
+
179
192
  if (context.page_context.credit_payment_options) {
180
193
  dispatch(
181
194
  setCreditPaymentOptions(context.page_context.credit_payment_options)
@@ -40,6 +40,7 @@ export interface CheckoutState {
40
40
  shippingOptions: ShippingOption[];
41
41
  dataSourceShippingOptions: DataSource[];
42
42
  paymentOptions: PaymentOption[];
43
+ unavailablePaymentOptions: PaymentOption[];
43
44
  creditPaymentOptions: CheckoutCreditPaymentOption[];
44
45
  selectedCreditPaymentPk: number;
45
46
  paymentChoices: PaymentChoice[];
@@ -60,6 +61,8 @@ export interface CheckoutState {
60
61
  countryCode: string;
61
62
  currencyCode: string;
62
63
  version: string;
64
+ public_key: string;
65
+ [key: string]: any;
63
66
  };
64
67
  detail: {
65
68
  label: string;
@@ -94,6 +97,7 @@ const initialState: CheckoutState = {
94
97
  shippingOptions: [],
95
98
  dataSourceShippingOptions: [],
96
99
  paymentOptions: [],
100
+ unavailablePaymentOptions: [],
97
101
  creditPaymentOptions: [],
98
102
  selectedCreditPaymentPk: null,
99
103
  paymentChoices: [],
@@ -157,6 +161,9 @@ const checkoutSlice = createSlice({
157
161
  setPaymentOptions(state, { payload }) {
158
162
  state.paymentOptions = payload;
159
163
  },
164
+ setUnavailablePaymentOptions(state, { payload }) {
165
+ state.unavailablePaymentOptions = payload;
166
+ },
160
167
  setPaymentChoices(state, { payload }) {
161
168
  state.paymentChoices = payload;
162
169
  },
@@ -218,9 +225,10 @@ export const {
218
225
  setShippingOptions,
219
226
  setDataSourceShippingOptions,
220
227
  setPaymentOptions,
228
+ setUnavailablePaymentOptions,
229
+ setPaymentChoices,
221
230
  setCreditPaymentOptions,
222
231
  setSelectedCreditPaymentPk,
223
- setPaymentChoices,
224
232
  setCardType,
225
233
  setInstallmentOptions,
226
234
  setBankAccounts,
package/sentry/index.ts CHANGED
@@ -13,36 +13,73 @@ const ALLOWED_CLIENT_LOG_TYPES: ClientLogType[] = [
13
13
  ClientLogType.CHECKOUT
14
14
  ];
15
15
 
16
+ const isNetworkError = (exception: unknown): boolean => {
17
+ if (!(exception instanceof Error)) return false;
18
+
19
+ const networkErrorPatterns = [
20
+ 'networkerror',
21
+ 'failed to fetch',
22
+ 'network request failed',
23
+ 'network error',
24
+ 'loading chunk',
25
+ 'chunk load failed'
26
+ ];
27
+
28
+ if (exception.name === 'NetworkError') return true;
29
+
30
+ if (exception.name === 'TypeError') {
31
+ return networkErrorPatterns.some((pattern) =>
32
+ exception.message.toLowerCase().includes(pattern)
33
+ );
34
+ }
35
+
36
+ return networkErrorPatterns.some((pattern) =>
37
+ exception.message.toLowerCase().includes(pattern)
38
+ );
39
+ };
40
+
16
41
  export const initSentry = (
17
42
  type: 'Server' | 'Client' | 'Edge',
18
43
  options: Sentry.BrowserOptions | Sentry.NodeOptions | Sentry.EdgeOptions = {}
19
44
  ) => {
20
- // TODO: Handle options with ESLint rules
45
+ // TODO: Remove Zero Project DSN
21
46
 
22
- Sentry.init({
47
+ const baseConfig = {
23
48
  dsn:
24
- options.dsn ||
25
49
  SENTRY_DSN ||
50
+ options.dsn ||
26
51
  'https://d8558ef8997543deacf376c7d8d7cf4b@o64293.ingest.sentry.io/4504338423742464',
27
52
  initialScope: {
28
53
  tags: {
29
54
  APP_TYPE: 'ProjectZeroNext',
30
- TYPE: type
55
+ TYPE: type,
56
+ ...((options.initialScope as any)?.tags || {})
31
57
  }
32
58
  },
33
59
  tracesSampleRate: 0,
34
- integrations: [],
35
- beforeSend: (event, hint) => {
36
- if (
37
- type === 'Client' &&
38
- !ALLOWED_CLIENT_LOG_TYPES.includes(
39
- event.tags?.LOG_TYPE as ClientLogType
40
- )
41
- ) {
42
- return null;
43
- }
60
+ integrations: []
61
+ };
44
62
 
45
- return event;
46
- }
47
- });
63
+ if (type === 'Server' || type === 'Edge') {
64
+ Sentry.init(baseConfig);
65
+ } else if (type === 'Client') {
66
+ Sentry.init({
67
+ ...baseConfig,
68
+ beforeSend: (event, hint) => {
69
+ if (
70
+ !ALLOWED_CLIENT_LOG_TYPES.includes(
71
+ event.tags?.LOG_TYPE as ClientLogType
72
+ )
73
+ ) {
74
+ return null;
75
+ }
76
+
77
+ if (isNetworkError(hint?.originalException)) {
78
+ return null;
79
+ }
80
+
81
+ return event;
82
+ }
83
+ });
84
+ }
48
85
  };
@@ -114,6 +114,7 @@ export interface Order {
114
114
  pk: number;
115
115
  name: string;
116
116
  slug: string;
117
+ logo: string;
117
118
  [key: string]: any;
118
119
  };
119
120
  }
package/types/index.ts CHANGED
@@ -83,6 +83,12 @@ export interface Settings {
83
83
  };
84
84
  usePrettyUrlRoute?: boolean;
85
85
  commerceUrl: string;
86
+ /**
87
+ * This option allows you to track Sentry events on the client side, in addition to server and edge environments.
88
+ *
89
+ * It overrides process.env.NEXT_PUBLIC_SENTRY_DSN and process.env.SENTRY_DSN.
90
+ */
91
+ sentryDsn?: string;
86
92
  redis: {
87
93
  defaultExpirationTime: number;
88
94
  };
@@ -218,6 +224,14 @@ export interface CacheOptions {
218
224
  useProxy?: boolean;
219
225
  }
220
226
 
227
+ export interface SetCookieOptions {
228
+ expires?: number; // days
229
+ path?: string;
230
+ domain?: string;
231
+ secure?: boolean;
232
+ sameSite?: 'strict' | 'lax' | 'none';
233
+ }
234
+
221
235
  export interface ClientRequestOptions {
222
236
  useTrailingSlash?: boolean;
223
237
  useFormData?: boolean;
@@ -283,7 +297,13 @@ export interface ButtonProps
283
297
  target?: '_blank' | '_self' | '_parent' | '_top';
284
298
  }
285
299
 
286
- export type FileInputProps = React.HTMLProps<HTMLInputElement>;
300
+ export interface FileInputProps extends React.HTMLProps<HTMLInputElement> {
301
+ fileClassName?: string;
302
+ fileNameWrapperClassName?: string;
303
+ fileInputClassName?: string;
304
+ onChange?: (event: React.ChangeEvent<HTMLInputElement>) => void;
305
+ buttonClassName?: string;
306
+ }
287
307
 
288
308
  export interface PriceProps {
289
309
  currencyCode?: string;
@@ -304,15 +324,19 @@ export interface InputProps extends React.HTMLProps<HTMLInputElement> {
304
324
 
305
325
  export interface AccordionProps {
306
326
  isCollapse?: boolean;
327
+ collapseClassName?: string;
307
328
  title?: string;
308
329
  subTitle?: string;
309
330
  icons?: string[];
310
331
  iconSize?: number;
311
332
  iconColor?: string;
312
333
  children?: ReactNode;
334
+ headerClassName?: string;
313
335
  className?: string;
314
336
  titleClassName?: string;
337
+ subTitleClassName?: string;
315
338
  dataTestId?: string;
339
+ contentClassName?: string;
316
340
  }
317
341
 
318
342
  export interface PluginModuleComponentProps {
@@ -337,3 +361,20 @@ export interface PaginationProps {
337
361
  direction?: 'next' | 'prev';
338
362
  isLoading?: boolean;
339
363
  }
364
+
365
+ export interface ModalProps {
366
+ portalId: string;
367
+ children?: React.ReactNode;
368
+ open?: boolean;
369
+ setOpen?: (open: boolean) => void;
370
+ title?: React.ReactNode;
371
+ showCloseButton?: React.ReactNode;
372
+ className?: string;
373
+ overlayClassName?: string;
374
+ headerWrapperClassName?: string;
375
+ titleClassName?: string;
376
+ closeButtonClassName?: string;
377
+ iconName?: string;
378
+ iconSize?: number;
379
+ iconClassName?: string;
380
+ }
@@ -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(),
46
47
  ...(init.headers ?? {}),
47
48
  ...(ServerVariables.globalHeaders ?? {}),
48
49
  'Accept-Language': currentLocale.apiValue,
49
50
  'x-currency': currency,
50
- 'x-forwarded-for': ip,
51
- cookie: nextCookies.toString()
51
+ 'x-forwarded-for': ip
52
52
  };
53
53
 
54
54
  init.next = {
@@ -60,6 +60,11 @@ const appFetch = async <T>({
60
60
  status = req.status;
61
61
  logger.debug(`FETCH END ${url}`, { status: req.status, ip });
62
62
 
63
+ if (!req.ok) {
64
+ const errorMessage = `HTTP ${req.status}: ${req.statusText}`;
65
+ throw new Error(errorMessage);
66
+ }
67
+
63
68
  if (responseType === FetchResponseType.JSON) {
64
69
  response = (await req.json()) as T;
65
70
  } else {
package/utils/index.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import settings from 'settings';
2
2
  import { LocaleUrlStrategy } from '../localization';
3
- import { CDNOptions, ClientRequestOptions } from '../types';
3
+ import { CDNOptions, ClientRequestOptions, SetCookieOptions } from '../types';
4
+ import getRootHostname from './get-root-hostname';
4
5
 
5
6
  export * from './get-currency';
6
7
  export * from './menu-generator';
@@ -20,14 +21,40 @@ export function getCookie(name: string) {
20
21
  }
21
22
  }
22
23
 
23
- export function setCookie(name: string, val: string) {
24
- const date = new Date();
25
- const value = val;
24
+ export function setCookie(
25
+ name: string,
26
+ value: string,
27
+ options: SetCookieOptions = {}
28
+ ) {
29
+ const cookieParts = [`${name}=${value}`];
30
+
31
+ if (options.expires) {
32
+ const date = new Date();
33
+ date.setTime(date.getTime() + options.expires * 24 * 60 * 60 * 1000);
34
+ cookieParts.push(`expires=${date.toUTCString()}`);
35
+ }
26
36
 
27
- date.setTime(date.getTime() + 7 * 24 * 60 * 60 * 1000);
37
+ cookieParts.push(`path=${options.path ?? '/'}`);
28
38
 
29
- document.cookie =
30
- name + '=' + value + '; expires=' + date.toUTCString() + '; path=/';
39
+ if (options.secure) {
40
+ cookieParts.push('secure');
41
+ }
42
+
43
+ if (options.sameSite) {
44
+ cookieParts.push(`sameSite=${options.sameSite}`);
45
+ }
46
+
47
+ const domain =
48
+ options.domain ??
49
+ (settings.localization.localeUrlStrategy === LocaleUrlStrategy.Subdomain
50
+ ? getRootHostname(document.location.href)
51
+ : null);
52
+
53
+ if (domain) {
54
+ cookieParts.push(`domain=${domain}`);
55
+ }
56
+
57
+ document.cookie = cookieParts.join('; ');
31
58
  }
32
59
 
33
60
  export function removeCookie(name: string) {
@@ -152,9 +179,6 @@ export function buildCDNUrl(url: string, config?: CDNOptions) {
152
179
  return `${rootWithoutOptions}${options}${fileExtension}`;
153
180
  }
154
181
 
155
- const { locales, localeUrlStrategy, defaultLocaleValue } =
156
- settings.localization;
157
-
158
182
  export const urlLocaleMatcherRegex = new RegExp(
159
183
  `^/(${settings.localization.locales
160
184
  .filter((l) =>
@@ -0,0 +1,35 @@
1
+ import settings from 'settings';
2
+ import { getUrlPathWithLocale } from './localization';
3
+
4
+ type IgnorePath = string | RegExp;
5
+
6
+ const defaultIgnoreList: string[] = [];
7
+
8
+ const extraIgnores: IgnorePath[] = Array.isArray(
9
+ settings.commerceRedirectionIgnoreList
10
+ )
11
+ ? settings.commerceRedirectionIgnoreList.map((path) => {
12
+ if (path === '/users/reset') {
13
+ return /^\/users\/reset\/[^/]+\/[^/]+\/$/;
14
+ }
15
+ return path;
16
+ })
17
+ : [];
18
+
19
+ export function shouldIgnoreRedirect(
20
+ pathname: string,
21
+ locale: string
22
+ ): boolean {
23
+ if (!pathname) return false;
24
+
25
+ const rawIgnoreList: IgnorePath[] = [...defaultIgnoreList, ...extraIgnores];
26
+
27
+ return rawIgnoreList.some((ignorePath) => {
28
+ if (ignorePath instanceof RegExp) {
29
+ return ignorePath.test(pathname);
30
+ }
31
+
32
+ const localized = getUrlPathWithLocale(ignorePath, locale);
33
+ return localized === pathname;
34
+ });
35
+ }