@akinon/next 2.0.0-beta.2 → 2.0.0-beta.20

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 (189) hide show
  1. package/.eslintrc.js +12 -0
  2. package/CHANGELOG.md +377 -7
  3. package/__tests__/next-config.test.ts +83 -0
  4. package/__tests__/tsconfig.json +23 -0
  5. package/api/auth.ts +133 -44
  6. package/api/barcode-search.ts +59 -0
  7. package/api/cache.ts +41 -5
  8. package/api/client.ts +21 -4
  9. package/api/form.ts +85 -0
  10. package/api/image-proxy.ts +75 -0
  11. package/api/product-categories.ts +53 -0
  12. package/api/similar-product-list.ts +63 -0
  13. package/api/similar-products.ts +111 -0
  14. package/api/virtual-try-on.ts +382 -0
  15. package/assets/styles/index.scss +84 -0
  16. package/babel.config.js +6 -0
  17. package/bin/pz-generate-routes.js +115 -0
  18. package/bin/pz-prebuild.js +1 -0
  19. package/bin/pz-predev.js +1 -0
  20. package/bin/pz-run-tests.js +99 -0
  21. package/bin/run-prebuild-tests.js +46 -0
  22. package/components/accordion.tsx +20 -5
  23. package/components/button.tsx +51 -36
  24. package/components/client-root.tsx +138 -2
  25. package/components/file-input.tsx +65 -3
  26. package/components/index.ts +1 -0
  27. package/components/input.tsx +1 -1
  28. package/components/link.tsx +46 -16
  29. package/components/logger-popup.tsx +213 -0
  30. package/components/modal.tsx +32 -16
  31. package/components/plugin-module.tsx +62 -3
  32. package/components/price.tsx +2 -2
  33. package/components/select.tsx +1 -1
  34. package/components/selected-payment-option-view.tsx +21 -0
  35. package/components/theme-editor/blocks/accordion-block.tsx +136 -0
  36. package/components/theme-editor/blocks/block-renderer-registry.tsx +77 -0
  37. package/components/theme-editor/blocks/button-block.tsx +593 -0
  38. package/components/theme-editor/blocks/counter-block.tsx +348 -0
  39. package/components/theme-editor/blocks/divider-block.tsx +20 -0
  40. package/components/theme-editor/blocks/embed-block.tsx +208 -0
  41. package/components/theme-editor/blocks/group-block.tsx +116 -0
  42. package/components/theme-editor/blocks/hotspot-block.tsx +147 -0
  43. package/components/theme-editor/blocks/icon-block.tsx +230 -0
  44. package/components/theme-editor/blocks/image-block.tsx +137 -0
  45. package/components/theme-editor/blocks/image-gallery-block.tsx +269 -0
  46. package/components/theme-editor/blocks/input-block.tsx +123 -0
  47. package/components/theme-editor/blocks/link-block.tsx +216 -0
  48. package/components/theme-editor/blocks/lottie-block.tsx +325 -0
  49. package/components/theme-editor/blocks/map-block.tsx +89 -0
  50. package/components/theme-editor/blocks/slider-block.tsx +595 -0
  51. package/components/theme-editor/blocks/tab-block.tsx +10 -0
  52. package/components/theme-editor/blocks/text-block.tsx +52 -0
  53. package/components/theme-editor/blocks/video-block.tsx +122 -0
  54. package/components/theme-editor/components/action-toolbar.tsx +305 -0
  55. package/components/theme-editor/components/designer-overlay.tsx +74 -0
  56. package/components/theme-editor/components/with-designer-features.tsx +142 -0
  57. package/components/theme-editor/dynamic-font-loader.tsx +79 -0
  58. package/components/theme-editor/hooks/use-designer-features.tsx +100 -0
  59. package/components/theme-editor/hooks/use-external-designer.tsx +95 -0
  60. package/components/theme-editor/hooks/use-native-widget-data.ts +188 -0
  61. package/components/theme-editor/hooks/use-visibility-context.ts +27 -0
  62. package/components/theme-editor/placeholder-registry.ts +31 -0
  63. package/components/theme-editor/sections/before-after-section.tsx +245 -0
  64. package/components/theme-editor/sections/contact-form-section.tsx +563 -0
  65. package/components/theme-editor/sections/countdown-campaign-banner-section.tsx +433 -0
  66. package/components/theme-editor/sections/coupon-banner-section.tsx +710 -0
  67. package/components/theme-editor/sections/divider-section.tsx +62 -0
  68. package/components/theme-editor/sections/featured-product-spotlight-section.tsx +507 -0
  69. package/components/theme-editor/sections/find-in-store-section.tsx +1995 -0
  70. package/components/theme-editor/sections/hover-showcase-section.tsx +326 -0
  71. package/components/theme-editor/sections/image-hotspot-section.tsx +142 -0
  72. package/components/theme-editor/sections/installment-options-section.tsx +1065 -0
  73. package/components/theme-editor/sections/notification-banner-section.tsx +173 -0
  74. package/components/theme-editor/sections/order-tracking-lookup-section.tsx +1379 -0
  75. package/components/theme-editor/sections/posts-slider-section.tsx +472 -0
  76. package/components/theme-editor/sections/pre-order-launch-banner-section.tsx +663 -0
  77. package/components/theme-editor/sections/section-renderer-registry.tsx +89 -0
  78. package/components/theme-editor/sections/section-wrapper.tsx +135 -0
  79. package/components/theme-editor/sections/shipping-threshold-progress-section.tsx +586 -0
  80. package/components/theme-editor/sections/stats-counter-section.tsx +486 -0
  81. package/components/theme-editor/sections/tabs-section.tsx +578 -0
  82. package/components/theme-editor/theme-block.tsx +102 -0
  83. package/components/theme-editor/theme-placeholder-client.tsx +218 -0
  84. package/components/theme-editor/theme-placeholder-wrapper.tsx +732 -0
  85. package/components/theme-editor/theme-placeholder.tsx +288 -0
  86. package/components/theme-editor/theme-section.tsx +1224 -0
  87. package/components/theme-editor/theme-settings-context.tsx +13 -0
  88. package/components/theme-editor/utils/index.ts +792 -0
  89. package/components/theme-editor/utils/iterator-utils.ts +234 -0
  90. package/components/theme-editor/utils/publish-window.ts +86 -0
  91. package/components/theme-editor/utils/visibility-rules.ts +188 -0
  92. package/data/client/account.ts +17 -2
  93. package/data/client/api.ts +2 -0
  94. package/data/client/basket.ts +66 -5
  95. package/data/client/checkout.ts +391 -99
  96. package/data/client/misc.ts +38 -2
  97. package/data/client/product.ts +19 -2
  98. package/data/client/user.ts +16 -8
  99. package/data/server/category.ts +11 -9
  100. package/data/server/flatpage.ts +11 -4
  101. package/data/server/form.ts +15 -4
  102. package/data/server/landingpage.ts +11 -4
  103. package/data/server/list.ts +5 -4
  104. package/data/server/menu.ts +11 -3
  105. package/data/server/product.ts +111 -55
  106. package/data/server/seo.ts +14 -4
  107. package/data/server/special-page.ts +5 -4
  108. package/data/server/widget.ts +90 -5
  109. package/data/urls.ts +16 -5
  110. package/hocs/client/with-segment-defaults.tsx +2 -2
  111. package/hocs/server/with-segment-defaults.tsx +65 -20
  112. package/hooks/index.ts +4 -0
  113. package/hooks/use-localization.ts +24 -10
  114. package/hooks/use-logger-context.tsx +114 -0
  115. package/hooks/use-logger.ts +92 -0
  116. package/hooks/use-loyalty-availability.ts +21 -0
  117. package/hooks/use-payment-options.ts +2 -1
  118. package/hooks/use-pz-params.ts +37 -0
  119. package/hooks/use-router.ts +51 -14
  120. package/hooks/use-sentry-uncaught-errors.ts +24 -0
  121. package/instrumentation/index.ts +10 -1
  122. package/instrumentation/node.ts +2 -20
  123. package/jest.config.js +25 -0
  124. package/lib/cache-handler.mjs +534 -16
  125. package/lib/cache.ts +272 -37
  126. package/localization/index.ts +2 -1
  127. package/localization/provider.tsx +2 -5
  128. package/middlewares/bfcache-headers.ts +18 -0
  129. package/middlewares/checkout-provider.ts +1 -1
  130. package/middlewares/complete-gpay.ts +32 -26
  131. package/middlewares/complete-masterpass.ts +33 -26
  132. package/middlewares/complete-wallet.ts +182 -0
  133. package/middlewares/default.ts +360 -215
  134. package/middlewares/index.ts +10 -2
  135. package/middlewares/locale.ts +34 -11
  136. package/middlewares/masterpass-rest-callback.ts +230 -0
  137. package/middlewares/oauth-login.ts +200 -57
  138. package/middlewares/pretty-url.ts +21 -8
  139. package/middlewares/redirection-payment.ts +32 -26
  140. package/middlewares/saved-card-redirection.ts +33 -26
  141. package/middlewares/three-d-redirection.ts +32 -26
  142. package/middlewares/url-redirection.ts +11 -1
  143. package/middlewares/wallet-complete-redirection.ts +206 -0
  144. package/package.json +25 -10
  145. package/plugins.d.ts +19 -4
  146. package/plugins.js +10 -1
  147. package/redux/actions.ts +47 -0
  148. package/redux/middlewares/checkout.ts +63 -138
  149. package/redux/middlewares/index.ts +14 -10
  150. package/redux/middlewares/pre-order/address.ts +7 -2
  151. package/redux/middlewares/pre-order/attribute-based-shipping-option.ts +7 -1
  152. package/redux/middlewares/pre-order/data-source-shipping-option.ts +7 -1
  153. package/redux/middlewares/pre-order/delivery-option.ts +7 -1
  154. package/redux/middlewares/pre-order/index.ts +16 -10
  155. package/redux/middlewares/pre-order/installment-option.ts +8 -1
  156. package/redux/middlewares/pre-order/payment-option-reset.ts +37 -0
  157. package/redux/middlewares/pre-order/payment-option.ts +7 -1
  158. package/redux/middlewares/pre-order/pre-order-validation.ts +8 -3
  159. package/redux/middlewares/pre-order/redirection.ts +8 -2
  160. package/redux/middlewares/pre-order/set-pre-order.ts +6 -2
  161. package/redux/middlewares/pre-order/shipping-option.ts +7 -1
  162. package/redux/middlewares/pre-order/shipping-step.ts +5 -1
  163. package/redux/reducers/checkout.ts +23 -3
  164. package/redux/reducers/index.ts +11 -3
  165. package/redux/reducers/root.ts +7 -2
  166. package/redux/reducers/widget.ts +80 -0
  167. package/sentry/index.ts +69 -13
  168. package/tailwind/content.js +16 -0
  169. package/types/commerce/account.ts +5 -1
  170. package/types/commerce/checkout.ts +35 -1
  171. package/types/commerce/widget.ts +33 -0
  172. package/types/index.ts +101 -6
  173. package/types/next-auth.d.ts +2 -2
  174. package/types/widget.ts +80 -0
  175. package/utils/app-fetch.ts +7 -2
  176. package/utils/generate-commerce-search-params.ts +3 -2
  177. package/utils/get-checkout-path.ts +3 -0
  178. package/utils/get-root-hostname.ts +28 -0
  179. package/utils/index.ts +64 -10
  180. package/utils/localization.ts +4 -0
  181. package/utils/mobile-3d-iframe.ts +8 -2
  182. package/utils/override-middleware.ts +7 -12
  183. package/utils/pz-segments.ts +92 -0
  184. package/utils/redirect-ignore.ts +35 -0
  185. package/utils/redirect.ts +9 -3
  186. package/utils/redirection-iframe.ts +8 -2
  187. package/utils/widget-styles.ts +107 -0
  188. package/views/error-page.tsx +93 -0
  189. package/with-pz-config.js +13 -6
@@ -9,6 +9,10 @@ import withCompleteGpay from './complete-gpay';
9
9
  import withCompleteMasterpass from './complete-masterpass';
10
10
  import withCheckoutProvider from './checkout-provider';
11
11
  import withSavedCardRedirection from './saved-card-redirection';
12
+ import withCompleteWallet from './complete-wallet';
13
+ import withWalletCompleteRedirection from './wallet-complete-redirection';
14
+ import withMasterpassRestCallback from './masterpass-rest-callback';
15
+ import withBfcacheHeaders from './bfcache-headers';
12
16
  import { NextRequest } from 'next/server';
13
17
 
14
18
  export {
@@ -22,17 +26,21 @@ export {
22
26
  withCompleteGpay,
23
27
  withCompleteMasterpass,
24
28
  withCheckoutProvider,
25
- withSavedCardRedirection
29
+ withSavedCardRedirection,
30
+ withCompleteWallet,
31
+ withWalletCompleteRedirection,
32
+ withMasterpassRestCallback,
33
+ withBfcacheHeaders
26
34
  };
27
35
 
28
36
  export interface PzNextRequest extends NextRequest {
29
37
  middlewareParams: {
30
38
  commerceUrl: string;
31
- found: boolean;
32
39
  rewrites: {
33
40
  locale?: string;
34
41
  prettyUrl?: string;
35
42
  currency?: string;
43
+ [key: string]: string | undefined;
36
44
  };
37
45
  };
38
46
  }
@@ -4,17 +4,33 @@ import { PzNextRequest } from '.';
4
4
  import { LocaleUrlStrategy } from '../localization';
5
5
  import { urlLocaleMatcherRegex } from '../utils';
6
6
  import logger from '../utils/log';
7
+ import { getUrlPathWithLocale } from '../utils/localization';
7
8
 
8
- const getMatchedLocale = (pathname: string) => {
9
+ const getMatchedLocale = (pathname: string, req: PzNextRequest) => {
9
10
  let matchedLocale = pathname.match(urlLocaleMatcherRegex)?.[0] ?? '';
10
11
  matchedLocale = matchedLocale.replace('/', '');
11
12
 
13
+ const { localeUrlStrategy, defaultLocaleValue } = settings.localization;
14
+
15
+ if (localeUrlStrategy === LocaleUrlStrategy.Subdomain) {
16
+ const host =
17
+ req.headers.get('x-forwarded-host') || req.headers.get('host') || '';
18
+
19
+ if (host) {
20
+ const subDomain = host.split('.')[0] || '';
21
+ const subDomainLocaleMatched = `/${subDomain}`.match(
22
+ urlLocaleMatcherRegex
23
+ );
24
+
25
+ if (subDomainLocaleMatched && subDomainLocaleMatched[0]) {
26
+ matchedLocale = subDomainLocaleMatched[0].slice(1);
27
+ }
28
+ }
29
+ }
30
+
12
31
  if (!matchedLocale.length) {
13
- if (
14
- settings.localization.localeUrlStrategy !==
15
- LocaleUrlStrategy.ShowAllLocales
16
- ) {
17
- matchedLocale = settings.localization.defaultLocaleValue;
32
+ if (localeUrlStrategy !== LocaleUrlStrategy.ShowAllLocales) {
33
+ matchedLocale = defaultLocaleValue;
18
34
  }
19
35
  }
20
36
 
@@ -28,8 +44,10 @@ const withLocale =
28
44
 
29
45
  try {
30
46
  const url = req.nextUrl.clone();
31
- const matchedLocale = getMatchedLocale(url.pathname);
32
- let { localeUrlStrategy, defaultLocaleValue, redirectToDefaultLocale } =
47
+ const matchedLocale = getMatchedLocale(url.pathname, req);
48
+ let { localeUrlStrategy } = settings.localization;
49
+
50
+ const { defaultLocaleValue, redirectToDefaultLocale } =
33
51
  settings.localization;
34
52
 
35
53
  localeUrlStrategy =
@@ -50,8 +68,14 @@ const withLocale =
50
68
  redirectToDefaultLocale &&
51
69
  req.method === 'GET'
52
70
  ) {
53
- url.pathname = `/${defaultLocaleValue}${url.pathname}`;
54
- return NextResponse.redirect(url);
71
+ // Redirect to existing or default locale
72
+ url.pathname = getUrlPathWithLocale(
73
+ url.pathname,
74
+ req.cookies.get('pz-locale')?.value
75
+ );
76
+
77
+ // Use 303 for POST requests
78
+ return NextResponse.redirect(url, 303);
55
79
  }
56
80
 
57
81
  req.middlewareParams.rewrites.locale = matchedLocale;
@@ -61,7 +85,6 @@ const withLocale =
61
85
  ip
62
86
  });
63
87
  }
64
-
65
88
  return middleware(req, event);
66
89
  };
67
90
 
@@ -0,0 +1,230 @@
1
+ import { NextFetchEvent, NextMiddleware, NextResponse } from 'next/server';
2
+ import Settings from 'settings';
3
+ import logger from '../utils/log';
4
+ import { getUrlPathWithLocale } from '../utils/localization';
5
+ import { PzNextRequest } from '.';
6
+ import { getCheckoutPath } from '../utils';
7
+
8
+ const withMasterpassRestCallback =
9
+ (middleware: NextMiddleware) =>
10
+ async (req: PzNextRequest, event: NextFetchEvent) => {
11
+ const url = req.nextUrl.clone();
12
+ const ip = req.headers.get('x-forwarded-for') ?? '';
13
+ const sessionId = req.cookies.get('osessionid');
14
+
15
+ if (!url.pathname.includes('/orders/masterpass-rest-callback')) {
16
+ return middleware(req, event);
17
+ }
18
+
19
+ if (req.method !== 'POST') {
20
+ logger.warn('Invalid request method for masterpass REST callback', {
21
+ middleware: 'masterpass-rest-callback',
22
+ method: req.method,
23
+ ip
24
+ });
25
+
26
+ return NextResponse.redirect(
27
+ `${url.origin}${getUrlPathWithLocale(
28
+ '/orders/checkout/',
29
+ req.cookies.get('pz-locale')?.value
30
+ )}`,
31
+ 303
32
+ );
33
+ }
34
+
35
+ const responseCode = url.searchParams.get('responseCode');
36
+ const token = url.searchParams.get('token');
37
+
38
+ if (!responseCode || !token) {
39
+ logger.warn('Missing required parameters for masterpass REST callback', {
40
+ middleware: 'masterpass-rest-callback',
41
+ responseCode,
42
+ token,
43
+ ip
44
+ });
45
+
46
+ return NextResponse.redirect(
47
+ `${url.origin}${getUrlPathWithLocale(
48
+ '/orders/checkout/',
49
+ req.cookies.get('pz-locale')?.value
50
+ )}`,
51
+ 303
52
+ );
53
+ }
54
+
55
+ try {
56
+ const formData = await req.formData();
57
+ const body: Record<string, string> = {};
58
+
59
+ Array.from(formData.entries()).forEach(([key, value]) => {
60
+ body[key] = value.toString();
61
+ });
62
+
63
+ if (!sessionId) {
64
+ logger.warn(
65
+ 'Make sure that the SESSION_COOKIE_SAMESITE environment variable is set to None in Commerce.',
66
+ {
67
+ middleware: 'masterpass-rest-callback',
68
+ ip
69
+ }
70
+ );
71
+
72
+ return NextResponse.redirect(
73
+ `${url.origin}${getUrlPathWithLocale(
74
+ '/orders/checkout/',
75
+ req.cookies.get('pz-locale')?.value
76
+ )}`,
77
+ 303
78
+ );
79
+ }
80
+
81
+ const isPostCheckout = req.cookies.get('pz-post-checkout-flow')?.value === 'true';
82
+ const requestUrl = new URL(getCheckoutPath(isPostCheckout), Settings.commerceUrl);
83
+ requestUrl.searchParams.set('page', 'MasterpassRestCompletePage');
84
+ requestUrl.searchParams.set('responseCode', responseCode);
85
+ requestUrl.searchParams.set('token', token);
86
+ requestUrl.searchParams.set(
87
+ 'three_d_secure',
88
+ body.transactionType?.includes('3D') ? 'true' : 'false'
89
+ );
90
+ requestUrl.searchParams.set(
91
+ 'transactionType',
92
+ body.transactionType || ''
93
+ );
94
+
95
+ const requestHeaders = {
96
+ 'Content-Type': 'application/x-www-form-urlencoded',
97
+ 'X-Requested-With': 'XMLHttpRequest',
98
+ Cookie: req.headers.get('cookie') ?? '',
99
+ 'x-currency': req.cookies.get('pz-currency')?.value ?? '',
100
+ 'x-forwarded-for': ip,
101
+ 'User-Agent': req.headers.get('user-agent') ?? ''
102
+ };
103
+
104
+ const request = await fetch(requestUrl.toString(), {
105
+ method: 'POST',
106
+ headers: requestHeaders,
107
+ body: new URLSearchParams(body)
108
+ });
109
+
110
+ logger.info('Masterpass REST callback request', {
111
+ requestUrl: requestUrl.toString(),
112
+ status: request.status,
113
+ requestHeaders,
114
+ ip
115
+ });
116
+
117
+ const response = await request.json();
118
+
119
+ const { context_list: contextList, errors } = response;
120
+
121
+ let redirectUrl = response.redirect_url;
122
+
123
+ if (!redirectUrl && contextList && contextList.length > 0) {
124
+ for (const context of contextList) {
125
+ if (context.page_context && context.page_context.redirect_url) {
126
+ redirectUrl = context.page_context.redirect_url;
127
+ break;
128
+ }
129
+ }
130
+ }
131
+
132
+ if (errors && Object.keys(errors).length) {
133
+ logger.error('Error while processing masterpass REST callback', {
134
+ middleware: 'masterpass-rest-callback',
135
+ errors,
136
+ requestHeaders,
137
+ ip
138
+ });
139
+
140
+ const errorResponse = NextResponse.redirect(
141
+ `${url.origin}${getUrlPathWithLocale(
142
+ '/orders/checkout/',
143
+ req.cookies.get('pz-locale')?.value
144
+ )}`,
145
+ {
146
+ status: 303,
147
+ headers: {
148
+ 'Set-Cookie': `pz-pos-error=${encodeURIComponent(JSON.stringify(errors))}; path=/;`
149
+ }
150
+ }
151
+ );
152
+
153
+ // Add error cookie
154
+ errorResponse.cookies.set('pz-pos-error', JSON.stringify(errors), {
155
+ path: '/'
156
+ });
157
+
158
+ return errorResponse;
159
+ }
160
+
161
+ logger.info('Masterpass REST callback response', {
162
+ middleware: 'masterpass-rest-callback',
163
+ contextList,
164
+ redirectUrl,
165
+ ip
166
+ });
167
+
168
+ if (!redirectUrl) {
169
+ logger.warn(
170
+ 'No redirection url found in response. Redirecting to checkout page.',
171
+ {
172
+ middleware: 'masterpass-rest-callback',
173
+ requestHeaders,
174
+ response: JSON.stringify(response),
175
+ ip
176
+ }
177
+ );
178
+
179
+ const redirectUrlWithLocale = `${url.origin}${getUrlPathWithLocale(
180
+ '/orders/checkout/',
181
+ req.cookies.get('pz-locale')?.value
182
+ )}`;
183
+
184
+ return NextResponse.redirect(redirectUrlWithLocale, 303);
185
+ }
186
+
187
+ const redirectUrlWithLocale = `${url.origin}${getUrlPathWithLocale(
188
+ redirectUrl,
189
+ req.cookies.get('pz-locale')?.value
190
+ )}`;
191
+
192
+ logger.info('Redirecting after masterpass REST callback', {
193
+ middleware: 'masterpass-rest-callback',
194
+ redirectUrl: redirectUrlWithLocale,
195
+ ip
196
+ });
197
+
198
+ const nextResponse = NextResponse.redirect(redirectUrlWithLocale, 303);
199
+
200
+ // Forward set-cookie headers from the upstream response
201
+ const setCookieHeader = request.headers.get('set-cookie');
202
+ if (setCookieHeader) {
203
+ setCookieHeader.split(',').forEach((cookie) => {
204
+ nextResponse.headers.append('Set-Cookie', cookie.trim());
205
+ });
206
+ }
207
+
208
+ return nextResponse;
209
+ } catch (error) {
210
+ logger.error('Error while processing masterpass REST callback', {
211
+ middleware: 'masterpass-rest-callback',
212
+ error,
213
+ requestHeaders: {
214
+ Cookie: req.headers.get('cookie') ?? '',
215
+ 'x-currency': req.cookies.get('pz-currency')?.value ?? ''
216
+ },
217
+ ip
218
+ });
219
+
220
+ return NextResponse.redirect(
221
+ `${url.origin}${getUrlPathWithLocale(
222
+ '/orders/checkout/',
223
+ req.cookies.get('pz-locale')?.value
224
+ )}`,
225
+ 303
226
+ );
227
+ }
228
+ };
229
+
230
+ export default withMasterpassRestCallback;
@@ -6,78 +6,221 @@ import {
6
6
  } from 'next/server';
7
7
  import Settings from 'settings';
8
8
  import { getUrlPathWithLocale } from '../utils/localization';
9
+ import logger from '../utils/log';
9
10
 
10
- const withOauthLogin =
11
- (middleware: NextMiddleware) =>
12
- async (req: NextRequest, event: NextFetchEvent) => {
13
- const url = req.nextUrl.clone();
14
- const loginUrlMatcherRegex = new RegExp(/^\/(\w+)\/login\/?$/);
15
- const loginCallbackUrlMatcherRegex = new RegExp(
16
- /^\/(\w+)\/login\/callback\/?$/
17
- );
18
- const ip = req.headers.get('x-forwarded-for') ?? '';
19
-
20
- const headers = {
21
- 'x-forwarded-host':
22
- req.headers.get('x-forwarded-host') || req.headers.get('host') || '',
23
- 'x-currency': req.cookies.get('pz-currency')?.value ?? '',
24
- 'x-forwarded-for': ip
11
+ const LOGIN_URL_REGEX = /^\/(\w+)\/login\/?$/;
12
+ const CALLBACK_URL_REGEX = /^\/(\w+)\/login\/callback\/?$/;
13
+
14
+ function buildCommerceHeaders(req: NextRequest): Record<string, string> {
15
+ return {
16
+ 'x-forwarded-host':
17
+ req.headers.get('x-forwarded-host') || req.headers.get('host') || '',
18
+ 'x-forwarded-for': req.headers.get('x-forwarded-for') ?? '',
19
+ 'x-forwarded-proto': req.headers.get('x-forwarded-proto') || 'https',
20
+ 'x-currency': req.cookies.get('pz-currency')?.value ?? '',
21
+ cookie: req.headers.get('cookie') ?? ''
22
+ };
23
+ }
24
+
25
+ async function getRequestBody(
26
+ req: NextRequest
27
+ ): Promise<{ content: string; contentType: string } | undefined> {
28
+ if (req.method !== 'POST') return undefined;
29
+
30
+ const content = await req.text();
31
+ if (!content.length) return undefined;
32
+
33
+ return {
34
+ content,
35
+ contentType:
36
+ req.headers.get('content-type') ?? 'application/x-www-form-urlencoded'
37
+ };
38
+ }
39
+
40
+ function fetchCommerce(
41
+ url: string,
42
+ req: NextRequest,
43
+ body?: { content: string; contentType: string }
44
+ ): Promise<Response> {
45
+ const headers = buildCommerceHeaders(req);
46
+
47
+ if (body) {
48
+ headers['content-type'] = body.contentType;
49
+ }
50
+
51
+ return fetch(url, {
52
+ method: body ? 'POST' : 'GET',
53
+ headers,
54
+ body: body?.content,
55
+ redirect: 'manual'
56
+ });
57
+ }
58
+
59
+ function forwardCookies(from: Response, to: NextResponse): void {
60
+ from.headers.getSetCookie().forEach((cookie) => {
61
+ to.headers.append('set-cookie', cookie);
62
+ });
63
+ }
64
+
65
+ function buildRedirectResponse(
66
+ commerceResponse: Response,
67
+ location: string,
68
+ origin: string
69
+ ): NextResponse {
70
+ const response = NextResponse.redirect(new URL(location, origin));
71
+ forwardCookies(commerceResponse, response);
72
+ return response;
73
+ }
74
+
75
+ function commercePassthrough(commerceResponse: Response): NextResponse {
76
+ return new NextResponse(commerceResponse.body, {
77
+ status: commerceResponse.status,
78
+ headers: commerceResponse.headers
79
+ });
80
+ }
81
+
82
+ function buildOAuthCallbackCookie(referer: string): string {
83
+ return `pz-oauth-callback-url=${encodeURIComponent(referer)}; Path=/`;
84
+ }
85
+
86
+ async function handleLogin(
87
+ req: NextRequest,
88
+ provider: string
89
+ ): Promise<{ response: NextResponse; redirected: boolean }> {
90
+ const commerceResponse = await fetchCommerce(
91
+ `${Settings.commerceUrl}/${provider}/login/`,
92
+ req
93
+ );
94
+
95
+ const location = commerceResponse.headers.get('location');
96
+ if (!location) {
97
+ return {
98
+ response: commercePassthrough(commerceResponse),
99
+ redirected: false
25
100
  };
101
+ }
26
102
 
27
- if (loginUrlMatcherRegex.test(url.pathname)) {
28
- const provider = url.pathname.match(loginUrlMatcherRegex)[1];
29
- const response = NextResponse.rewrite(
30
- `${Settings.commerceUrl}/${provider}/login/`,
31
- {
32
- headers
33
- }
34
- );
103
+ const response = buildRedirectResponse(
104
+ commerceResponse,
105
+ location,
106
+ req.nextUrl.origin
107
+ );
35
108
 
36
- if (req.headers.get('referer')) {
37
- response.cookies.set(
38
- 'pz-oauth-callback-url',
39
- req.headers.get('referer')
40
- );
41
- }
109
+ response.headers.append(
110
+ 'set-cookie',
111
+ buildOAuthCallbackCookie(req.headers.get('referer') || '')
112
+ );
42
113
 
43
- return response;
44
- }
114
+ return { response, redirected: true };
115
+ }
45
116
 
46
- if (loginCallbackUrlMatcherRegex.test(url.pathname)) {
47
- const provider = url.pathname.match(loginCallbackUrlMatcherRegex)[1];
117
+ async function handleCallback(
118
+ req: NextRequest,
119
+ provider: string,
120
+ search: string
121
+ ): Promise<{ response: NextResponse; redirected: boolean }> {
122
+ const body = await getRequestBody(req);
123
+ const commerceResponse = await fetchCommerce(
124
+ `${Settings.commerceUrl}/${provider}/login/callback/${search}`,
125
+ req,
126
+ body
127
+ );
48
128
 
49
- return NextResponse.rewrite(
50
- `${Settings.commerceUrl}/${provider}/login/callback/${url.search}`,
51
- {
52
- headers
53
- }
54
- );
55
- }
129
+ const location = commerceResponse.headers.get('location');
130
+ if (!location) {
131
+ return {
132
+ response: commercePassthrough(commerceResponse),
133
+ redirected: false
134
+ };
135
+ }
136
+
137
+ return {
138
+ response: buildRedirectResponse(
139
+ commerceResponse,
140
+ location,
141
+ req.nextUrl.origin
142
+ ),
143
+ redirected: true
144
+ };
145
+ }
146
+
147
+ function handleBasketRedirect(req: NextRequest): NextResponse | null {
148
+ const hasSession = req.cookies.get('osessionid');
149
+ const messages = req.cookies.get('messages')?.value;
150
+
151
+ if (!messages) return null;
152
+ if (!messages.includes('Successfully signed in') && !hasSession) return null;
153
+
154
+ let redirectUrl = `${req.nextUrl.origin}${getUrlPathWithLocale(
155
+ '/auth/oauth-login',
156
+ req.cookies.get('pz-locale')?.value
157
+ )}`;
158
+
159
+ const callbackUrl = req.cookies.get('pz-oauth-callback-url')?.value ?? '';
160
+ if (callbackUrl.length) {
161
+ redirectUrl += `?next=${encodeURIComponent(callbackUrl)}`;
162
+ }
163
+
164
+ const response = NextResponse.redirect(redirectUrl);
165
+ response.cookies.delete('messages');
166
+ response.cookies.delete('pz-oauth-callback-url');
167
+ return response;
168
+ }
56
169
 
57
- if (!url.pathname.startsWith('/baskets/basket')) {
170
+ const withOauthLogin =
171
+ (middleware: NextMiddleware) =>
172
+ async (req: NextRequest, event: NextFetchEvent) => {
173
+ const { pathname, search } = req.nextUrl;
174
+
175
+ if (!pathname.includes('/login') && !pathname.startsWith('/baskets/basket')) {
58
176
  return middleware(req, event);
59
177
  }
60
178
 
61
- const currentSessionId = req.cookies.get('osessionid');
179
+ logger.info('OAuth login redirect', {
180
+ host: req.headers.get('host'),
181
+ 'x-forwarded-host': req.headers.get('x-forwarded-host'),
182
+ 'x-forwarded-for': req.headers.get('x-forwarded-for')
183
+ });
62
184
 
63
- if (
64
- req.cookies.get('messages')?.value.includes('Successfully signed in') ||
65
- (currentSessionId && req.cookies.get('messages'))
66
- ) {
67
- let redirectUrlWithLocale = `${url.origin}${getUrlPathWithLocale(
68
- '/auth/oauth-login',
69
- req.cookies.get('pz-locale')?.value
70
- )}`;
71
- let callbackUrl = req.cookies.get('pz-oauth-callback-url')?.value ?? '';
185
+ const loginMatch = LOGIN_URL_REGEX.exec(pathname);
186
+ if (loginMatch) {
187
+ try {
188
+ const { response, redirected } = await handleLogin(req, loginMatch[1]);
189
+ if (!redirected) {
190
+ logger.warn('OAuth login: no redirect from commerce', {
191
+ provider: loginMatch[1]
192
+ });
193
+ }
194
+ return response;
195
+ } catch (error) {
196
+ logger.error('OAuth login fetch failed', { error });
197
+ return middleware(req, event);
198
+ }
199
+ }
72
200
 
73
- if (callbackUrl.length) {
74
- redirectUrlWithLocale += `?next=${encodeURIComponent(callbackUrl)}`;
201
+ const callbackMatch = CALLBACK_URL_REGEX.exec(pathname);
202
+ if (callbackMatch) {
203
+ try {
204
+ const { response, redirected } = await handleCallback(
205
+ req,
206
+ callbackMatch[1],
207
+ search
208
+ );
209
+ if (!redirected) {
210
+ logger.warn('OAuth callback: no redirect from commerce', {
211
+ provider: callbackMatch[1]
212
+ });
213
+ }
214
+ return response;
215
+ } catch (error) {
216
+ logger.error('OAuth callback fetch failed', { error });
217
+ return middleware(req, event);
75
218
  }
219
+ }
76
220
 
77
- const response = NextResponse.redirect(redirectUrlWithLocale);
78
- response.cookies.delete('messages');
79
- response.cookies.delete('pz-oauth-callback-url');
80
- return response;
221
+ if (pathname.startsWith('/baskets/basket')) {
222
+ const response = handleBasketRedirect(req);
223
+ if (response) return response;
81
224
  }
82
225
 
83
226
  return middleware(req, event);
@@ -1,17 +1,33 @@
1
1
  import { Cache, CacheKey } from '../lib/cache';
2
- import { NextFetchEvent, NextMiddleware, NextRequest } from 'next/server';
3
- import { ROUTES } from 'routes';
2
+ import { NextFetchEvent, NextMiddleware } from 'next/server';
4
3
  import { URLS } from '../data/urls';
5
4
  import Settings from 'settings';
6
5
  import { urlLocaleMatcherRegex } from '../utils';
7
6
  import { PzNextRequest } from '.';
8
7
  import logger from '../utils/log';
8
+ import { ROUTES } from 'routes';
9
9
 
10
10
  type PrettyUrlResult = {
11
11
  matched: boolean;
12
12
  path?: string;
13
13
  };
14
14
 
15
+ let APP_ROUTES: string[] = [];
16
+
17
+ const legacyRoutes = Object.values(ROUTES);
18
+
19
+ try {
20
+ const generatedRoutes = require('routes/generated-routes');
21
+ const allRoutes = [...legacyRoutes, ...(generatedRoutes || [])];
22
+ APP_ROUTES = Array.from(new Set(allRoutes));
23
+ logger.debug('Loaded merged routes (legacy + generated)', {
24
+ count: APP_ROUTES.length
25
+ });
26
+ } catch (error) {
27
+ APP_ROUTES = legacyRoutes;
28
+ logger.debug('Loaded only legacy routes', { count: APP_ROUTES.length });
29
+ }
30
+
15
31
  const resolvePrettyUrlHandler =
16
32
  (pathname: string, ip: string | null) => async () => {
17
33
  let results = <{ old_path: string }[]>[];
@@ -53,7 +69,8 @@ const resolvePrettyUrl = async (
53
69
  locale,
54
70
  resolvePrettyUrlHandler(pathname, ip),
55
71
  {
56
- useProxy: true
72
+ useProxy: true,
73
+ compressed: true
57
74
  }
58
75
  );
59
76
  };
@@ -73,9 +90,7 @@ const withPrettyUrl =
73
90
  const isValidPrettyUrlPath = (pathname: string) => {
74
91
  return (
75
92
  new RegExp(/^\/[a-zA-Z0-9/]+(?:-[a-zA-Z0-9/]+)*$/).test(pathname) &&
76
- !Object.entries(ROUTES).find(([, value]) =>
77
- new RegExp(`^${value}$`).test(pathname)
78
- )
93
+ !APP_ROUTES.some((route) => new RegExp(`^${route}$`).test(pathname))
79
94
  );
80
95
  };
81
96
  const ip = req.headers.get('x-forwarded-for') ?? '';
@@ -108,8 +123,6 @@ const withPrettyUrl =
108
123
  return middleware(req, event);
109
124
  }
110
125
 
111
- req.middlewareParams.found = false;
112
-
113
126
  return middleware(req, event);
114
127
  };
115
128