@akinon/next 1.99.0-snapshot-ZERO-3640-20250919140935 → 1.100.0-rc.71
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/CHANGELOG.md +1337 -51
- package/__tests__/next-config.test.ts +1 -10
- package/__tests__/redirect.test.ts +319 -0
- package/api/cache.ts +41 -5
- package/api/form.ts +84 -0
- package/api/image-proxy.ts +75 -0
- package/api/similar-product-list.ts +84 -0
- package/api/similar-products.ts +120 -0
- package/bin/pz-generate-routes.js +105 -0
- package/bin/pz-prebuild.js +1 -0
- package/bin/pz-predev.js +1 -0
- package/components/accordion.tsx +20 -5
- package/components/file-input.tsx +65 -3
- package/components/input.tsx +2 -0
- package/components/link.tsx +16 -12
- package/components/modal.tsx +32 -16
- package/components/plugin-module.tsx +32 -4
- package/data/client/checkout.ts +4 -2
- package/data/server/basket.ts +72 -0
- package/data/server/category.ts +48 -26
- package/data/server/flatpage.ts +20 -13
- package/data/server/form.ts +4 -1
- package/data/server/landingpage.ts +20 -13
- package/data/server/list.ts +25 -14
- package/data/server/menu.ts +4 -1
- package/data/server/product.ts +68 -40
- package/data/server/seo.ts +4 -1
- package/data/server/special-page.ts +18 -13
- package/data/server/widget.ts +4 -1
- package/data/urls.ts +6 -2
- package/hocs/server/with-segment-defaults.tsx +5 -2
- package/hooks/use-localization.ts +2 -3
- package/jest.config.js +7 -1
- package/lib/cache-handler.mjs +365 -87
- package/lib/cache.ts +254 -25
- package/middlewares/complete-gpay.ts +2 -1
- package/middlewares/complete-masterpass.ts +2 -1
- package/middlewares/default.ts +50 -13
- package/middlewares/locale.ts +9 -1
- package/middlewares/pretty-url.ts +21 -6
- package/middlewares/redirection-payment.ts +2 -1
- package/middlewares/saved-card-redirection.ts +2 -1
- package/middlewares/three-d-redirection.ts +2 -2
- package/middlewares/url-redirection.ts +8 -14
- package/package.json +4 -3
- package/plugins.d.ts +8 -5
- package/plugins.js +3 -1
- package/redux/middlewares/checkout.ts +5 -1
- package/types/commerce/order.ts +1 -0
- package/types/index.ts +35 -1
- package/utils/app-fetch.ts +7 -2
- package/utils/redirect-ignore.ts +35 -0
- package/utils/redirect.ts +31 -6
- package/with-pz-config.js +1 -5
package/lib/cache.ts
CHANGED
|
@@ -3,6 +3,65 @@ import { RedisClientType } from 'redis';
|
|
|
3
3
|
import Settings from 'settings';
|
|
4
4
|
import { CacheOptions } 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);
|
|
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) {
|
|
@@ -31,6 +90,8 @@ export const CacheKey = {
|
|
|
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,
|
|
@@ -58,8 +119,32 @@ export const CacheKey = {
|
|
|
58
119
|
export class Cache {
|
|
59
120
|
static PROXY_URL = `${process.env.NEXT_PUBLIC_URL}/api/cache`;
|
|
60
121
|
|
|
122
|
+
private static serializeValue(value: any): string {
|
|
123
|
+
return typeof value === 'object' ? JSON.stringify(value) : String(value);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
private static validateKey(key: string): boolean {
|
|
127
|
+
return !(!key || key.trim() === '');
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
private static validateKeyValuePairs(keyValuePairs: Record<string, any>): {
|
|
131
|
+
isValid: boolean;
|
|
132
|
+
invalidKeys: string[];
|
|
133
|
+
} {
|
|
134
|
+
if (!keyValuePairs || Object.keys(keyValuePairs).length === 0) {
|
|
135
|
+
return { isValid: false, invalidKeys: [] };
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const invalidKeys = Object.keys(keyValuePairs).filter(
|
|
139
|
+
(key) => !this.validateKey(key)
|
|
140
|
+
);
|
|
141
|
+
return { isValid: invalidKeys.length === 0, invalidKeys };
|
|
142
|
+
}
|
|
143
|
+
|
|
61
144
|
static formatKey(key: string, locale: string) {
|
|
62
|
-
return encodeURIComponent(
|
|
145
|
+
return encodeURIComponent(
|
|
146
|
+
`${CACHE_VERSION}_${Settings.commerceUrl}_${locale}_${key}`
|
|
147
|
+
);
|
|
63
148
|
}
|
|
64
149
|
|
|
65
150
|
static clientPool: Pool<RedisClientType> = createPool(
|
|
@@ -96,9 +181,9 @@ export class Cache {
|
|
|
96
181
|
return await Cache.clientPool.acquire();
|
|
97
182
|
}
|
|
98
183
|
|
|
99
|
-
static async get(key: string) {
|
|
100
|
-
let value;
|
|
101
|
-
let client;
|
|
184
|
+
static async get(key: string): Promise<any> {
|
|
185
|
+
let value: any;
|
|
186
|
+
let client: RedisClientType | undefined;
|
|
102
187
|
|
|
103
188
|
try {
|
|
104
189
|
client = await Cache.getClient();
|
|
@@ -108,9 +193,7 @@ export class Cache {
|
|
|
108
193
|
} else {
|
|
109
194
|
value = null;
|
|
110
195
|
}
|
|
111
|
-
logger.debug('Redis get success', { key, value });
|
|
112
196
|
} catch (error) {
|
|
113
|
-
logger.error('Redis get error', { key, error });
|
|
114
197
|
value = null;
|
|
115
198
|
} finally {
|
|
116
199
|
if (client) {
|
|
@@ -121,14 +204,13 @@ export class Cache {
|
|
|
121
204
|
return value;
|
|
122
205
|
}
|
|
123
206
|
|
|
124
|
-
static async set(key: string, value: any, expire?: number) {
|
|
207
|
+
static async set(key: string, value: any, expire?: number): Promise<boolean> {
|
|
125
208
|
let success = false;
|
|
126
|
-
let client;
|
|
209
|
+
let client: RedisClientType | undefined;
|
|
127
210
|
|
|
128
211
|
try {
|
|
129
212
|
client = await Cache.getClient();
|
|
130
|
-
const serializedValue =
|
|
131
|
-
typeof value === 'object' ? JSON.stringify(value) : value;
|
|
213
|
+
const serializedValue = Cache.serializeValue(value);
|
|
132
214
|
|
|
133
215
|
if (expire) {
|
|
134
216
|
await client.set(key, serializedValue, { EX: expire });
|
|
@@ -137,9 +219,7 @@ export class Cache {
|
|
|
137
219
|
}
|
|
138
220
|
|
|
139
221
|
success = true;
|
|
140
|
-
logger.debug('Redis set success', { key, value });
|
|
141
222
|
} catch (error) {
|
|
142
|
-
logger.error('Redis set error', { key, error });
|
|
143
223
|
success = false;
|
|
144
224
|
} finally {
|
|
145
225
|
if (client) {
|
|
@@ -168,7 +248,8 @@ export class Cache {
|
|
|
168
248
|
|
|
169
249
|
const defaultOptions: CacheOptions = {
|
|
170
250
|
cache: true,
|
|
171
|
-
expire: Settings.redis.defaultExpirationTime
|
|
251
|
+
expire: Settings.redis.defaultExpirationTime,
|
|
252
|
+
compressed: process.env.CACHE_COMPRESSION_ENABLED !== 'false'
|
|
172
253
|
};
|
|
173
254
|
|
|
174
255
|
const _options = Object.assign(defaultOptions, options);
|
|
@@ -178,21 +259,22 @@ export class Cache {
|
|
|
178
259
|
_options.expire = 120;
|
|
179
260
|
}
|
|
180
261
|
|
|
181
|
-
logger.debug('Cache wrap', { key, formattedKey, _options });
|
|
182
|
-
|
|
183
262
|
if (_options.cache) {
|
|
184
|
-
let cachedValue;
|
|
263
|
+
let cachedValue: any;
|
|
185
264
|
|
|
186
265
|
if (_options.useProxy) {
|
|
187
266
|
const body = new URLSearchParams();
|
|
188
267
|
|
|
189
268
|
body.append('key', formattedKey);
|
|
269
|
+
if (_options.compressed) {
|
|
270
|
+
body.append('compressed', 'true');
|
|
271
|
+
}
|
|
190
272
|
|
|
191
273
|
cachedValue = await Cache.proxyRequest('POST', body);
|
|
192
|
-
logger.debug('Cache proxy request success', { key });
|
|
193
|
-
logger.trace('Cache proxy request', { key, cachedValue });
|
|
194
274
|
} else {
|
|
195
|
-
cachedValue =
|
|
275
|
+
cachedValue = _options.compressed
|
|
276
|
+
? await Cache.getCompressed(formattedKey)
|
|
277
|
+
: await Cache.get(formattedKey);
|
|
196
278
|
}
|
|
197
279
|
|
|
198
280
|
if (cachedValue) {
|
|
@@ -200,8 +282,6 @@ export class Cache {
|
|
|
200
282
|
}
|
|
201
283
|
}
|
|
202
284
|
|
|
203
|
-
logger.debug('Redis cache miss. Setting new value...', { key });
|
|
204
|
-
|
|
205
285
|
const data = await handler();
|
|
206
286
|
|
|
207
287
|
if (data && _options.cache) {
|
|
@@ -215,14 +295,19 @@ export class Cache {
|
|
|
215
295
|
'expire',
|
|
216
296
|
String(_options?.expire ?? Settings.redis.defaultExpirationTime)
|
|
217
297
|
);
|
|
298
|
+
if (_options.compressed) {
|
|
299
|
+
body.append('compressed', 'true');
|
|
300
|
+
}
|
|
218
301
|
await Cache.proxyRequest('PUT', body);
|
|
219
|
-
|
|
220
|
-
logger.debug('Cache proxy request', { key, body: body.toString() });
|
|
221
302
|
} catch (error) {
|
|
222
303
|
logger.error('Cache proxy error', error);
|
|
223
304
|
}
|
|
224
305
|
} else {
|
|
225
|
-
|
|
306
|
+
if (_options.compressed) {
|
|
307
|
+
await Cache.setCompressed(formattedKey, data, _options?.expire);
|
|
308
|
+
} else {
|
|
309
|
+
await Cache.set(formattedKey, JSON.stringify(data), _options?.expire);
|
|
310
|
+
}
|
|
226
311
|
}
|
|
227
312
|
}
|
|
228
313
|
|
|
@@ -234,7 +319,7 @@ export class Cache {
|
|
|
234
319
|
await fetch(Cache.PROXY_URL, {
|
|
235
320
|
method,
|
|
236
321
|
headers: {
|
|
237
|
-
authorization: process.env.CACHE_SECRET
|
|
322
|
+
authorization: process.env.CACHE_SECRET || ''
|
|
238
323
|
},
|
|
239
324
|
body
|
|
240
325
|
})
|
|
@@ -242,4 +327,148 @@ export class Cache {
|
|
|
242
327
|
|
|
243
328
|
return response;
|
|
244
329
|
}
|
|
330
|
+
|
|
331
|
+
static async mset(
|
|
332
|
+
keyValuePairs: Record<string, any>,
|
|
333
|
+
expire?: number
|
|
334
|
+
): Promise<boolean> {
|
|
335
|
+
const validation = Cache.validateKeyValuePairs(keyValuePairs);
|
|
336
|
+
if (!validation.isValid) {
|
|
337
|
+
if (validation.invalidKeys.length > 0) {
|
|
338
|
+
logger.error('Invalid keys in mset', {
|
|
339
|
+
invalidKeys: validation.invalidKeys
|
|
340
|
+
});
|
|
341
|
+
} else {
|
|
342
|
+
logger.warn('mset called with empty keyValuePairs');
|
|
343
|
+
}
|
|
344
|
+
return false;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
let success = false;
|
|
348
|
+
let client: RedisClientType | undefined;
|
|
349
|
+
|
|
350
|
+
try {
|
|
351
|
+
client = await Cache.getClient();
|
|
352
|
+
const pipeline = client.multi();
|
|
353
|
+
|
|
354
|
+
Object.entries(keyValuePairs).forEach(([key, value]) => {
|
|
355
|
+
const serializedValue = Cache.serializeValue(value);
|
|
356
|
+
if (expire) {
|
|
357
|
+
pipeline.set(key, serializedValue, { EX: expire });
|
|
358
|
+
} else {
|
|
359
|
+
pipeline.set(key, serializedValue);
|
|
360
|
+
}
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
const results = await pipeline.exec();
|
|
364
|
+
|
|
365
|
+
const failures =
|
|
366
|
+
results?.filter((result) => result instanceof Error) || [];
|
|
367
|
+
|
|
368
|
+
if (failures.length > 0) {
|
|
369
|
+
success = false;
|
|
370
|
+
} else {
|
|
371
|
+
success = true;
|
|
372
|
+
}
|
|
373
|
+
} catch (error) {
|
|
374
|
+
success = false;
|
|
375
|
+
} finally {
|
|
376
|
+
if (client) {
|
|
377
|
+
await Cache.clientPool.release(client);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
return success;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
static async setCompressed(
|
|
385
|
+
key: string,
|
|
386
|
+
value: any,
|
|
387
|
+
expire?: number
|
|
388
|
+
): Promise<boolean> {
|
|
389
|
+
if (!Cache.validateKey(key)) {
|
|
390
|
+
return false;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
let success = false;
|
|
394
|
+
let client: RedisClientType | undefined;
|
|
395
|
+
|
|
396
|
+
try {
|
|
397
|
+
client = await Cache.getClient();
|
|
398
|
+
const serializedValue = Cache.serializeValue(value);
|
|
399
|
+
|
|
400
|
+
try {
|
|
401
|
+
const compressed = await compressData(serializedValue);
|
|
402
|
+
const compressedBase64 = Buffer.from(compressed).toString('base64');
|
|
403
|
+
|
|
404
|
+
if (expire) {
|
|
405
|
+
await client.set(key, compressedBase64, { EX: expire });
|
|
406
|
+
} else {
|
|
407
|
+
await client.set(key, compressedBase64);
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
success = true;
|
|
411
|
+
} catch (compressionError) {
|
|
412
|
+
if (expire) {
|
|
413
|
+
await client.set(key, serializedValue, { EX: expire });
|
|
414
|
+
} else {
|
|
415
|
+
await client.set(key, serializedValue);
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
success = true;
|
|
419
|
+
}
|
|
420
|
+
} catch (error) {
|
|
421
|
+
success = false;
|
|
422
|
+
} finally {
|
|
423
|
+
if (client) {
|
|
424
|
+
await Cache.clientPool.release(client);
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
return success;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
static async getCompressed(key: string): Promise<unknown> {
|
|
432
|
+
if (!Cache.validateKey(key)) {
|
|
433
|
+
return null;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
let value: unknown;
|
|
437
|
+
let client: RedisClientType | undefined;
|
|
438
|
+
|
|
439
|
+
try {
|
|
440
|
+
client = await Cache.getClient();
|
|
441
|
+
const compressed = await client.get(key);
|
|
442
|
+
|
|
443
|
+
if (compressed) {
|
|
444
|
+
const compressedBuffer = Buffer.from(compressed, 'base64');
|
|
445
|
+
|
|
446
|
+
try {
|
|
447
|
+
const decompressedString = await decompressData(
|
|
448
|
+
new Uint8Array(compressedBuffer)
|
|
449
|
+
);
|
|
450
|
+
value = JSON.parse(decompressedString);
|
|
451
|
+
return value;
|
|
452
|
+
} catch (decompressionError) {
|
|
453
|
+
try {
|
|
454
|
+
const rawString = compressed;
|
|
455
|
+
const parsedData = JSON.parse(rawString);
|
|
456
|
+
return parsedData;
|
|
457
|
+
} catch (jsonError) {
|
|
458
|
+
return null;
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
} else {
|
|
462
|
+
value = null;
|
|
463
|
+
}
|
|
464
|
+
} catch (error) {
|
|
465
|
+
value = null;
|
|
466
|
+
} finally {
|
|
467
|
+
if (client) {
|
|
468
|
+
await Cache.clientPool.release(client);
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
return value;
|
|
473
|
+
}
|
|
245
474
|
}
|
|
@@ -148,7 +148,8 @@ const withCompleteGpay =
|
|
|
148
148
|
logger.info('Redirecting to order success page', {
|
|
149
149
|
middleware: 'complete-gpay',
|
|
150
150
|
redirectUrlWithLocale,
|
|
151
|
-
ip
|
|
151
|
+
ip,
|
|
152
|
+
setCookie: request.headers.get('set-cookie')
|
|
152
153
|
});
|
|
153
154
|
|
|
154
155
|
// Using POST method while redirecting causes an error,
|
|
@@ -149,7 +149,8 @@ const withCompleteMasterpass =
|
|
|
149
149
|
logger.info('Redirecting to order success page', {
|
|
150
150
|
middleware: 'complete-masterpass',
|
|
151
151
|
redirectUrlWithLocale,
|
|
152
|
-
ip
|
|
152
|
+
ip,
|
|
153
|
+
setCookie: request.headers.get('set-cookie')
|
|
153
154
|
});
|
|
154
155
|
|
|
155
156
|
// Using POST method while redirecting causes an error,
|
package/middlewares/default.ts
CHANGED
|
@@ -302,19 +302,6 @@ const withPzDefault =
|
|
|
302
302
|
)}`;
|
|
303
303
|
}
|
|
304
304
|
|
|
305
|
-
if (
|
|
306
|
-
!req.middlewareParams.found &&
|
|
307
|
-
Settings.customNotFoundEnabled
|
|
308
|
-
) {
|
|
309
|
-
const pathname = url.pathname
|
|
310
|
-
.replace(/\/+$/, '')
|
|
311
|
-
.split('/');
|
|
312
|
-
url.pathname = url.pathname.replace(
|
|
313
|
-
pathname.pop(),
|
|
314
|
-
'pz-not-found'
|
|
315
|
-
);
|
|
316
|
-
}
|
|
317
|
-
|
|
318
305
|
Settings.rewrites.forEach((rewrite) => {
|
|
319
306
|
url.pathname = url.pathname.replace(
|
|
320
307
|
rewrite.source,
|
|
@@ -352,6 +339,24 @@ const withPzDefault =
|
|
|
352
339
|
middlewareResult = NextResponse.rewrite(url);
|
|
353
340
|
}
|
|
354
341
|
|
|
342
|
+
if (
|
|
343
|
+
!req.middlewareParams.found &&
|
|
344
|
+
Settings.customNotFoundEnabled
|
|
345
|
+
) {
|
|
346
|
+
const pathSegments = url.pathname
|
|
347
|
+
.replace(/\/+$/, '')
|
|
348
|
+
.split('/');
|
|
349
|
+
if (pathSegments.length >= 3) {
|
|
350
|
+
url.pathname = `/${pathSegments[1]}/${pathSegments[2]}/pz-not-found`;
|
|
351
|
+
} else {
|
|
352
|
+
url.pathname = '/pz-not-found';
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
middlewareResult = NextResponse.rewrite(url, {
|
|
356
|
+
status: 404
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
|
|
355
360
|
const { localeUrlStrategy } =
|
|
356
361
|
Settings.localization;
|
|
357
362
|
|
|
@@ -401,6 +406,38 @@ const withPzDefault =
|
|
|
401
406
|
}
|
|
402
407
|
);
|
|
403
408
|
|
|
409
|
+
if (
|
|
410
|
+
!url.pathname.startsWith(
|
|
411
|
+
`/${currency}/orders`
|
|
412
|
+
)
|
|
413
|
+
) {
|
|
414
|
+
const currentCookieLocale =
|
|
415
|
+
req.cookies.get('pz-locale')?.value;
|
|
416
|
+
|
|
417
|
+
const urlHasExplicitLocale =
|
|
418
|
+
url.pathname.match(urlLocaleMatcherRegex);
|
|
419
|
+
const shouldUpdateCookie =
|
|
420
|
+
!currentCookieLocale ||
|
|
421
|
+
urlHasExplicitLocale;
|
|
422
|
+
|
|
423
|
+
if (shouldUpdateCookie) {
|
|
424
|
+
middlewareResult.cookies.set(
|
|
425
|
+
'pz-locale',
|
|
426
|
+
locale?.length > 0
|
|
427
|
+
? locale
|
|
428
|
+
: defaultLocaleValue,
|
|
429
|
+
{
|
|
430
|
+
domain: rootHostname,
|
|
431
|
+
sameSite: 'none',
|
|
432
|
+
secure: true,
|
|
433
|
+
expires: new Date(
|
|
434
|
+
Date.now() + 1000 * 60 * 60 * 24 * 7
|
|
435
|
+
) // 7 days
|
|
436
|
+
}
|
|
437
|
+
);
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
|
|
404
441
|
if (
|
|
405
442
|
req.cookies.get('pz-locale') &&
|
|
406
443
|
req.cookies.get('pz-locale').value !== locale
|
package/middlewares/locale.ts
CHANGED
|
@@ -23,7 +23,15 @@ const getMatchedLocale = (pathname: string, req: PzNextRequest) => {
|
|
|
23
23
|
);
|
|
24
24
|
|
|
25
25
|
if (subDomainLocaleMatched && subDomainLocaleMatched[0]) {
|
|
26
|
-
|
|
26
|
+
const subdomainLocale = subDomainLocaleMatched[0].slice(1);
|
|
27
|
+
|
|
28
|
+
const isValidSubdomainLocale = settings.localization.locales.find(
|
|
29
|
+
(l) => l.value === subdomainLocale
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
if (isValidSubdomainLocale) {
|
|
33
|
+
matchedLocale = subdomainLocale;
|
|
34
|
+
}
|
|
27
35
|
}
|
|
28
36
|
}
|
|
29
37
|
}
|
|
@@ -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') ?? '';
|
|
@@ -149,7 +149,8 @@ const withRedirectionPayment =
|
|
|
149
149
|
logger.info('Redirecting to order success page', {
|
|
150
150
|
middleware: 'redirection-payment',
|
|
151
151
|
redirectUrlWithLocale,
|
|
152
|
-
ip
|
|
152
|
+
ip,
|
|
153
|
+
setCookie: request.headers.get('set-cookie')
|
|
153
154
|
});
|
|
154
155
|
|
|
155
156
|
// Using POST method while redirecting causes an error,
|
|
@@ -149,7 +149,8 @@ const withSavedCardRedirection =
|
|
|
149
149
|
logger.info('Redirecting to order success page', {
|
|
150
150
|
middleware: 'saved-card-redirection',
|
|
151
151
|
redirectUrlWithLocale,
|
|
152
|
-
ip
|
|
152
|
+
ip,
|
|
153
|
+
setCookie: request.headers.get('set-cookie')
|
|
153
154
|
});
|
|
154
155
|
|
|
155
156
|
// Using POST method while redirecting causes an error,
|
|
@@ -4,7 +4,6 @@ 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 { ServerVariables } from '../utils/server-variables';
|
|
8
7
|
|
|
9
8
|
const streamToString = async (stream: ReadableStream<Uint8Array> | null) => {
|
|
10
9
|
if (stream) {
|
|
@@ -149,7 +148,8 @@ const withThreeDRedirection =
|
|
|
149
148
|
logger.info('Redirecting to order success page', {
|
|
150
149
|
middleware: 'three-d-redirection',
|
|
151
150
|
redirectUrlWithLocale,
|
|
152
|
-
ip
|
|
151
|
+
ip,
|
|
152
|
+
setCookie: request.headers.get('set-cookie')
|
|
153
153
|
});
|
|
154
154
|
|
|
155
155
|
// Using POST method while redirecting causes an error,
|
|
@@ -4,6 +4,7 @@ import { PzNextRequest } from '.';
|
|
|
4
4
|
import logger from '../utils/log';
|
|
5
5
|
import { urlLocaleMatcherRegex } from '../utils';
|
|
6
6
|
import { getUrlPathWithLocale } from '../utils/localization';
|
|
7
|
+
import { shouldIgnoreRedirect } from '../utils/redirect-ignore';
|
|
7
8
|
import { ROUTES } from 'routes';
|
|
8
9
|
|
|
9
10
|
// This middleware is used to handle url redirections set in Omnitron
|
|
@@ -60,20 +61,13 @@ const withUrlRedirection =
|
|
|
60
61
|
|
|
61
62
|
const setCookies = request.headers.getSetCookie();
|
|
62
63
|
|
|
63
|
-
if (
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
)
|
|
71
|
-
)
|
|
72
|
-
);
|
|
73
|
-
|
|
74
|
-
if (shouldIgnoreRedirect) {
|
|
75
|
-
return middleware(req, event);
|
|
76
|
-
}
|
|
64
|
+
if (
|
|
65
|
+
shouldIgnoreRedirect(
|
|
66
|
+
url.pathname,
|
|
67
|
+
req.middlewareParams.rewrites.locale
|
|
68
|
+
)
|
|
69
|
+
) {
|
|
70
|
+
return middleware(req, event);
|
|
77
71
|
}
|
|
78
72
|
|
|
79
73
|
const response = NextResponse.redirect(redirectUrl.toString(), {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@akinon/next",
|
|
3
3
|
"description": "Core package for Project Zero Next",
|
|
4
|
-
"version": "1.
|
|
4
|
+
"version": "1.100.0-rc.71",
|
|
5
5
|
"private": false,
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"bin": {
|
|
@@ -17,7 +17,8 @@
|
|
|
17
17
|
"test": "jest"
|
|
18
18
|
},
|
|
19
19
|
"dependencies": {
|
|
20
|
-
"@
|
|
20
|
+
"@mongodb-js/zstd": "^2.0.1",
|
|
21
|
+
"@neshca/cache-handler": "1.9.0",
|
|
21
22
|
"@opentelemetry/exporter-trace-otlp-http": "0.46.0",
|
|
22
23
|
"@opentelemetry/resources": "1.19.0",
|
|
23
24
|
"@opentelemetry/sdk-node": "0.46.0",
|
|
@@ -34,7 +35,7 @@
|
|
|
34
35
|
"set-cookie-parser": "2.6.0"
|
|
35
36
|
},
|
|
36
37
|
"devDependencies": {
|
|
37
|
-
"@akinon/eslint-plugin-projectzero": "1.
|
|
38
|
+
"@akinon/eslint-plugin-projectzero": "1.100.0-rc.71",
|
|
38
39
|
"@babel/core": "7.26.10",
|
|
39
40
|
"@babel/preset-env": "7.26.9",
|
|
40
41
|
"@babel/preset-typescript": "7.27.0",
|
package/plugins.d.ts
CHANGED
|
@@ -30,11 +30,14 @@ declare module '@akinon/pz-saved-card' {
|
|
|
30
30
|
export const SavedCardOption: any;
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
-
declare module '@akinon/pz-iyzico-saved-card' {
|
|
34
|
-
export const iyzicoSavedCardReducer: any;
|
|
35
|
-
export const iyzicoSavedCardMiddleware: any;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
33
|
declare module '@akinon/pz-apple-pay' {}
|
|
39
34
|
|
|
40
35
|
declare module '@akinon/pz-flow-payment' {}
|
|
36
|
+
|
|
37
|
+
declare module '@akinon/pz-similar-products' {
|
|
38
|
+
export const SimilarProductsModal: any;
|
|
39
|
+
export const SimilarProductsFilterSidebar: any;
|
|
40
|
+
export const SimilarProductsResultsGrid: any;
|
|
41
|
+
export const SimilarProductsPlugin: any;
|
|
42
|
+
export const SimilarProductsButtonPlugin: any;
|
|
43
|
+
}
|