@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.
- package/.eslintrc.js +12 -0
- package/CHANGELOG.md +377 -7
- package/__tests__/next-config.test.ts +83 -0
- package/__tests__/tsconfig.json +23 -0
- package/api/auth.ts +133 -44
- package/api/barcode-search.ts +59 -0
- package/api/cache.ts +41 -5
- package/api/client.ts +21 -4
- package/api/form.ts +85 -0
- package/api/image-proxy.ts +75 -0
- package/api/product-categories.ts +53 -0
- package/api/similar-product-list.ts +63 -0
- package/api/similar-products.ts +111 -0
- package/api/virtual-try-on.ts +382 -0
- package/assets/styles/index.scss +84 -0
- package/babel.config.js +6 -0
- package/bin/pz-generate-routes.js +115 -0
- package/bin/pz-prebuild.js +1 -0
- package/bin/pz-predev.js +1 -0
- package/bin/pz-run-tests.js +99 -0
- package/bin/run-prebuild-tests.js +46 -0
- package/components/accordion.tsx +20 -5
- package/components/button.tsx +51 -36
- package/components/client-root.tsx +138 -2
- package/components/file-input.tsx +65 -3
- package/components/index.ts +1 -0
- package/components/input.tsx +1 -1
- package/components/link.tsx +46 -16
- package/components/logger-popup.tsx +213 -0
- package/components/modal.tsx +32 -16
- package/components/plugin-module.tsx +62 -3
- package/components/price.tsx +2 -2
- package/components/select.tsx +1 -1
- package/components/selected-payment-option-view.tsx +21 -0
- package/components/theme-editor/blocks/accordion-block.tsx +136 -0
- package/components/theme-editor/blocks/block-renderer-registry.tsx +77 -0
- package/components/theme-editor/blocks/button-block.tsx +593 -0
- package/components/theme-editor/blocks/counter-block.tsx +348 -0
- package/components/theme-editor/blocks/divider-block.tsx +20 -0
- package/components/theme-editor/blocks/embed-block.tsx +208 -0
- package/components/theme-editor/blocks/group-block.tsx +116 -0
- package/components/theme-editor/blocks/hotspot-block.tsx +147 -0
- package/components/theme-editor/blocks/icon-block.tsx +230 -0
- package/components/theme-editor/blocks/image-block.tsx +137 -0
- package/components/theme-editor/blocks/image-gallery-block.tsx +269 -0
- package/components/theme-editor/blocks/input-block.tsx +123 -0
- package/components/theme-editor/blocks/link-block.tsx +216 -0
- package/components/theme-editor/blocks/lottie-block.tsx +325 -0
- package/components/theme-editor/blocks/map-block.tsx +89 -0
- package/components/theme-editor/blocks/slider-block.tsx +595 -0
- package/components/theme-editor/blocks/tab-block.tsx +10 -0
- package/components/theme-editor/blocks/text-block.tsx +52 -0
- package/components/theme-editor/blocks/video-block.tsx +122 -0
- package/components/theme-editor/components/action-toolbar.tsx +305 -0
- package/components/theme-editor/components/designer-overlay.tsx +74 -0
- package/components/theme-editor/components/with-designer-features.tsx +142 -0
- package/components/theme-editor/dynamic-font-loader.tsx +79 -0
- package/components/theme-editor/hooks/use-designer-features.tsx +100 -0
- package/components/theme-editor/hooks/use-external-designer.tsx +95 -0
- package/components/theme-editor/hooks/use-native-widget-data.ts +188 -0
- package/components/theme-editor/hooks/use-visibility-context.ts +27 -0
- package/components/theme-editor/placeholder-registry.ts +31 -0
- package/components/theme-editor/sections/before-after-section.tsx +245 -0
- package/components/theme-editor/sections/contact-form-section.tsx +563 -0
- package/components/theme-editor/sections/countdown-campaign-banner-section.tsx +433 -0
- package/components/theme-editor/sections/coupon-banner-section.tsx +710 -0
- package/components/theme-editor/sections/divider-section.tsx +62 -0
- package/components/theme-editor/sections/featured-product-spotlight-section.tsx +507 -0
- package/components/theme-editor/sections/find-in-store-section.tsx +1995 -0
- package/components/theme-editor/sections/hover-showcase-section.tsx +326 -0
- package/components/theme-editor/sections/image-hotspot-section.tsx +142 -0
- package/components/theme-editor/sections/installment-options-section.tsx +1065 -0
- package/components/theme-editor/sections/notification-banner-section.tsx +173 -0
- package/components/theme-editor/sections/order-tracking-lookup-section.tsx +1379 -0
- package/components/theme-editor/sections/posts-slider-section.tsx +472 -0
- package/components/theme-editor/sections/pre-order-launch-banner-section.tsx +663 -0
- package/components/theme-editor/sections/section-renderer-registry.tsx +89 -0
- package/components/theme-editor/sections/section-wrapper.tsx +135 -0
- package/components/theme-editor/sections/shipping-threshold-progress-section.tsx +586 -0
- package/components/theme-editor/sections/stats-counter-section.tsx +486 -0
- package/components/theme-editor/sections/tabs-section.tsx +578 -0
- package/components/theme-editor/theme-block.tsx +102 -0
- package/components/theme-editor/theme-placeholder-client.tsx +218 -0
- package/components/theme-editor/theme-placeholder-wrapper.tsx +732 -0
- package/components/theme-editor/theme-placeholder.tsx +288 -0
- package/components/theme-editor/theme-section.tsx +1224 -0
- package/components/theme-editor/theme-settings-context.tsx +13 -0
- package/components/theme-editor/utils/index.ts +792 -0
- package/components/theme-editor/utils/iterator-utils.ts +234 -0
- package/components/theme-editor/utils/publish-window.ts +86 -0
- package/components/theme-editor/utils/visibility-rules.ts +188 -0
- package/data/client/account.ts +17 -2
- package/data/client/api.ts +2 -0
- package/data/client/basket.ts +66 -5
- package/data/client/checkout.ts +391 -99
- package/data/client/misc.ts +38 -2
- package/data/client/product.ts +19 -2
- package/data/client/user.ts +16 -8
- package/data/server/category.ts +11 -9
- package/data/server/flatpage.ts +11 -4
- package/data/server/form.ts +15 -4
- package/data/server/landingpage.ts +11 -4
- package/data/server/list.ts +5 -4
- package/data/server/menu.ts +11 -3
- package/data/server/product.ts +111 -55
- package/data/server/seo.ts +14 -4
- package/data/server/special-page.ts +5 -4
- package/data/server/widget.ts +90 -5
- package/data/urls.ts +16 -5
- package/hocs/client/with-segment-defaults.tsx +2 -2
- package/hocs/server/with-segment-defaults.tsx +65 -20
- package/hooks/index.ts +4 -0
- package/hooks/use-localization.ts +24 -10
- package/hooks/use-logger-context.tsx +114 -0
- package/hooks/use-logger.ts +92 -0
- package/hooks/use-loyalty-availability.ts +21 -0
- package/hooks/use-payment-options.ts +2 -1
- package/hooks/use-pz-params.ts +37 -0
- package/hooks/use-router.ts +51 -14
- package/hooks/use-sentry-uncaught-errors.ts +24 -0
- package/instrumentation/index.ts +10 -1
- package/instrumentation/node.ts +2 -20
- package/jest.config.js +25 -0
- package/lib/cache-handler.mjs +534 -16
- package/lib/cache.ts +272 -37
- package/localization/index.ts +2 -1
- package/localization/provider.tsx +2 -5
- package/middlewares/bfcache-headers.ts +18 -0
- package/middlewares/checkout-provider.ts +1 -1
- package/middlewares/complete-gpay.ts +32 -26
- package/middlewares/complete-masterpass.ts +33 -26
- package/middlewares/complete-wallet.ts +182 -0
- package/middlewares/default.ts +360 -215
- package/middlewares/index.ts +10 -2
- package/middlewares/locale.ts +34 -11
- package/middlewares/masterpass-rest-callback.ts +230 -0
- package/middlewares/oauth-login.ts +200 -57
- package/middlewares/pretty-url.ts +21 -8
- package/middlewares/redirection-payment.ts +32 -26
- package/middlewares/saved-card-redirection.ts +33 -26
- package/middlewares/three-d-redirection.ts +32 -26
- package/middlewares/url-redirection.ts +11 -1
- package/middlewares/wallet-complete-redirection.ts +206 -0
- package/package.json +25 -10
- package/plugins.d.ts +19 -4
- package/plugins.js +10 -1
- package/redux/actions.ts +47 -0
- package/redux/middlewares/checkout.ts +63 -138
- package/redux/middlewares/index.ts +14 -10
- package/redux/middlewares/pre-order/address.ts +7 -2
- package/redux/middlewares/pre-order/attribute-based-shipping-option.ts +7 -1
- package/redux/middlewares/pre-order/data-source-shipping-option.ts +7 -1
- package/redux/middlewares/pre-order/delivery-option.ts +7 -1
- package/redux/middlewares/pre-order/index.ts +16 -10
- package/redux/middlewares/pre-order/installment-option.ts +8 -1
- package/redux/middlewares/pre-order/payment-option-reset.ts +37 -0
- package/redux/middlewares/pre-order/payment-option.ts +7 -1
- package/redux/middlewares/pre-order/pre-order-validation.ts +8 -3
- package/redux/middlewares/pre-order/redirection.ts +8 -2
- package/redux/middlewares/pre-order/set-pre-order.ts +6 -2
- package/redux/middlewares/pre-order/shipping-option.ts +7 -1
- package/redux/middlewares/pre-order/shipping-step.ts +5 -1
- package/redux/reducers/checkout.ts +23 -3
- package/redux/reducers/index.ts +11 -3
- package/redux/reducers/root.ts +7 -2
- package/redux/reducers/widget.ts +80 -0
- package/sentry/index.ts +69 -13
- package/tailwind/content.js +16 -0
- package/types/commerce/account.ts +5 -1
- package/types/commerce/checkout.ts +35 -1
- package/types/commerce/widget.ts +33 -0
- package/types/index.ts +101 -6
- package/types/next-auth.d.ts +2 -2
- package/types/widget.ts +80 -0
- package/utils/app-fetch.ts +7 -2
- package/utils/generate-commerce-search-params.ts +3 -2
- package/utils/get-checkout-path.ts +3 -0
- package/utils/get-root-hostname.ts +28 -0
- package/utils/index.ts +64 -10
- package/utils/localization.ts +4 -0
- package/utils/mobile-3d-iframe.ts +8 -2
- package/utils/override-middleware.ts +7 -12
- package/utils/pz-segments.ts +92 -0
- package/utils/redirect-ignore.ts +35 -0
- package/utils/redirect.ts +9 -3
- package/utils/redirection-iframe.ts +8 -2
- package/utils/widget-styles.ts +107 -0
- package/views/error-page.tsx +93 -0
- package/with-pz-config.js +13 -6
package/middlewares/index.ts
CHANGED
|
@@ -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
|
}
|
package/middlewares/locale.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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
|
-
|
|
54
|
-
|
|
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
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
)
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
headers
|
|
33
|
-
}
|
|
34
|
-
);
|
|
103
|
+
const response = buildRedirectResponse(
|
|
104
|
+
commerceResponse,
|
|
105
|
+
location,
|
|
106
|
+
req.nextUrl.origin
|
|
107
|
+
);
|
|
35
108
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
);
|
|
41
|
-
}
|
|
109
|
+
response.headers.append(
|
|
110
|
+
'set-cookie',
|
|
111
|
+
buildOAuthCallbackCookie(req.headers.get('referer') || '')
|
|
112
|
+
);
|
|
42
113
|
|
|
43
|
-
|
|
44
|
-
|
|
114
|
+
return { response, redirected: true };
|
|
115
|
+
}
|
|
45
116
|
|
|
46
|
-
|
|
47
|
-
|
|
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
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
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
|
-
|
|
74
|
-
|
|
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
|
-
|
|
78
|
-
response
|
|
79
|
-
response
|
|
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
|
|
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
|
-
!
|
|
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
|
|