@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/lib/cache.ts
CHANGED
|
@@ -1,8 +1,67 @@
|
|
|
1
1
|
import { createPool, Pool } from 'generic-pool';
|
|
2
2
|
import { RedisClientType } from 'redis';
|
|
3
3
|
import Settings from 'settings';
|
|
4
|
-
import { CacheOptions } from '../types';
|
|
4
|
+
import { CacheOptions, SearchParams } from '../types';
|
|
5
5
|
import logger from '../utils/log';
|
|
6
|
+
const CACHE_VERSION = 'v2';
|
|
7
|
+
|
|
8
|
+
const compressData = async (data: string): Promise<Uint8Array> => {
|
|
9
|
+
const stream = new CompressionStream('gzip');
|
|
10
|
+
const writer = stream.writable.getWriter();
|
|
11
|
+
const reader = stream.readable.getReader();
|
|
12
|
+
|
|
13
|
+
writer.write(new TextEncoder().encode(data));
|
|
14
|
+
writer.close();
|
|
15
|
+
|
|
16
|
+
const chunks: Uint8Array[] = [];
|
|
17
|
+
let done = false;
|
|
18
|
+
|
|
19
|
+
while (!done) {
|
|
20
|
+
const { value, done: readerDone } = await reader.read();
|
|
21
|
+
done = readerDone;
|
|
22
|
+
if (value) chunks.push(value);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const totalLength = chunks.reduce((sum, chunk) => sum + chunk.length, 0);
|
|
26
|
+
const result = new Uint8Array(totalLength);
|
|
27
|
+
let offset = 0;
|
|
28
|
+
|
|
29
|
+
for (const chunk of chunks) {
|
|
30
|
+
result.set(chunk, offset);
|
|
31
|
+
offset += chunk.length;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return result;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const decompressData = async (compressed: Uint8Array): Promise<string> => {
|
|
38
|
+
const stream = new DecompressionStream('gzip');
|
|
39
|
+
const writer = stream.writable.getWriter();
|
|
40
|
+
const reader = stream.readable.getReader();
|
|
41
|
+
|
|
42
|
+
writer.write(compressed as any);
|
|
43
|
+
writer.close();
|
|
44
|
+
|
|
45
|
+
const chunks: Uint8Array[] = [];
|
|
46
|
+
let done = false;
|
|
47
|
+
|
|
48
|
+
while (!done) {
|
|
49
|
+
const { value, done: readerDone } = await reader.read();
|
|
50
|
+
done = readerDone;
|
|
51
|
+
if (value) chunks.push(value);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const totalLength = chunks.reduce((sum, chunk) => sum + chunk.length, 0);
|
|
55
|
+
const result = new Uint8Array(totalLength);
|
|
56
|
+
let offset = 0;
|
|
57
|
+
|
|
58
|
+
for (const chunk of chunks) {
|
|
59
|
+
result.set(chunk, offset);
|
|
60
|
+
offset += chunk.length;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return new TextDecoder().decode(result);
|
|
64
|
+
};
|
|
6
65
|
|
|
7
66
|
const hashCacheKey = (object?: Record<string, string>) => {
|
|
8
67
|
if (!object) {
|
|
@@ -19,34 +78,37 @@ const hashCacheKey = (object?: Record<string, string>) => {
|
|
|
19
78
|
return `_${encodeURIComponent(cacheKey)}`;
|
|
20
79
|
};
|
|
21
80
|
export const CacheKey = {
|
|
22
|
-
List: (searchParams:
|
|
81
|
+
List: (searchParams: SearchParams, headers?: Record<string, string>) =>
|
|
23
82
|
`list_${encodeURIComponent(JSON.stringify(searchParams))}${hashCacheKey(
|
|
24
83
|
headers
|
|
25
84
|
)}`,
|
|
26
85
|
Category: (
|
|
27
86
|
pk: number,
|
|
28
|
-
searchParams?:
|
|
87
|
+
searchParams?: SearchParams,
|
|
29
88
|
headers?: Record<string, string>
|
|
30
89
|
) =>
|
|
31
90
|
`category_${pk}_${encodeURIComponent(
|
|
32
91
|
JSON.stringify(searchParams)
|
|
33
92
|
)}${hashCacheKey(headers)}`,
|
|
93
|
+
Basket: (namespace?: string) => `basket${namespace ? `_${namespace}` : ''}`,
|
|
94
|
+
AllBaskets: () => 'all_baskets',
|
|
34
95
|
CategorySlug: (slug: string) => `category_${slug}`,
|
|
35
96
|
SpecialPage: (
|
|
36
97
|
pk: number,
|
|
37
|
-
searchParams:
|
|
98
|
+
searchParams: SearchParams,
|
|
38
99
|
headers?: Record<string, string>
|
|
39
100
|
) =>
|
|
40
101
|
`special_page_${pk}_${encodeURIComponent(
|
|
41
102
|
JSON.stringify(searchParams)
|
|
42
103
|
)}${hashCacheKey(headers)}`,
|
|
43
|
-
Product: (pk: number, searchParams:
|
|
104
|
+
Product: (pk: number, searchParams: SearchParams) =>
|
|
44
105
|
`product_${pk}_${encodeURIComponent(JSON.stringify(searchParams))}`,
|
|
45
|
-
GroupProduct: (pk: number, searchParams:
|
|
106
|
+
GroupProduct: (pk: number, searchParams: SearchParams) =>
|
|
46
107
|
`group_product_${pk}_${encodeURIComponent(JSON.stringify(searchParams))}`,
|
|
47
108
|
FlatPage: (pk: number) => `flat_page_${pk}`,
|
|
48
109
|
LandingPage: (pk: number) => `landing_page_${pk}`,
|
|
49
110
|
Widget: (slug: string) => `widget_${slug}`,
|
|
111
|
+
WidgetSchema: (widgetSlug: string) => `widget_schema_${widgetSlug}`,
|
|
50
112
|
PrettyUrl: (pathname: string) => `pretty_url_${pathname}`,
|
|
51
113
|
Menu: (depth: number, parent?: string) =>
|
|
52
114
|
`menu_${depth}${parent ? `_${parent}` : ''}`,
|
|
@@ -58,8 +120,32 @@ export const CacheKey = {
|
|
|
58
120
|
export class Cache {
|
|
59
121
|
static PROXY_URL = `${process.env.NEXT_PUBLIC_URL}/api/cache`;
|
|
60
122
|
|
|
123
|
+
private static serializeValue(value: any): string {
|
|
124
|
+
return typeof value === 'object' ? JSON.stringify(value) : String(value);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
private static validateKey(key: string): boolean {
|
|
128
|
+
return !(!key || key.trim() === '');
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
private static validateKeyValuePairs(keyValuePairs: Record<string, any>): {
|
|
132
|
+
isValid: boolean;
|
|
133
|
+
invalidKeys: string[];
|
|
134
|
+
} {
|
|
135
|
+
if (!keyValuePairs || Object.keys(keyValuePairs).length === 0) {
|
|
136
|
+
return { isValid: false, invalidKeys: [] };
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const invalidKeys = Object.keys(keyValuePairs).filter(
|
|
140
|
+
(key) => !this.validateKey(key)
|
|
141
|
+
);
|
|
142
|
+
return { isValid: invalidKeys.length === 0, invalidKeys };
|
|
143
|
+
}
|
|
144
|
+
|
|
61
145
|
static formatKey(key: string, locale: string) {
|
|
62
|
-
return encodeURIComponent(
|
|
146
|
+
return encodeURIComponent(
|
|
147
|
+
`${CACHE_VERSION}_${Settings.commerceUrl}_${locale}_${key}`
|
|
148
|
+
);
|
|
63
149
|
}
|
|
64
150
|
|
|
65
151
|
static clientPool: Pool<RedisClientType> = createPool(
|
|
@@ -70,9 +156,14 @@ export class Cache {
|
|
|
70
156
|
process.env.CACHE_PORT
|
|
71
157
|
}/${process.env.CACHE_BUCKET ?? '0'}`;
|
|
72
158
|
|
|
73
|
-
const
|
|
74
|
-
url: redisUrl
|
|
75
|
-
|
|
159
|
+
const options = {
|
|
160
|
+
url: redisUrl,
|
|
161
|
+
...(process.env.CACHE_PASSWORD && {
|
|
162
|
+
password: process.env.CACHE_PASSWORD
|
|
163
|
+
})
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
const client: RedisClientType = createClient(options);
|
|
76
167
|
|
|
77
168
|
client.on('error', (error) => {
|
|
78
169
|
logger.error('Redis client error', { redisUrl, error });
|
|
@@ -96,9 +187,9 @@ export class Cache {
|
|
|
96
187
|
return await Cache.clientPool.acquire();
|
|
97
188
|
}
|
|
98
189
|
|
|
99
|
-
static async get(key: string) {
|
|
100
|
-
let value;
|
|
101
|
-
let client;
|
|
190
|
+
static async get(key: string): Promise<any> {
|
|
191
|
+
let value: any;
|
|
192
|
+
let client: RedisClientType | undefined;
|
|
102
193
|
|
|
103
194
|
try {
|
|
104
195
|
client = await Cache.getClient();
|
|
@@ -108,9 +199,7 @@ export class Cache {
|
|
|
108
199
|
} else {
|
|
109
200
|
value = null;
|
|
110
201
|
}
|
|
111
|
-
logger.debug('Redis get success', { key, value });
|
|
112
202
|
} catch (error) {
|
|
113
|
-
logger.error('Redis get error', { key, error });
|
|
114
203
|
value = null;
|
|
115
204
|
} finally {
|
|
116
205
|
if (client) {
|
|
@@ -121,14 +210,13 @@ export class Cache {
|
|
|
121
210
|
return value;
|
|
122
211
|
}
|
|
123
212
|
|
|
124
|
-
static async set(key: string, value: any, expire?: number) {
|
|
213
|
+
static async set(key: string, value: any, expire?: number): Promise<boolean> {
|
|
125
214
|
let success = false;
|
|
126
|
-
let client;
|
|
215
|
+
let client: RedisClientType | undefined;
|
|
127
216
|
|
|
128
217
|
try {
|
|
129
218
|
client = await Cache.getClient();
|
|
130
|
-
const serializedValue =
|
|
131
|
-
typeof value === 'object' ? JSON.stringify(value) : value;
|
|
219
|
+
const serializedValue = Cache.serializeValue(value);
|
|
132
220
|
|
|
133
221
|
if (expire) {
|
|
134
222
|
await client.set(key, serializedValue, { EX: expire });
|
|
@@ -137,9 +225,7 @@ export class Cache {
|
|
|
137
225
|
}
|
|
138
226
|
|
|
139
227
|
success = true;
|
|
140
|
-
logger.debug('Redis set success', { key, value });
|
|
141
228
|
} catch (error) {
|
|
142
|
-
logger.error('Redis set error', { key, error });
|
|
143
229
|
success = false;
|
|
144
230
|
} finally {
|
|
145
231
|
if (client) {
|
|
@@ -156,10 +242,6 @@ export class Cache {
|
|
|
156
242
|
handler: () => Promise<T>,
|
|
157
243
|
options?: CacheOptions
|
|
158
244
|
): Promise<T> {
|
|
159
|
-
if (Settings.usePrettyUrlRoute) {
|
|
160
|
-
return await handler();
|
|
161
|
-
}
|
|
162
|
-
|
|
163
245
|
const requiredVariables = [
|
|
164
246
|
process.env.CACHE_HOST,
|
|
165
247
|
process.env.CACHE_PORT,
|
|
@@ -172,27 +254,33 @@ export class Cache {
|
|
|
172
254
|
|
|
173
255
|
const defaultOptions: CacheOptions = {
|
|
174
256
|
cache: true,
|
|
175
|
-
expire: Settings.redis.defaultExpirationTime
|
|
257
|
+
expire: Settings.redis.defaultExpirationTime,
|
|
258
|
+
compressed: process.env.CACHE_COMPRESSION_ENABLED !== 'false'
|
|
176
259
|
};
|
|
177
260
|
|
|
178
261
|
const _options = Object.assign(defaultOptions, options);
|
|
179
262
|
const formattedKey = Cache.formatKey(key, locale);
|
|
180
263
|
|
|
181
|
-
|
|
264
|
+
if (Settings.usePrettyUrlRoute) {
|
|
265
|
+
_options.expire = 120;
|
|
266
|
+
}
|
|
182
267
|
|
|
183
268
|
if (_options.cache) {
|
|
184
|
-
let cachedValue;
|
|
269
|
+
let cachedValue: any;
|
|
185
270
|
|
|
186
271
|
if (_options.useProxy) {
|
|
187
272
|
const body = new URLSearchParams();
|
|
188
273
|
|
|
189
274
|
body.append('key', formattedKey);
|
|
275
|
+
if (_options.compressed) {
|
|
276
|
+
body.append('compressed', 'true');
|
|
277
|
+
}
|
|
190
278
|
|
|
191
279
|
cachedValue = await Cache.proxyRequest('POST', body);
|
|
192
|
-
logger.debug('Cache proxy request success', { key });
|
|
193
|
-
logger.trace('Cache proxy request', { key, cachedValue });
|
|
194
280
|
} else {
|
|
195
|
-
cachedValue =
|
|
281
|
+
cachedValue = _options.compressed
|
|
282
|
+
? await Cache.getCompressed(formattedKey)
|
|
283
|
+
: await Cache.get(formattedKey);
|
|
196
284
|
}
|
|
197
285
|
|
|
198
286
|
if (cachedValue) {
|
|
@@ -200,8 +288,6 @@ export class Cache {
|
|
|
200
288
|
}
|
|
201
289
|
}
|
|
202
290
|
|
|
203
|
-
logger.debug('Redis cache miss. Setting new value...', { key });
|
|
204
|
-
|
|
205
291
|
const data = await handler();
|
|
206
292
|
|
|
207
293
|
if (data && _options.cache) {
|
|
@@ -215,14 +301,19 @@ export class Cache {
|
|
|
215
301
|
'expire',
|
|
216
302
|
String(_options?.expire ?? Settings.redis.defaultExpirationTime)
|
|
217
303
|
);
|
|
304
|
+
if (_options.compressed) {
|
|
305
|
+
body.append('compressed', 'true');
|
|
306
|
+
}
|
|
218
307
|
await Cache.proxyRequest('PUT', body);
|
|
219
|
-
|
|
220
|
-
logger.debug('Cache proxy request', { key, body: body.toString() });
|
|
221
308
|
} catch (error) {
|
|
222
309
|
logger.error('Cache proxy error', error);
|
|
223
310
|
}
|
|
224
311
|
} else {
|
|
225
|
-
|
|
312
|
+
if (_options.compressed) {
|
|
313
|
+
await Cache.setCompressed(formattedKey, data, _options?.expire);
|
|
314
|
+
} else {
|
|
315
|
+
await Cache.set(formattedKey, JSON.stringify(data), _options?.expire);
|
|
316
|
+
}
|
|
226
317
|
}
|
|
227
318
|
}
|
|
228
319
|
|
|
@@ -234,7 +325,7 @@ export class Cache {
|
|
|
234
325
|
await fetch(Cache.PROXY_URL, {
|
|
235
326
|
method,
|
|
236
327
|
headers: {
|
|
237
|
-
authorization: process.env.CACHE_SECRET
|
|
328
|
+
authorization: process.env.CACHE_SECRET || ''
|
|
238
329
|
},
|
|
239
330
|
body
|
|
240
331
|
})
|
|
@@ -242,4 +333,148 @@ export class Cache {
|
|
|
242
333
|
|
|
243
334
|
return response;
|
|
244
335
|
}
|
|
336
|
+
|
|
337
|
+
static async mset(
|
|
338
|
+
keyValuePairs: Record<string, any>,
|
|
339
|
+
expire?: number
|
|
340
|
+
): Promise<boolean> {
|
|
341
|
+
const validation = Cache.validateKeyValuePairs(keyValuePairs);
|
|
342
|
+
if (!validation.isValid) {
|
|
343
|
+
if (validation.invalidKeys.length > 0) {
|
|
344
|
+
logger.error('Invalid keys in mset', {
|
|
345
|
+
invalidKeys: validation.invalidKeys
|
|
346
|
+
});
|
|
347
|
+
} else {
|
|
348
|
+
logger.warn('mset called with empty keyValuePairs');
|
|
349
|
+
}
|
|
350
|
+
return false;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
let success = false;
|
|
354
|
+
let client: RedisClientType | undefined;
|
|
355
|
+
|
|
356
|
+
try {
|
|
357
|
+
client = await Cache.getClient();
|
|
358
|
+
const pipeline = client.multi();
|
|
359
|
+
|
|
360
|
+
Object.entries(keyValuePairs).forEach(([key, value]) => {
|
|
361
|
+
const serializedValue = Cache.serializeValue(value);
|
|
362
|
+
if (expire) {
|
|
363
|
+
pipeline.set(key, serializedValue, { EX: expire });
|
|
364
|
+
} else {
|
|
365
|
+
pipeline.set(key, serializedValue);
|
|
366
|
+
}
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
const results = await pipeline.exec();
|
|
370
|
+
|
|
371
|
+
const failures =
|
|
372
|
+
results?.filter((result) => result instanceof Error) || [];
|
|
373
|
+
|
|
374
|
+
if (failures.length > 0) {
|
|
375
|
+
success = false;
|
|
376
|
+
} else {
|
|
377
|
+
success = true;
|
|
378
|
+
}
|
|
379
|
+
} catch (error) {
|
|
380
|
+
success = false;
|
|
381
|
+
} finally {
|
|
382
|
+
if (client) {
|
|
383
|
+
await Cache.clientPool.release(client);
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
return success;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
static async setCompressed(
|
|
391
|
+
key: string,
|
|
392
|
+
value: any,
|
|
393
|
+
expire?: number
|
|
394
|
+
): Promise<boolean> {
|
|
395
|
+
if (!Cache.validateKey(key)) {
|
|
396
|
+
return false;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
let success = false;
|
|
400
|
+
let client: RedisClientType | undefined;
|
|
401
|
+
|
|
402
|
+
try {
|
|
403
|
+
client = await Cache.getClient();
|
|
404
|
+
const serializedValue = Cache.serializeValue(value);
|
|
405
|
+
|
|
406
|
+
try {
|
|
407
|
+
const compressed = await compressData(serializedValue);
|
|
408
|
+
const compressedBase64 = Buffer.from(compressed).toString('base64');
|
|
409
|
+
|
|
410
|
+
if (expire) {
|
|
411
|
+
await client.set(key, compressedBase64, { EX: expire });
|
|
412
|
+
} else {
|
|
413
|
+
await client.set(key, compressedBase64);
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
success = true;
|
|
417
|
+
} catch (compressionError) {
|
|
418
|
+
if (expire) {
|
|
419
|
+
await client.set(key, serializedValue, { EX: expire });
|
|
420
|
+
} else {
|
|
421
|
+
await client.set(key, serializedValue);
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
success = true;
|
|
425
|
+
}
|
|
426
|
+
} catch (error) {
|
|
427
|
+
success = false;
|
|
428
|
+
} finally {
|
|
429
|
+
if (client) {
|
|
430
|
+
await Cache.clientPool.release(client);
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
return success;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
static async getCompressed(key: string): Promise<unknown> {
|
|
438
|
+
if (!Cache.validateKey(key)) {
|
|
439
|
+
return null;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
let value: unknown;
|
|
443
|
+
let client: RedisClientType | undefined;
|
|
444
|
+
|
|
445
|
+
try {
|
|
446
|
+
client = await Cache.getClient();
|
|
447
|
+
const compressed = await client.get(key);
|
|
448
|
+
|
|
449
|
+
if (compressed) {
|
|
450
|
+
const compressedBuffer = Buffer.from(compressed, 'base64');
|
|
451
|
+
|
|
452
|
+
try {
|
|
453
|
+
const decompressedString = await decompressData(
|
|
454
|
+
new Uint8Array(compressedBuffer)
|
|
455
|
+
);
|
|
456
|
+
value = JSON.parse(decompressedString);
|
|
457
|
+
return value;
|
|
458
|
+
} catch (decompressionError) {
|
|
459
|
+
try {
|
|
460
|
+
const rawString = compressed;
|
|
461
|
+
const parsedData = JSON.parse(rawString);
|
|
462
|
+
return parsedData;
|
|
463
|
+
} catch (jsonError) {
|
|
464
|
+
return null;
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
} else {
|
|
468
|
+
value = null;
|
|
469
|
+
}
|
|
470
|
+
} catch (error) {
|
|
471
|
+
value = null;
|
|
472
|
+
} finally {
|
|
473
|
+
if (client) {
|
|
474
|
+
await Cache.clientPool.release(client);
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
return value;
|
|
479
|
+
}
|
|
245
480
|
}
|
package/localization/index.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import React, { createContext } from 'react';
|
|
4
4
|
import { getTranslateFn } from '../utils';
|
|
5
|
-
import {
|
|
5
|
+
import { usePzParams } from '../hooks/use-pz-params';
|
|
6
6
|
import { Currency, Locale } from '../types';
|
|
7
7
|
import settings from 'settings';
|
|
8
8
|
|
|
@@ -34,10 +34,7 @@ export default function LocalizationProvider({
|
|
|
34
34
|
localeUrlStrategy
|
|
35
35
|
} = settings.localization;
|
|
36
36
|
|
|
37
|
-
const { locale, currency } =
|
|
38
|
-
locale: string;
|
|
39
|
-
currency: string;
|
|
40
|
-
};
|
|
37
|
+
const { locale, currency } = usePzParams();
|
|
41
38
|
|
|
42
39
|
return (
|
|
43
40
|
<LocalizationContext.Provider
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { NextMiddleware } from 'next/server';
|
|
2
|
+
|
|
3
|
+
const withBfcacheHeaders = (middleware: NextMiddleware): NextMiddleware => {
|
|
4
|
+
return async (req, event) => {
|
|
5
|
+
const response = await middleware(req, event);
|
|
6
|
+
|
|
7
|
+
if (process.env.BF_CACHE === 'true' && response) {
|
|
8
|
+
response.headers.set(
|
|
9
|
+
'Cache-Control',
|
|
10
|
+
'private, no-cache, max-age=0, must-revalidate'
|
|
11
|
+
);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
return response;
|
|
15
|
+
};
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export default withBfcacheHeaders;
|
|
@@ -64,7 +64,7 @@ const withCheckoutProvider =
|
|
|
64
64
|
const location = request.headers.get('location');
|
|
65
65
|
const redirectUrl = new URL(
|
|
66
66
|
request.headers.get('location'),
|
|
67
|
-
location.startsWith('http') ? '' :
|
|
67
|
+
location.startsWith('http') ? '' : url.origin
|
|
68
68
|
);
|
|
69
69
|
|
|
70
70
|
redirectUrl.pathname = getUrlPathWithLocale(
|
|
@@ -4,6 +4,7 @@ import { Buffer } from 'buffer';
|
|
|
4
4
|
import logger from '../utils/log';
|
|
5
5
|
import { getUrlPathWithLocale } from '../utils/localization';
|
|
6
6
|
import { PzNextRequest } from '.';
|
|
7
|
+
import { getCheckoutPath } from '../utils';
|
|
7
8
|
|
|
8
9
|
const streamToString = async (stream: ReadableStream<Uint8Array> | null) => {
|
|
9
10
|
if (stream) {
|
|
@@ -34,18 +35,22 @@ const withCompleteGpay =
|
|
|
34
35
|
const url = req.nextUrl.clone();
|
|
35
36
|
const ip = req.headers.get('x-forwarded-for') ?? '';
|
|
36
37
|
const sessionId = req.cookies.get('osessionid');
|
|
38
|
+
const currentLocale = req.middlewareParams?.rewrites?.locale;
|
|
37
39
|
|
|
38
40
|
if (url.search.indexOf('GPayCompletePage') === -1) {
|
|
39
41
|
return middleware(req, event);
|
|
40
42
|
}
|
|
41
43
|
|
|
42
|
-
const
|
|
44
|
+
const isPostCheckout = req.cookies.get('pz-post-checkout-flow')?.value === 'true';
|
|
45
|
+
const requestUrl = `${Settings.commerceUrl}${getCheckoutPath(isPostCheckout)}${url.search}`;
|
|
43
46
|
const requestHeaders = {
|
|
44
47
|
'X-Requested-With': 'XMLHttpRequest',
|
|
45
48
|
'Content-Type': 'application/x-www-form-urlencoded',
|
|
46
49
|
Cookie: req.headers.get('cookie') ?? '',
|
|
47
50
|
'x-currency': req.cookies.get('pz-currency')?.value ?? '',
|
|
48
|
-
'x-forwarded-for': ip
|
|
51
|
+
'x-forwarded-for': ip,
|
|
52
|
+
'Accept-Language':
|
|
53
|
+
currentLocale ?? req.cookies.get('pz-locale')?.value ?? ''
|
|
49
54
|
};
|
|
50
55
|
|
|
51
56
|
try {
|
|
@@ -59,17 +64,9 @@ const withCompleteGpay =
|
|
|
59
64
|
ip
|
|
60
65
|
}
|
|
61
66
|
);
|
|
62
|
-
|
|
63
|
-
return NextResponse.redirect(
|
|
64
|
-
`${url.origin}${getUrlPathWithLocale(
|
|
65
|
-
'/orders/checkout/',
|
|
66
|
-
req.cookies.get('pz-locale')?.value
|
|
67
|
-
)}`,
|
|
68
|
-
303
|
|
69
|
-
);
|
|
70
67
|
}
|
|
71
68
|
|
|
72
|
-
const
|
|
69
|
+
const fetchResponse = await fetch(requestUrl, {
|
|
73
70
|
method: 'POST',
|
|
74
71
|
headers: requestHeaders,
|
|
75
72
|
body
|
|
@@ -77,14 +74,14 @@ const withCompleteGpay =
|
|
|
77
74
|
|
|
78
75
|
logger.info('Complete GPay payment request', {
|
|
79
76
|
requestUrl,
|
|
80
|
-
status:
|
|
77
|
+
status: fetchResponse.status,
|
|
81
78
|
requestHeaders,
|
|
82
79
|
ip
|
|
83
80
|
});
|
|
84
81
|
|
|
85
|
-
const
|
|
82
|
+
const responseData = await fetchResponse.json();
|
|
86
83
|
|
|
87
|
-
const { context_list: contextList, errors } =
|
|
84
|
+
const { context_list: contextList, errors } = responseData;
|
|
88
85
|
const redirectionContext = contextList?.find(
|
|
89
86
|
(context) => context.page_context?.redirect_url
|
|
90
87
|
);
|
|
@@ -98,18 +95,26 @@ const withCompleteGpay =
|
|
|
98
95
|
ip
|
|
99
96
|
});
|
|
100
97
|
|
|
101
|
-
|
|
98
|
+
const errorResponse = NextResponse.redirect(
|
|
102
99
|
`${url.origin}${getUrlPathWithLocale(
|
|
103
100
|
'/orders/checkout/',
|
|
104
101
|
req.cookies.get('pz-locale')?.value
|
|
105
102
|
)}`,
|
|
106
|
-
|
|
107
|
-
status: 303,
|
|
108
|
-
headers: {
|
|
109
|
-
'Set-Cookie': `pz-pos-error=${JSON.stringify(errors)}; path=/;`
|
|
110
|
-
}
|
|
111
|
-
}
|
|
103
|
+
303
|
|
112
104
|
);
|
|
105
|
+
|
|
106
|
+
// Forward set-cookie headers from the upstream response
|
|
107
|
+
const setCookies = fetchResponse.headers.getSetCookie?.() ?? [];
|
|
108
|
+
setCookies.forEach((cookie) => {
|
|
109
|
+
errorResponse.headers.append('Set-Cookie', cookie);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
// Add error cookie
|
|
113
|
+
errorResponse.cookies.set('pz-pos-error', JSON.stringify(errors), {
|
|
114
|
+
path: '/'
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
return errorResponse;
|
|
113
118
|
}
|
|
114
119
|
|
|
115
120
|
logger.info('Order success page context list', {
|
|
@@ -124,7 +129,7 @@ const withCompleteGpay =
|
|
|
124
129
|
{
|
|
125
130
|
middleware: 'complete-gpay',
|
|
126
131
|
requestHeaders,
|
|
127
|
-
response: JSON.stringify(
|
|
132
|
+
response: JSON.stringify(responseData),
|
|
128
133
|
ip
|
|
129
134
|
}
|
|
130
135
|
);
|
|
@@ -152,10 +157,11 @@ const withCompleteGpay =
|
|
|
152
157
|
// So we use 303 status code to change the method to GET
|
|
153
158
|
const nextResponse = NextResponse.redirect(redirectUrlWithLocale, 303);
|
|
154
159
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
160
|
+
// Forward all set-cookie headers from the upstream response
|
|
161
|
+
const setCookies = fetchResponse.headers.getSetCookie?.() ?? [];
|
|
162
|
+
setCookies.forEach((cookie) => {
|
|
163
|
+
nextResponse.headers.append('Set-Cookie', cookie);
|
|
164
|
+
});
|
|
159
165
|
|
|
160
166
|
return nextResponse;
|
|
161
167
|
} catch (error) {
|