@akinon/next 1.21.0 → 1.22.0-rc.0
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 +16 -0
- package/api/auth.ts +11 -0
- package/bin/pz-install-plugins.js +59 -27
- package/data/client/wishlist.ts +2 -0
- package/data/server/category.ts +3 -3
- package/data/server/list.ts +1 -1
- package/hooks/use-pagination.ts +0 -4
- package/lib/cache.ts +55 -27
- package/middlewares/checkout-provider.ts +88 -0
- package/middlewares/default.ts +145 -104
- package/middlewares/index.ts +3 -1
- package/package.json +5 -4
- package/plugins.d.ts +4 -0
- package/redux/reducers/index.ts +3 -1
- package/sentry/index.ts +20 -14
- package/types/index.ts +6 -0
- package/utils/app-fetch.ts +17 -6
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# @akinon/next
|
|
2
2
|
|
|
3
|
+
## 1.22.0-rc.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 32668ed: ZERO-2296: Implement Checkout Provider Middleware and Enhancements
|
|
8
|
+
- 0181251: ZERO-2440: move otp popup state to redux
|
|
9
|
+
- 07cc81a: Add infinite and more types to pagination
|
|
10
|
+
- 8d6caba: ZERO-2434: enhance error handling and logging in appFetch function
|
|
11
|
+
- 1b4b0fa: Redis connection change to connection pool
|
|
12
|
+
- b4452e9: ZERO-2463: Refactor Sentry initialization and add Sentry DSN option to settings
|
|
13
|
+
- b2da5e4: Revert ZERO-2435
|
|
14
|
+
|
|
15
|
+
### Patch Changes
|
|
16
|
+
|
|
17
|
+
- da1e501: ZERO-2296: Fix ROUTES import
|
|
18
|
+
|
|
3
19
|
## 1.21.0
|
|
4
20
|
|
|
5
21
|
### Minor Changes
|
package/api/auth.ts
CHANGED
|
@@ -220,6 +220,17 @@ const nextAuthOptions = (req: NextApiRequest, res: NextApiResponse) => {
|
|
|
220
220
|
pages: {
|
|
221
221
|
signIn: ROUTES.AUTH,
|
|
222
222
|
error: ROUTES.AUTH
|
|
223
|
+
},
|
|
224
|
+
cookies: {
|
|
225
|
+
sessionToken: {
|
|
226
|
+
name: `__Secure-next-auth.session-token`,
|
|
227
|
+
options: {
|
|
228
|
+
httpOnly: true,
|
|
229
|
+
sameSite: 'none',
|
|
230
|
+
path: '/',
|
|
231
|
+
secure: true
|
|
232
|
+
}
|
|
233
|
+
}
|
|
223
234
|
}
|
|
224
235
|
};
|
|
225
236
|
};
|
|
@@ -1,39 +1,71 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
1
|
const fs = require('fs');
|
|
4
2
|
const path = require('path');
|
|
5
|
-
const
|
|
6
|
-
|
|
7
|
-
|
|
3
|
+
const { execSync } = require('child_process');
|
|
4
|
+
|
|
5
|
+
function findBaseDir() {
|
|
6
|
+
const insideNodeModules = __dirname.includes('node_modules');
|
|
7
|
+
return insideNodeModules
|
|
8
|
+
? process.cwd()
|
|
9
|
+
: path.resolve(__dirname, '../../../apps/projectzeronext');
|
|
10
|
+
}
|
|
8
11
|
|
|
9
|
-
|
|
12
|
+
const BASE_DIR = findBaseDir();
|
|
13
|
+
const getFullPath = (relativePath) => path.join(BASE_DIR, relativePath);
|
|
14
|
+
|
|
15
|
+
const packageJsonPath = getFullPath('package.json');
|
|
16
|
+
let packageJson;
|
|
10
17
|
|
|
11
18
|
try {
|
|
12
|
-
|
|
19
|
+
packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
|
13
20
|
} catch (error) {
|
|
14
|
-
console.error('
|
|
15
|
-
process.exit(
|
|
21
|
+
console.error('Error reading package.json:', error);
|
|
22
|
+
process.exit(1);
|
|
16
23
|
}
|
|
17
24
|
|
|
18
|
-
|
|
25
|
+
const pluginsJsPath = getFullPath('src/plugins.js');
|
|
26
|
+
let installedPlugins;
|
|
27
|
+
|
|
28
|
+
try {
|
|
29
|
+
installedPlugins = require(pluginsJsPath);
|
|
30
|
+
} catch (error) {
|
|
31
|
+
console.error('Error loading installed plugins:', error);
|
|
32
|
+
process.exit(1);
|
|
33
|
+
}
|
|
19
34
|
|
|
20
|
-
|
|
21
|
-
.filter((p) => plugins?.includes(p))
|
|
22
|
-
.forEach((name) => {
|
|
23
|
-
installCmd.push(`@akinon/${name}`);
|
|
24
|
-
});
|
|
35
|
+
const protectedPackages = ['@akinon/next', 'next'];
|
|
25
36
|
|
|
26
|
-
|
|
37
|
+
const pluginsToRemove = Object.keys(packageJson.dependencies || {}).filter(
|
|
38
|
+
(dep) =>
|
|
39
|
+
dep.startsWith('@akinon/') &&
|
|
40
|
+
!installedPlugins.includes(dep.replace('@akinon/', '')) &&
|
|
41
|
+
!protectedPackages.includes(dep)
|
|
42
|
+
);
|
|
27
43
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
}
|
|
44
|
+
pluginsToRemove.forEach((plugin) => {
|
|
45
|
+
try {
|
|
46
|
+
execSync(`yarn remove ${plugin}`, { stdio: 'inherit', cwd: BASE_DIR });
|
|
47
|
+
} catch (error) {
|
|
48
|
+
console.warn(
|
|
49
|
+
`Warning: Could not remove ${plugin}. It may not have been installed.`
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
});
|
|
31
53
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
)
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
54
|
+
installedPlugins.forEach((plugin) => {
|
|
55
|
+
const packageName = `@akinon/${plugin}`;
|
|
56
|
+
if (
|
|
57
|
+
!Object.keys(packageJson.dependencies || {}).includes(packageName) &&
|
|
58
|
+
!protectedPackages.includes(packageName)
|
|
59
|
+
) {
|
|
60
|
+
try {
|
|
61
|
+
const version = packageJson.dependencies['@akinon/next'].replace('^', '');
|
|
62
|
+
const command = `yarn add ${packageName}@${version} --exact --ignore-scripts`;
|
|
63
|
+
execSync(command, { stdio: 'inherit', cwd: BASE_DIR });
|
|
64
|
+
} catch (error) {
|
|
65
|
+
console.warn(
|
|
66
|
+
'\n\x1b[33m%s\x1b[0m',
|
|
67
|
+
`Error adding ${packageName}: ${error}`
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
});
|
package/data/client/wishlist.ts
CHANGED
package/data/server/category.ts
CHANGED
|
@@ -15,7 +15,7 @@ function getCategoryDataHandler(
|
|
|
15
15
|
const params = generateCommerceSearchParams(searchParams);
|
|
16
16
|
|
|
17
17
|
const rawData = await appFetch<string>(
|
|
18
|
-
`${category.getCategoryByPk(pk)}${params ? params : ''}`,
|
|
18
|
+
`${category.getCategoryByPk(pk)}s${params ? params : ''}`,
|
|
19
19
|
{
|
|
20
20
|
headers: {
|
|
21
21
|
Accept: 'application/json',
|
|
@@ -39,7 +39,7 @@ function getCategoryDataHandler(
|
|
|
39
39
|
numberValueParser
|
|
40
40
|
) as GetCategoryResponse;
|
|
41
41
|
} catch (error) {
|
|
42
|
-
logger.
|
|
42
|
+
logger.fatal('Error while parsing category data', {
|
|
43
43
|
handler: 'getCategoryDataHandler',
|
|
44
44
|
error,
|
|
45
45
|
rawData: rawData.startsWith('<!DOCTYPE html>')
|
|
@@ -108,7 +108,7 @@ function getCategoryBySlugDataHandler(slug: string) {
|
|
|
108
108
|
numberValueParser
|
|
109
109
|
) as GetCategoryResponse;
|
|
110
110
|
} catch (error) {
|
|
111
|
-
logger.
|
|
111
|
+
logger.fatal('Error while parsing category data', {
|
|
112
112
|
handler: 'getCategoryBySlugDataHandler',
|
|
113
113
|
error,
|
|
114
114
|
rawData: rawData.startsWith('<!DOCTYPE html>')
|
package/data/server/list.ts
CHANGED
|
@@ -38,7 +38,7 @@ const getListDataHandler = (
|
|
|
38
38
|
numberValueParser
|
|
39
39
|
) as GetCategoryResponse;
|
|
40
40
|
} catch (error) {
|
|
41
|
-
logger.
|
|
41
|
+
logger.fatal('Error while parsing list data', {
|
|
42
42
|
error,
|
|
43
43
|
rawData: rawData.startsWith('<!DOCTYPE html>')
|
|
44
44
|
? `${rawData.substring(0, 50)}...`
|
package/hooks/use-pagination.ts
CHANGED
|
@@ -70,10 +70,6 @@ export default function usePagination(
|
|
|
70
70
|
dispatch({ type: 'setLimit', payload: limit });
|
|
71
71
|
}, [limit]);
|
|
72
72
|
|
|
73
|
-
useEffect(() => {
|
|
74
|
-
window.scrollTo(0, 0);
|
|
75
|
-
}, [state.page, state.limit]);
|
|
76
|
-
|
|
77
73
|
const setTotal = useCallback(
|
|
78
74
|
(total: number) => {
|
|
79
75
|
dispatch({ type: 'setTotal', payload: total });
|
package/lib/cache.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { createPool, Pool } from 'generic-pool';
|
|
1
2
|
import { RedisClientType } from 'redis';
|
|
2
3
|
import Settings from 'settings';
|
|
3
4
|
import { CacheOptions } from '../types';
|
|
@@ -64,21 +65,38 @@ export class Cache {
|
|
|
64
65
|
);
|
|
65
66
|
}
|
|
66
67
|
|
|
67
|
-
static
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
68
|
+
static clientPool: Pool<RedisClientType> = createPool(
|
|
69
|
+
{
|
|
70
|
+
create: async () => {
|
|
71
|
+
const { createClient } = await import('redis');
|
|
72
|
+
const redisUrl = `redis://${process.env.CACHE_HOST}:${
|
|
73
|
+
process.env.CACHE_PORT
|
|
74
|
+
}/${process.env.CACHE_BUCKET ?? '0'}`;
|
|
75
|
+
|
|
76
|
+
const client: RedisClientType = createClient({
|
|
77
|
+
url: redisUrl
|
|
78
|
+
});
|
|
72
79
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
80
|
+
client.on('error', (error) => {
|
|
81
|
+
logger.error('Redis client error', { redisUrl, error });
|
|
82
|
+
});
|
|
76
83
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
84
|
+
await client.connect();
|
|
85
|
+
|
|
86
|
+
return client;
|
|
87
|
+
},
|
|
88
|
+
destroy: async (client: RedisClientType) => {
|
|
89
|
+
await client.disconnect();
|
|
90
|
+
}
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
max: 500,
|
|
94
|
+
min: 2
|
|
95
|
+
}
|
|
96
|
+
);
|
|
80
97
|
|
|
81
|
-
|
|
98
|
+
static async getClient() {
|
|
99
|
+
return await Cache.clientPool.acquire();
|
|
82
100
|
}
|
|
83
101
|
|
|
84
102
|
static async get(key: string) {
|
|
@@ -87,39 +105,49 @@ export class Cache {
|
|
|
87
105
|
|
|
88
106
|
try {
|
|
89
107
|
client = await Cache.getClient();
|
|
90
|
-
await client.
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
108
|
+
const response = await client.get(key);
|
|
109
|
+
if (response) {
|
|
110
|
+
value = JSON.parse(response);
|
|
111
|
+
} else {
|
|
112
|
+
value = null;
|
|
113
|
+
}
|
|
114
|
+
logger.debug('Redis get success', { key, value });
|
|
95
115
|
} catch (error) {
|
|
96
116
|
logger.error('Redis get error', { key, error });
|
|
117
|
+
value = null;
|
|
97
118
|
} finally {
|
|
98
|
-
|
|
119
|
+
if (client) {
|
|
120
|
+
await Cache.clientPool.release(client);
|
|
121
|
+
}
|
|
99
122
|
}
|
|
100
123
|
|
|
101
124
|
return value;
|
|
102
125
|
}
|
|
103
126
|
|
|
104
|
-
static async set(key: string, value:
|
|
127
|
+
static async set(key: string, value: any, expire?: number) {
|
|
105
128
|
let success = false;
|
|
106
129
|
let client;
|
|
107
130
|
|
|
108
131
|
try {
|
|
109
132
|
client = await Cache.getClient();
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
EX: expire
|
|
113
|
-
});
|
|
133
|
+
const serializedValue =
|
|
134
|
+
typeof value === 'object' ? JSON.stringify(value) : value;
|
|
114
135
|
|
|
115
|
-
|
|
136
|
+
if (expire) {
|
|
137
|
+
await client.set(key, serializedValue, { EX: expire });
|
|
138
|
+
} else {
|
|
139
|
+
await client.set(key, serializedValue);
|
|
140
|
+
}
|
|
116
141
|
|
|
117
|
-
|
|
118
|
-
logger.
|
|
142
|
+
success = true;
|
|
143
|
+
logger.debug('Redis set success', { key, value });
|
|
119
144
|
} catch (error) {
|
|
120
145
|
logger.error('Redis set error', { key, error });
|
|
146
|
+
success = false;
|
|
121
147
|
} finally {
|
|
122
|
-
|
|
148
|
+
if (client) {
|
|
149
|
+
await Cache.clientPool.release(client);
|
|
150
|
+
}
|
|
123
151
|
}
|
|
124
152
|
|
|
125
153
|
return success;
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { NextFetchEvent, NextMiddleware, NextResponse } from 'next/server';
|
|
2
|
+
import settings from 'settings';
|
|
3
|
+
import { PzNextRequest } from '.';
|
|
4
|
+
import logger from '../utils/log';
|
|
5
|
+
import { urlLocaleMatcherRegex } from '../utils';
|
|
6
|
+
import { getUrlPathWithLocale } from '../utils/localization';
|
|
7
|
+
|
|
8
|
+
const withCheckoutProvider =
|
|
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 pathnameWithoutLocale = url.pathname.replace(
|
|
14
|
+
urlLocaleMatcherRegex,
|
|
15
|
+
''
|
|
16
|
+
);
|
|
17
|
+
const searchParams = new URLSearchParams(url.search);
|
|
18
|
+
|
|
19
|
+
if (pathnameWithoutLocale.startsWith('/orders/checkout-provider/')) {
|
|
20
|
+
try {
|
|
21
|
+
const request = await fetch(
|
|
22
|
+
`${encodeURI(settings.commerceUrl)}${url.pathname.replace(
|
|
23
|
+
urlLocaleMatcherRegex,
|
|
24
|
+
''
|
|
25
|
+
)}?${searchParams.toString()}`,
|
|
26
|
+
{
|
|
27
|
+
next: {
|
|
28
|
+
revalidate: 0
|
|
29
|
+
},
|
|
30
|
+
headers: {
|
|
31
|
+
Cookie: req.headers.get('cookie') || '',
|
|
32
|
+
Accept: 'application/json',
|
|
33
|
+
'X-Requested-With': 'XMLHttpRequest'
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
return NextResponse.json(await request.json());
|
|
39
|
+
} catch (error) {
|
|
40
|
+
logger.error('withCheckoutProvider error', {
|
|
41
|
+
error,
|
|
42
|
+
ip
|
|
43
|
+
});
|
|
44
|
+
return NextResponse.next();
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (pathnameWithoutLocale.startsWith('/orders/checkout-provider-cancel')) {
|
|
49
|
+
try {
|
|
50
|
+
const request = await fetch(
|
|
51
|
+
`${settings.commerceUrl}${pathnameWithoutLocale}${url.search}`,
|
|
52
|
+
{
|
|
53
|
+
redirect: 'manual',
|
|
54
|
+
next: {
|
|
55
|
+
revalidate: 0
|
|
56
|
+
},
|
|
57
|
+
headers: {
|
|
58
|
+
Cookie: req.headers.get('cookie') || ''
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
if (request.headers.get('location')) {
|
|
64
|
+
const location = request.headers.get('location');
|
|
65
|
+
const redirectUrl = new URL(
|
|
66
|
+
request.headers.get('location'),
|
|
67
|
+
location.startsWith('http') ? '' : process.env.NEXT_PUBLIC_URL
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
redirectUrl.pathname = getUrlPathWithLocale(
|
|
71
|
+
redirectUrl.pathname,
|
|
72
|
+
req.middlewareParams.rewrites.locale
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
return Response.redirect(redirectUrl.toString(), request.status);
|
|
76
|
+
}
|
|
77
|
+
} catch (error) {
|
|
78
|
+
logger.error('withCheckoutProvider error', {
|
|
79
|
+
error,
|
|
80
|
+
ip
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return middleware(req, event);
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
export default withCheckoutProvider;
|
package/middlewares/default.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { NextFetchEvent, NextMiddleware, NextResponse } from 'next/server';
|
|
2
2
|
import Settings from 'settings';
|
|
3
|
+
import { ROUTES } from 'routes';
|
|
3
4
|
import {
|
|
4
5
|
PzNextRequest,
|
|
6
|
+
withCheckoutProvider,
|
|
5
7
|
withCompleteGpay,
|
|
6
8
|
withCompleteMasterpass,
|
|
7
9
|
withOauthLogin,
|
|
@@ -112,6 +114,36 @@ const withPzDefault =
|
|
|
112
114
|
return NextResponse.redirect(redirectUrlWithLocale, 303);
|
|
113
115
|
}
|
|
114
116
|
|
|
117
|
+
if (req.nextUrl.pathname.startsWith('/orders/checkout-provider/')) {
|
|
118
|
+
try {
|
|
119
|
+
const data = await req.json();
|
|
120
|
+
|
|
121
|
+
const request = await fetch(
|
|
122
|
+
`${encodeURI(Settings.commerceUrl)}${url.pathname.replace(
|
|
123
|
+
urlLocaleMatcherRegex,
|
|
124
|
+
''
|
|
125
|
+
)}?${searchParams.toString()}`,
|
|
126
|
+
{
|
|
127
|
+
method: 'POST',
|
|
128
|
+
body: JSON.stringify(data),
|
|
129
|
+
next: {
|
|
130
|
+
revalidate: 0
|
|
131
|
+
},
|
|
132
|
+
headers: {
|
|
133
|
+
Cookie: req.headers.get('cookie') || '',
|
|
134
|
+
Accept: 'application/json',
|
|
135
|
+
'Content-Type': 'application/json',
|
|
136
|
+
'X-Requested-With': 'XMLHttpRequest'
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
return NextResponse.json(await request.json());
|
|
142
|
+
} catch (error) {
|
|
143
|
+
return NextResponse.redirect(ROUTES.BASKET);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
115
147
|
req.middlewareParams = {
|
|
116
148
|
commerceUrl,
|
|
117
149
|
rewrites: {}
|
|
@@ -123,64 +155,82 @@ const withPzDefault =
|
|
|
123
155
|
withPrettyUrl(
|
|
124
156
|
withRedirectionPayment(
|
|
125
157
|
withThreeDRedirection(
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
NextResponse
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
url.basePath = `/${commerceUrl}`;
|
|
144
|
-
url.pathname = `/${
|
|
145
|
-
locale.length ? `${locale}/` : ''
|
|
146
|
-
}${currency}${prettyUrl ?? pathnameWithoutLocale}`;
|
|
147
|
-
|
|
148
|
-
Settings.rewrites.forEach((rewrite) => {
|
|
149
|
-
url.pathname = url.pathname.replace(
|
|
150
|
-
rewrite.source,
|
|
151
|
-
rewrite.destination
|
|
158
|
+
withCheckoutProvider(
|
|
159
|
+
withUrlRedirection(
|
|
160
|
+
withCompleteGpay(
|
|
161
|
+
withCompleteMasterpass(
|
|
162
|
+
async (req: PzNextRequest, event: NextFetchEvent) => {
|
|
163
|
+
let middlewareResult: NextResponse | void =
|
|
164
|
+
NextResponse.next();
|
|
165
|
+
|
|
166
|
+
try {
|
|
167
|
+
const { locale, prettyUrl, currency } =
|
|
168
|
+
req.middlewareParams.rewrites;
|
|
169
|
+
const { defaultLocaleValue } =
|
|
170
|
+
Settings.localization;
|
|
171
|
+
const url = req.nextUrl.clone();
|
|
172
|
+
const pathnameWithoutLocale = url.pathname.replace(
|
|
173
|
+
urlLocaleMatcherRegex,
|
|
174
|
+
''
|
|
152
175
|
);
|
|
153
|
-
});
|
|
154
176
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
177
|
+
url.basePath = `/${commerceUrl}`;
|
|
178
|
+
url.pathname = `/${
|
|
179
|
+
locale.length ? `${locale}/` : ''
|
|
180
|
+
}${currency}${prettyUrl ?? pathnameWithoutLocale}`;
|
|
181
|
+
|
|
182
|
+
Settings.rewrites.forEach((rewrite) => {
|
|
183
|
+
url.pathname = url.pathname.replace(
|
|
184
|
+
rewrite.source,
|
|
185
|
+
rewrite.destination
|
|
186
|
+
);
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
middlewareResult = (await middleware(
|
|
190
|
+
req,
|
|
191
|
+
event
|
|
192
|
+
)) as NextResponse | void;
|
|
193
|
+
|
|
194
|
+
// if middleware.ts has a return value for current url
|
|
195
|
+
if (middlewareResult instanceof NextResponse) {
|
|
196
|
+
// pz-override-response header is used to prevent 404 page for custom responses.
|
|
197
|
+
if (
|
|
198
|
+
middlewareResult.headers.get(
|
|
199
|
+
'pz-override-response'
|
|
200
|
+
) !== 'true'
|
|
201
|
+
) {
|
|
202
|
+
middlewareResult.headers.set(
|
|
203
|
+
'x-middleware-rewrite',
|
|
204
|
+
url.href
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
} else {
|
|
208
|
+
// if middleware.ts doesn't have a return value.
|
|
209
|
+
// e.g. NextResponse.next() doesn't exist in middleware.ts
|
|
210
|
+
|
|
211
|
+
middlewareResult = NextResponse.rewrite(url);
|
|
212
|
+
}
|
|
159
213
|
|
|
160
|
-
// if middleware.ts has a return value for current url
|
|
161
|
-
if (middlewareResult instanceof NextResponse) {
|
|
162
|
-
// pz-override-response header is used to prevent 404 page for custom responses.
|
|
163
214
|
if (
|
|
164
|
-
|
|
165
|
-
'pz-override-response'
|
|
166
|
-
) !== 'true'
|
|
215
|
+
!url.pathname.startsWith(`/${currency}/orders`)
|
|
167
216
|
) {
|
|
168
|
-
middlewareResult.
|
|
169
|
-
'
|
|
170
|
-
|
|
217
|
+
middlewareResult.cookies.set(
|
|
218
|
+
'pz-locale',
|
|
219
|
+
locale?.length > 0
|
|
220
|
+
? locale
|
|
221
|
+
: defaultLocaleValue,
|
|
222
|
+
{
|
|
223
|
+
sameSite: 'none',
|
|
224
|
+
secure: true,
|
|
225
|
+
expires: new Date(
|
|
226
|
+
Date.now() + 1000 * 60 * 60 * 24 * 7
|
|
227
|
+
) // 7 days
|
|
228
|
+
}
|
|
171
229
|
);
|
|
172
230
|
}
|
|
173
|
-
} else {
|
|
174
|
-
// if middleware.ts doesn't have a return value.
|
|
175
|
-
// e.g. NextResponse.next() doesn't exist in middleware.ts
|
|
176
|
-
|
|
177
|
-
middlewareResult = NextResponse.rewrite(url);
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
if (!url.pathname.startsWith(`/${currency}/orders`)) {
|
|
181
231
|
middlewareResult.cookies.set(
|
|
182
|
-
'pz-
|
|
183
|
-
|
|
232
|
+
'pz-currency',
|
|
233
|
+
currency,
|
|
184
234
|
{
|
|
185
235
|
sameSite: 'none',
|
|
186
236
|
secure: true,
|
|
@@ -189,74 +239,65 @@ const withPzDefault =
|
|
|
189
239
|
) // 7 days
|
|
190
240
|
}
|
|
191
241
|
);
|
|
192
|
-
}
|
|
193
|
-
middlewareResult.cookies.set(
|
|
194
|
-
'pz-currency',
|
|
195
|
-
currency,
|
|
196
|
-
{
|
|
197
|
-
sameSite: 'none',
|
|
198
|
-
secure: true,
|
|
199
|
-
expires: new Date(
|
|
200
|
-
Date.now() + 1000 * 60 * 60 * 24 * 7
|
|
201
|
-
) // 7 days
|
|
202
|
-
}
|
|
203
|
-
);
|
|
204
|
-
|
|
205
|
-
if (
|
|
206
|
-
req.cookies.get('pz-locale') &&
|
|
207
|
-
req.cookies.get('pz-locale').value !== locale
|
|
208
|
-
) {
|
|
209
|
-
logger.debug('Locale changed', {
|
|
210
|
-
locale,
|
|
211
|
-
oldLocale: req.cookies.get('pz-locale')?.value,
|
|
212
|
-
ip
|
|
213
|
-
});
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
middlewareResult.headers.set(
|
|
217
|
-
'pz-url',
|
|
218
|
-
req.nextUrl.toString()
|
|
219
|
-
);
|
|
220
242
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
243
|
+
if (
|
|
244
|
+
req.cookies.get('pz-locale') &&
|
|
245
|
+
req.cookies.get('pz-locale').value !== locale
|
|
246
|
+
) {
|
|
247
|
+
logger.debug('Locale changed', {
|
|
248
|
+
locale,
|
|
249
|
+
oldLocale: req.cookies.get('pz-locale')?.value,
|
|
250
|
+
ip
|
|
251
|
+
});
|
|
252
|
+
}
|
|
224
253
|
|
|
225
|
-
if (process.env.ACC_APP_VERSION) {
|
|
226
254
|
middlewareResult.headers.set(
|
|
227
|
-
'
|
|
228
|
-
|
|
255
|
+
'pz-url',
|
|
256
|
+
req.nextUrl.toString()
|
|
229
257
|
);
|
|
230
|
-
}
|
|
231
258
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
259
|
+
if (req.cookies.get('pz-set-currency')) {
|
|
260
|
+
middlewareResult.cookies.delete(
|
|
261
|
+
'pz-set-currency'
|
|
262
|
+
);
|
|
263
|
+
}
|
|
235
264
|
|
|
236
|
-
if (
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
middlewareResult.cookies.set(
|
|
241
|
-
'csrftoken',
|
|
242
|
-
csrf_token
|
|
265
|
+
if (process.env.ACC_APP_VERSION) {
|
|
266
|
+
middlewareResult.headers.set(
|
|
267
|
+
'acc-app-version',
|
|
268
|
+
process.env.ACC_APP_VERSION
|
|
243
269
|
);
|
|
244
270
|
}
|
|
271
|
+
|
|
272
|
+
// Set CSRF token if not set
|
|
273
|
+
try {
|
|
274
|
+
const url = `${Settings.commerceUrl}${user.csrfToken}`;
|
|
275
|
+
|
|
276
|
+
if (!req.cookies.get('csrftoken')) {
|
|
277
|
+
const { csrf_token } = await (
|
|
278
|
+
await fetch(url)
|
|
279
|
+
).json();
|
|
280
|
+
middlewareResult.cookies.set(
|
|
281
|
+
'csrftoken',
|
|
282
|
+
csrf_token
|
|
283
|
+
);
|
|
284
|
+
}
|
|
285
|
+
} catch (error) {
|
|
286
|
+
logger.error('CSRF Error', {
|
|
287
|
+
error,
|
|
288
|
+
ip
|
|
289
|
+
});
|
|
290
|
+
}
|
|
245
291
|
} catch (error) {
|
|
246
|
-
logger.error('
|
|
292
|
+
logger.error('withPzDefault Error', {
|
|
247
293
|
error,
|
|
248
294
|
ip
|
|
249
295
|
});
|
|
250
296
|
}
|
|
251
|
-
} catch (error) {
|
|
252
|
-
logger.error('withPzDefault Error', {
|
|
253
|
-
error,
|
|
254
|
-
ip
|
|
255
|
-
});
|
|
256
|
-
}
|
|
257
297
|
|
|
258
|
-
|
|
259
|
-
|
|
298
|
+
return middlewareResult;
|
|
299
|
+
}
|
|
300
|
+
)
|
|
260
301
|
)
|
|
261
302
|
)
|
|
262
303
|
)
|
package/middlewares/index.ts
CHANGED
|
@@ -7,6 +7,7 @@ import withOauthLogin from './oauth-login';
|
|
|
7
7
|
import withUrlRedirection from './url-redirection';
|
|
8
8
|
import withCompleteGpay from './complete-gpay';
|
|
9
9
|
import withCompleteMasterpass from './complete-masterpass';
|
|
10
|
+
import withCheckoutProvider from './checkout-provider';
|
|
10
11
|
import { NextRequest } from 'next/server';
|
|
11
12
|
|
|
12
13
|
export {
|
|
@@ -18,7 +19,8 @@ export {
|
|
|
18
19
|
withOauthLogin,
|
|
19
20
|
withUrlRedirection,
|
|
20
21
|
withCompleteGpay,
|
|
21
|
-
withCompleteMasterpass
|
|
22
|
+
withCompleteMasterpass,
|
|
23
|
+
withCheckoutProvider
|
|
22
24
|
};
|
|
23
25
|
|
|
24
26
|
export interface PzNextRequest extends NextRequest {
|
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.22.0-rc.0",
|
|
5
5
|
"private": false,
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"bin": {
|
|
@@ -14,13 +14,14 @@
|
|
|
14
14
|
"pz-postdev": "bin/pz-postdev.js"
|
|
15
15
|
},
|
|
16
16
|
"dependencies": {
|
|
17
|
-
"@opentelemetry/sdk-node": "0.46.0",
|
|
18
17
|
"@opentelemetry/exporter-trace-otlp-http": "0.46.0",
|
|
19
18
|
"@opentelemetry/resources": "1.19.0",
|
|
20
|
-
"@opentelemetry/
|
|
19
|
+
"@opentelemetry/sdk-node": "0.46.0",
|
|
21
20
|
"@opentelemetry/sdk-trace-node": "1.19.0",
|
|
21
|
+
"@opentelemetry/semantic-conventions": "1.19.0",
|
|
22
22
|
"@reduxjs/toolkit": "1.9.7",
|
|
23
23
|
"cross-spawn": "7.0.3",
|
|
24
|
+
"generic-pool": "3.9.0",
|
|
24
25
|
"react-redux": "8.1.3",
|
|
25
26
|
"react-string-replace": "1.1.1",
|
|
26
27
|
"redis": "4.5.1",
|
|
@@ -31,7 +32,7 @@
|
|
|
31
32
|
"@typescript-eslint/eslint-plugin": "6.7.4",
|
|
32
33
|
"@typescript-eslint/parser": "6.7.4",
|
|
33
34
|
"eslint": "^8.14.0",
|
|
34
|
-
"@akinon/eslint-plugin-projectzero": "1.
|
|
35
|
+
"@akinon/eslint-plugin-projectzero": "1.22.0-rc.0",
|
|
35
36
|
"eslint-config-prettier": "8.5.0"
|
|
36
37
|
}
|
|
37
38
|
}
|
package/plugins.d.ts
CHANGED
package/redux/reducers/index.ts
CHANGED
|
@@ -6,6 +6,7 @@ import { api } from '../../data/client/api';
|
|
|
6
6
|
|
|
7
7
|
// Plugin reducers
|
|
8
8
|
import { masterpassReducer } from '@akinon/pz-masterpass';
|
|
9
|
+
import { otpReducer } from '@akinon/pz-otp';
|
|
9
10
|
|
|
10
11
|
const reducers = {
|
|
11
12
|
[api.reducerPath]: api.reducer,
|
|
@@ -13,7 +14,8 @@ const reducers = {
|
|
|
13
14
|
checkout: checkoutReducer,
|
|
14
15
|
config: configReducer,
|
|
15
16
|
header: headerReducer,
|
|
16
|
-
masterpass: masterpassReducer
|
|
17
|
+
masterpass: masterpassReducer,
|
|
18
|
+
otp: otpReducer
|
|
17
19
|
};
|
|
18
20
|
|
|
19
21
|
export default reducers;
|
package/sentry/index.ts
CHANGED
|
@@ -1,27 +1,33 @@
|
|
|
1
1
|
import * as Sentry from '@sentry/nextjs';
|
|
2
|
+
import settings from 'settings';
|
|
2
3
|
|
|
3
4
|
const SENTRY_DSN: string =
|
|
4
|
-
|
|
5
|
+
settings.sentryDsn ||
|
|
6
|
+
process.env.SENTRY_DSN ||
|
|
7
|
+
process.env.NEXT_PUBLIC_SENTRY_DSN;
|
|
5
8
|
|
|
6
9
|
export const initSentry = (
|
|
7
10
|
type: 'Server' | 'Client' | 'Edge',
|
|
8
|
-
options: Sentry.BrowserOptions | Sentry.NodeOptions | Sentry.EdgeOptions = {
|
|
11
|
+
options: Sentry.BrowserOptions | Sentry.NodeOptions | Sentry.EdgeOptions = {
|
|
12
|
+
dsn: SENTRY_DSN,
|
|
13
|
+
integrations: [],
|
|
14
|
+
tracesSampleRate: 1.0
|
|
15
|
+
}
|
|
9
16
|
) => {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
// TODO: Remove Zero Project DSN
|
|
13
|
-
|
|
14
|
-
Sentry.init({
|
|
15
|
-
dsn:
|
|
16
|
-
SENTRY_DSN ||
|
|
17
|
-
'https://d8558ef8997543deacf376c7d8d7cf4b@o64293.ingest.sentry.io/4504338423742464',
|
|
17
|
+
const initOptions = {
|
|
18
|
+
...options,
|
|
18
19
|
initialScope: {
|
|
19
20
|
tags: {
|
|
21
|
+
...((
|
|
22
|
+
options.initialScope as {
|
|
23
|
+
tags?: Record<string, string>;
|
|
24
|
+
}
|
|
25
|
+
)?.tags ?? {}),
|
|
20
26
|
APP_TYPE: 'ProjectZeroNext',
|
|
21
27
|
TYPE: type
|
|
22
28
|
}
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
Sentry.init(initOptions);
|
|
27
33
|
};
|
package/types/index.ts
CHANGED
|
@@ -71,6 +71,12 @@ export interface Currency {
|
|
|
71
71
|
|
|
72
72
|
export interface Settings {
|
|
73
73
|
commerceUrl: string;
|
|
74
|
+
/**
|
|
75
|
+
* This option allows you to track Sentry events on the client side, in addition to server and edge environments.
|
|
76
|
+
*
|
|
77
|
+
* It overrides process.env.NEXT_PUBLIC_SENTRY_DSN and process.env.SENTRY_DSN.
|
|
78
|
+
*/
|
|
79
|
+
sentryDsn?: string;
|
|
74
80
|
redis: {
|
|
75
81
|
defaultExpirationTime: number;
|
|
76
82
|
};
|
package/utils/app-fetch.ts
CHANGED
|
@@ -12,7 +12,7 @@ const appFetch = async <T>(
|
|
|
12
12
|
url: RequestInfo,
|
|
13
13
|
init: RequestInit = {},
|
|
14
14
|
responseType = FetchResponseType.JSON
|
|
15
|
-
) => {
|
|
15
|
+
): Promise<T> => {
|
|
16
16
|
let response: T;
|
|
17
17
|
let status: number;
|
|
18
18
|
let ip = '';
|
|
@@ -28,7 +28,7 @@ const appFetch = async <T>(
|
|
|
28
28
|
|
|
29
29
|
if (commerceUrl === 'default') {
|
|
30
30
|
logger.error('Commerce URL is not set. Current value is "default"');
|
|
31
|
-
|
|
31
|
+
throw new Error('Commerce URL is not set');
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
const requestURL = `${decodeURIComponent(commerceUrl)}${url}`;
|
|
@@ -48,19 +48,30 @@ const appFetch = async <T>(
|
|
|
48
48
|
status = req.status;
|
|
49
49
|
logger.debug(`FETCH END ${url}`, { status: req.status, ip });
|
|
50
50
|
|
|
51
|
-
|
|
51
|
+
if (!req.ok) {
|
|
52
|
+
throw new Error(`Request failed with status ${status}`);
|
|
53
|
+
}
|
|
52
54
|
|
|
53
55
|
if (responseType === FetchResponseType.JSON) {
|
|
54
|
-
response =
|
|
56
|
+
response = (await req.json()) as T;
|
|
55
57
|
} else {
|
|
56
|
-
response =
|
|
58
|
+
response = (await req.text()) as unknown as T;
|
|
57
59
|
}
|
|
58
60
|
|
|
59
61
|
logger.trace(`FETCH RESPONSE`, { url, response, ip });
|
|
60
62
|
} catch (error) {
|
|
63
|
+
const logType = status === 500 ? 'fatal' : 'error';
|
|
64
|
+
|
|
61
65
|
if (!url.toString().includes('/cms/seo/')) {
|
|
62
|
-
logger
|
|
66
|
+
logger[logType](`FETCH FAILED`, { url, status, error, ip });
|
|
63
67
|
}
|
|
68
|
+
|
|
69
|
+
// throw the error if it's fatal, so it can be caught and handled at higher levels
|
|
70
|
+
if (logType === 'fatal') {
|
|
71
|
+
throw error;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return Promise.reject(error);
|
|
64
75
|
}
|
|
65
76
|
|
|
66
77
|
return response;
|