@akinon/next 1.21.0-rc.1 → 1.21.0-rc.3

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 CHANGED
@@ -1,5 +1,21 @@
1
1
  # @akinon/next
2
2
 
3
+ ## 1.21.0-rc.3
4
+
5
+ ### Minor Changes
6
+
7
+ - af8e38e0: ZERO-2247: Add additionalParams to MasterpassProvider and set them in buildPurchaseForm and buildDirectPurchaseForm
8
+ - 16027410: ZERO-2451: Refactor extraHeaders assignment in client.ts
9
+ - 9edd725b: Datalayer init changed to 3rd party script. Declare is the same as next
10
+
11
+ ## 1.21.0-rc.2
12
+
13
+ ### Minor Changes
14
+
15
+ - 8075006: install plugins check the akinon version
16
+ - 32668ed: ZERO-2296: Implement Checkout Provider Middleware and Enhancements
17
+ - 29191da: ImageLoader supports crop none
18
+
3
19
  ## 1.21.0-rc.1
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
  };
package/api/client.ts CHANGED
@@ -43,13 +43,18 @@ async function proxyRequest(...args) {
43
43
  urlSearchParams.append(key, `${searchParams.get(key)}`);
44
44
  });
45
45
 
46
- const extraHeaders = Object.fromEntries(req.headers.entries());
46
+ const extraHeaders: Record<string, string> = {};
47
+
48
+ for (const [key, value] of Array.from(req.headers.entries())) {
49
+ extraHeaders[key.toLowerCase()] = value;
50
+ }
47
51
 
48
52
  [
49
53
  'x-forwarded-host',
50
54
  'x-forwarded-for',
51
55
  'x-forwarded-proto',
52
56
  'x-forwarded-port',
57
+ 'x-requested-with',
53
58
  'origin',
54
59
  'host',
55
60
  'referer',
@@ -1,39 +1,40 @@
1
- #!/usr/bin/env node
2
-
3
- const fs = require('fs');
4
1
  const path = require('path');
5
- const rootDir = path.resolve(process.cwd());
6
2
  const spawn = require('cross-spawn');
7
- const availablePlugins = require('../plugins');
8
-
9
- let plugins;
10
3
 
11
- try {
12
- plugins = require(path.resolve(rootDir, './src/plugins.js'));
13
- } catch (error) {
14
- console.error('No plugins.js file found, skipping plugin installation.');
15
- process.exit(0);
4
+ function findBaseDir() {
5
+ const insideNodeModules = __dirname.includes('node_modules');
6
+ return insideNodeModules
7
+ ? path.resolve(__dirname, '../../../../')
8
+ : path.resolve(__dirname, '../../../apps/projectzeronext');
16
9
  }
17
10
 
18
- let installCmd = [];
11
+ const BASE_DIR = findBaseDir();
19
12
 
20
- availablePlugins
21
- .filter((p) => plugins?.includes(p))
22
- .forEach((name) => {
23
- installCmd.push(`@akinon/${name}`);
24
- });
13
+ const getFullPath = (relativePath) => path.join(BASE_DIR, relativePath);
25
14
 
26
- spawn.sync('yarn', ['cache clean']);
15
+ const packageJsonPath = getFullPath('package.json');
16
+ const akinonPackageNumber = require(packageJsonPath).dependencies[
17
+ '@akinon/next'
18
+ ].replace('^', '');
19
+ //just in case
27
20
 
28
- for (const plugin of availablePlugins) {
29
- spawn.sync('yarn', ['remove', `@akinon/${plugin}`]);
30
- }
21
+ const availablePlugins = require('../plugins');
22
+
23
+ let installCmd = availablePlugins.map(
24
+ (name) => `@akinon/${name}@${akinonPackageNumber}`
25
+ );
26
+
27
+ spawn.sync('yarn', ['cache clean'], { stdio: 'inherit', cwd: BASE_DIR });
31
28
 
32
- if (
33
- installCmd.length > 0 &&
34
- !fs.existsSync(path.resolve(rootDir, '../../turbo.json'))
35
- ) {
36
- spawn.sync('yarn', ['add', ...installCmd, '--ignore-scripts'], {
37
- stdio: 'inherit'
29
+ availablePlugins.forEach((plugin) => {
30
+ spawn.sync('yarn', ['remove', `@akinon/${plugin}`], {
31
+ stdio: 'inherit',
32
+ cwd: BASE_DIR
38
33
  });
34
+ });
35
+
36
+ if (installCmd.length > 0) {
37
+ const yarnArgs = ['add', ...installCmd, '--ignore-scripts'];
38
+
39
+ spawn.sync('yarn', yarnArgs, { stdio: 'inherit', cwd: BASE_DIR });
39
40
  }
@@ -83,12 +83,18 @@ const completeMasterpassPayment = async (
83
83
  reduxStore: AppStore
84
84
  ) => {
85
85
  const state = reduxStore.getState();
86
- const { token, order_no } = context.page_context;
86
+ const { token, order_no, extras } = context.page_context;
87
87
  const { endpoints: apiEndpoints } = checkoutApi;
88
88
  const { preOrder } = state.checkout ?? {};
89
89
 
90
- const { isDirectPurchase, referenceNo, credentials, msisdn, selectedCard } =
91
- state.masterpass ?? {};
90
+ const {
91
+ isDirectPurchase,
92
+ referenceNo,
93
+ credentials,
94
+ msisdn,
95
+ selectedCard,
96
+ additionalParams
97
+ } = state.masterpass ?? {};
92
98
 
93
99
  const commonFormValues = {
94
100
  token,
@@ -96,12 +102,16 @@ const completeMasterpassPayment = async (
96
102
  referenceNo,
97
103
  merchantId: credentials?.merchant_id ?? null,
98
104
  msisdn,
99
- amount: preOrder?.unpaid_amount?.replace('.', '') ?? ''
105
+ amount: preOrder?.unpaid_amount?.replace('.', '') ?? '',
106
+ additionalParams: additionalParams ?? extras?.additionalParams
100
107
  };
101
108
 
102
109
  const form = isDirectPurchase
103
110
  ? await buildDirectPurchaseForm(commonFormValues, params)
104
- : await buildPurchaseForm({ ...commonFormValues, selectedCard });
111
+ : await buildPurchaseForm({
112
+ ...commonFormValues,
113
+ selectedCard
114
+ });
105
115
 
106
116
  window.MFS?.[
107
117
  state.masterpass.isDirectPurchase ? 'directPurchase' : 'purchase'
@@ -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;
@@ -2,6 +2,7 @@ import { NextFetchEvent, NextMiddleware, NextResponse } from 'next/server';
2
2
  import Settings from 'settings';
3
3
  import {
4
4
  PzNextRequest,
5
+ withCheckoutProvider,
5
6
  withCompleteGpay,
6
7
  withCompleteMasterpass,
7
8
  withOauthLogin,
@@ -16,6 +17,7 @@ import withLocale from './locale';
16
17
  import logger from '../utils/log';
17
18
  import { user } from '../data/urls';
18
19
  import { getUrlPathWithLocale } from '../utils/localization';
20
+ import { ROUTES } from 'projectzeronext/src/routes';
19
21
 
20
22
  const withPzDefault =
21
23
  (middleware: NextMiddleware) =>
@@ -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
- withUrlRedirection(
127
- withCompleteGpay(
128
- withCompleteMasterpass(
129
- async (req: PzNextRequest, event: NextFetchEvent) => {
130
- let middlewareResult: NextResponse | void =
131
- NextResponse.next();
132
-
133
- try {
134
- const { locale, prettyUrl, currency } =
135
- req.middlewareParams.rewrites;
136
- const { defaultLocaleValue } = Settings.localization;
137
- const url = req.nextUrl.clone();
138
- const pathnameWithoutLocale = url.pathname.replace(
139
- urlLocaleMatcherRegex,
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
- middlewareResult = (await middleware(
156
- req,
157
- event
158
- )) as NextResponse | void;
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
- middlewareResult.headers.get(
165
- 'pz-override-response'
166
- ) !== 'true'
215
+ !url.pathname.startsWith(`/${currency}/orders`)
167
216
  ) {
168
- middlewareResult.headers.set(
169
- 'x-middleware-rewrite',
170
- url.href
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-locale',
183
- locale?.length > 0 ? locale : defaultLocaleValue,
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
- if (req.cookies.get('pz-set-currency')) {
222
- middlewareResult.cookies.delete('pz-set-currency');
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
- 'acc-app-version',
228
- process.env.ACC_APP_VERSION
255
+ 'pz-url',
256
+ req.nextUrl.toString()
229
257
  );
230
- }
231
258
 
232
- // Set CSRF token if not set
233
- try {
234
- const url = `${Settings.commerceUrl}${user.csrfToken}`;
259
+ if (req.cookies.get('pz-set-currency')) {
260
+ middlewareResult.cookies.delete(
261
+ 'pz-set-currency'
262
+ );
263
+ }
235
264
 
236
- if (!req.cookies.get('csrftoken')) {
237
- const { csrf_token } = await (
238
- await fetch(url)
239
- ).json();
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('CSRF 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
- return middlewareResult;
259
- }
298
+ return middlewareResult;
299
+ }
300
+ )
260
301
  )
261
302
  )
262
303
  )
@@ -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.21.0-rc.1",
4
+ "version": "1.21.0-rc.3",
5
5
  "private": false,
6
6
  "license": "MIT",
7
7
  "bin": {
@@ -32,7 +32,7 @@
32
32
  "@typescript-eslint/eslint-plugin": "6.7.4",
33
33
  "@typescript-eslint/parser": "6.7.4",
34
34
  "eslint": "^8.14.0",
35
- "@akinon/eslint-plugin-projectzero": "1.21.0-rc.1",
35
+ "@akinon/eslint-plugin-projectzero": "1.21.0-rc.3",
36
36
  "eslint-config-prettier": "8.5.0"
37
37
  }
38
38
  }
package/types/index.ts CHANGED
@@ -4,8 +4,11 @@ import { Control, FieldError } from 'react-hook-form';
4
4
  import { ReactNode } from 'react';
5
5
 
6
6
  declare global {
7
- export interface Window {
8
- dataLayer: Record<string, any>[];
7
+ interface Window {
8
+ // we did it like this because declare types needs to be identical, if not it will fail
9
+ // eslint-disable-next-line @typescript-eslint/ban-types
10
+ dataLayer?: Object[];
11
+ [key: string]: any;
9
12
  }
10
13
  }
11
14
 
@@ -198,7 +201,7 @@ export type CDNOptions = {
198
201
  width?: number;
199
202
  height?: number;
200
203
  quality?: number;
201
- crop?: 'center' | 'top' | 'bottom' | 'left' | 'right';
204
+ crop?: 'center' | 'top' | 'bottom' | 'left' | 'right' | 'none';
202
205
  upscale?: boolean;
203
206
  };
204
207
 
@@ -19,12 +19,19 @@ export const ImageLoader = ({
19
19
  aspectRatio
20
20
  }: ImageLoaderParams) => {
21
21
  if (src.match(/akinoncdn|akinoncloud|b-cdn\.net/)) {
22
- return buildCDNUrl(src, {
22
+ const height =
23
+ fill && aspectRatio
24
+ ? parseInt(String(Number(width) / aspectRatio))
25
+ : undefined;
26
+
27
+ const config: CDNOptions = {
23
28
  width,
24
- height: fill ? parseInt(String(Number(width) / aspectRatio)) : undefined,
29
+ height,
25
30
  quality,
26
- crop
27
- });
31
+ crop: crop !== 'none' ? crop : undefined
32
+ };
33
+
34
+ return buildCDNUrl(src, config);
28
35
  }
29
36
 
30
37
  return src;