@akinon/next 2.0.16-beta.0 → 2.0.16-rc.1

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.
@@ -7,224 +7,111 @@ import { getCheckoutPath } from '../utils';
7
7
 
8
8
  const withMasterpassRestCallback =
9
9
  (middleware: NextMiddleware) =>
10
- async (req: PzNextRequest, event: NextFetchEvent) => {
11
- const url = req.nextUrl.clone();
12
- const ip = req.headers.get('x-forwarded-for') ?? '';
13
- const sessionId = req.cookies.get('osessionid');
14
-
15
- if (!url.pathname.includes('/orders/masterpass-rest-callback')) {
16
- return middleware(req, event);
17
- }
18
-
19
- if (req.method !== 'POST') {
20
- logger.warn('Invalid request method for masterpass REST callback', {
21
- middleware: 'masterpass-rest-callback',
22
- method: req.method,
23
- ip
24
- });
25
-
26
- return NextResponse.redirect(
27
- `${url.origin}${getUrlPathWithLocale(
28
- '/orders/checkout/',
29
- req.cookies.get('pz-locale')?.value
30
- )}`,
31
- 303
32
- );
33
- }
34
-
35
- const responseCode = url.searchParams.get('responseCode');
36
- const token = url.searchParams.get('token');
37
-
38
- if (!responseCode || !token) {
39
- logger.warn('Missing required parameters for masterpass REST callback', {
40
- middleware: 'masterpass-rest-callback',
41
- responseCode,
42
- token,
43
- ip
44
- });
45
-
46
- return NextResponse.redirect(
47
- `${url.origin}${getUrlPathWithLocale(
48
- '/orders/checkout/',
49
- req.cookies.get('pz-locale')?.value
50
- )}`,
51
- 303
52
- );
53
- }
54
-
55
- try {
56
- const formData = await req.formData();
57
- const body: Record<string, string> = {};
58
-
59
- Array.from(formData.entries()).forEach(([key, value]) => {
60
- body[key] = value.toString();
61
- });
62
-
63
- if (!sessionId) {
64
- logger.warn(
65
- 'Make sure that the SESSION_COOKIE_SAMESITE environment variable is set to None in Commerce.',
66
- {
67
- middleware: 'masterpass-rest-callback',
68
- ip
69
- }
70
- );
10
+ async (req: PzNextRequest, event: NextFetchEvent) => {
11
+ const url = req.nextUrl.clone();
12
+ const ip = req.headers.get('x-forwarded-for') ?? '';
71
13
 
72
- return NextResponse.redirect(
73
- `${url.origin}${getUrlPathWithLocale(
74
- '/orders/checkout/',
75
- req.cookies.get('pz-locale')?.value
76
- )}`,
77
- 303
78
- );
79
- }
14
+ const isMasterpassCompletePage =
15
+ url.pathname.includes('/orders/checkout') &&
16
+ url.searchParams.get('page') === 'MasterpassRestCompletePage';
80
17
 
81
- const isPostCheckout = req.cookies.get('pz-post-checkout-flow')?.value === 'true';
82
- const requestUrl = new URL(getCheckoutPath(isPostCheckout), Settings.commerceUrl);
83
- requestUrl.searchParams.set('page', 'MasterpassRestCompletePage');
84
- requestUrl.searchParams.set('responseCode', responseCode);
85
- requestUrl.searchParams.set('token', token);
86
- requestUrl.searchParams.set(
87
- 'three_d_secure',
88
- body.transactionType?.includes('3D') ? 'true' : 'false'
89
- );
90
- requestUrl.searchParams.set(
91
- 'transactionType',
92
- body.transactionType || ''
93
- );
94
-
95
- const requestHeaders = {
96
- 'Content-Type': 'application/x-www-form-urlencoded',
97
- 'X-Requested-With': 'XMLHttpRequest',
98
- Cookie: req.headers.get('cookie') ?? '',
99
- 'x-currency': req.cookies.get('pz-currency')?.value ?? '',
100
- 'x-forwarded-for': ip,
101
- 'User-Agent': req.headers.get('user-agent') ?? ''
102
- };
103
-
104
- const request = await fetch(requestUrl.toString(), {
105
- method: 'POST',
106
- headers: requestHeaders,
107
- body: new URLSearchParams(body)
108
- });
109
-
110
- logger.info('Masterpass REST callback request', {
111
- requestUrl: requestUrl.toString(),
112
- status: request.status,
113
- requestHeaders,
114
- ip
115
- });
116
-
117
- const response = await request.json();
118
-
119
- const { context_list: contextList, errors } = response;
120
-
121
- let redirectUrl = response.redirect_url;
122
-
123
- if (!redirectUrl && contextList && contextList.length > 0) {
124
- for (const context of contextList) {
125
- if (context.page_context && context.page_context.redirect_url) {
126
- redirectUrl = context.page_context.redirect_url;
127
- break;
128
- }
129
- }
18
+ if (!isMasterpassCompletePage) {
19
+ return middleware(req, event);
130
20
  }
131
21
 
132
- if (errors && Object.keys(errors).length) {
133
- logger.error('Error while processing masterpass REST callback', {
134
- middleware: 'masterpass-rest-callback',
135
- errors,
136
- requestHeaders,
137
- ip
22
+ try {
23
+ const isPostCheckout = req.cookies.get('pz-post-checkout-flow')?.value === 'true';
24
+ const requestUrl = new URL(getCheckoutPath(isPostCheckout), Settings.commerceUrl);
25
+
26
+ url.searchParams.forEach((value, key) => {
27
+ requestUrl.searchParams.set(key, value);
138
28
  });
139
29
 
140
- const errorResponse = NextResponse.redirect(
141
- `${url.origin}${getUrlPathWithLocale(
142
- '/orders/checkout/',
143
- req.cookies.get('pz-locale')?.value
144
- )}`,
145
- {
146
- status: 303,
147
- headers: {
148
- 'Set-Cookie': `pz-pos-error=${encodeURIComponent(JSON.stringify(errors))}; path=/;`
149
- }
150
- }
151
- );
30
+ const formData = await req.formData();
31
+ const body: Record<string, string> = {};
152
32
 
153
- // Add error cookie
154
- errorResponse.cookies.set('pz-pos-error', JSON.stringify(errors), {
155
- path: '/'
33
+ Array.from(formData.entries()).forEach(([key, value]) => {
34
+ body[key] = value.toString();
156
35
  });
157
36
 
158
- return errorResponse;
159
- }
37
+ const requestHeaders = {
38
+ 'Content-Type': 'application/x-www-form-urlencoded',
39
+ 'X-Requested-With': 'XMLHttpRequest',
40
+ Cookie: req.headers.get('cookie') ?? '',
41
+ 'x-currency': req.cookies.get('pz-currency')?.value ?? '',
42
+ 'x-forwarded-for': ip,
43
+ 'User-Agent': req.headers.get('user-agent') ?? ''
44
+ };
45
+
46
+ const request = await fetch(requestUrl.toString(), {
47
+ method: 'POST',
48
+ headers: requestHeaders,
49
+ body: new URLSearchParams(body)
50
+ });
160
51
 
161
- logger.info('Masterpass REST callback response', {
162
- middleware: 'masterpass-rest-callback',
163
- contextList,
164
- redirectUrl,
165
- ip
166
- });
167
-
168
- if (!redirectUrl) {
169
- logger.warn(
170
- 'No redirection url found in response. Redirecting to checkout page.',
171
- {
52
+ const response = await request.json();
53
+ const { errors } = response;
54
+
55
+ if (errors && Object.keys(errors).length) {
56
+ logger.error('Error while processing MasterpassRestCompletePage', {
172
57
  middleware: 'masterpass-rest-callback',
173
- requestHeaders,
174
- response: JSON.stringify(response),
58
+ errors,
175
59
  ip
176
- }
177
- );
178
-
179
- const redirectUrlWithLocale = `${url.origin}${getUrlPathWithLocale(
180
- '/orders/checkout/',
181
- req.cookies.get('pz-locale')?.value
182
- )}`;
183
-
184
- return NextResponse.redirect(redirectUrlWithLocale, 303);
185
- }
186
-
187
- const redirectUrlWithLocale = `${url.origin}${getUrlPathWithLocale(
188
- redirectUrl,
189
- req.cookies.get('pz-locale')?.value
190
- )}`;
60
+ });
61
+
62
+ const errorResponse = NextResponse.redirect(
63
+ `${url.origin}${getUrlPathWithLocale(
64
+ '/orders/checkout/',
65
+ req.cookies.get('pz-locale')?.value
66
+ )}`,
67
+ 303
68
+ );
69
+
70
+ // Add error cookie
71
+ errorResponse.cookies.set('pz-pos-error', JSON.stringify(errors), {
72
+ path: '/'
73
+ });
74
+
75
+ return errorResponse;
76
+ }
191
77
 
192
- logger.info('Redirecting after masterpass REST callback', {
193
- middleware: 'masterpass-rest-callback',
194
- redirectUrl: redirectUrlWithLocale,
195
- ip
196
- });
78
+ const redirectUrl =
79
+ response.redirect_url ||
80
+ response.context_list?.[0]?.page_context?.redirect_url;
81
+
82
+ if (redirectUrl) {
83
+ const nextResponse = NextResponse.redirect(
84
+ `${url.origin}${getUrlPathWithLocale(redirectUrl, req.cookies.get('pz-locale')?.value)}`,
85
+ 303
86
+ );
87
+
88
+ // Forward set-cookie headers from the upstream response
89
+ const setCookieHeader = request.headers.get('set-cookie');
90
+ if (setCookieHeader) {
91
+ setCookieHeader.split(',').forEach((cookie) => {
92
+ nextResponse.headers.append('Set-Cookie', cookie.trim());
93
+ });
94
+ }
197
95
 
198
- const nextResponse = NextResponse.redirect(redirectUrlWithLocale, 303);
96
+ return nextResponse;
97
+ }
199
98
 
200
- // Forward set-cookie headers from the upstream response
201
- const setCookieHeader = request.headers.get('set-cookie');
202
- if (setCookieHeader) {
203
- setCookieHeader.split(',').forEach((cookie) => {
204
- nextResponse.headers.append('Set-Cookie', cookie.trim());
99
+ return NextResponse.redirect(
100
+ `${url.origin}${getUrlPathWithLocale('/orders/checkout/', req.cookies.get('pz-locale')?.value)}`,
101
+ 303
102
+ );
103
+ } catch (error) {
104
+ logger.error('Error while processing MasterpassRestCompletePage', {
105
+ middleware: 'masterpass-rest-callback',
106
+ error,
107
+ ip
205
108
  });
206
- }
207
109
 
208
- return nextResponse;
209
- } catch (error) {
210
- logger.error('Error while processing masterpass REST callback', {
211
- middleware: 'masterpass-rest-callback',
212
- error,
213
- requestHeaders: {
214
- Cookie: req.headers.get('cookie') ?? '',
215
- 'x-currency': req.cookies.get('pz-currency')?.value ?? ''
216
- },
217
- ip
218
- });
219
-
220
- return NextResponse.redirect(
221
- `${url.origin}${getUrlPathWithLocale(
222
- '/orders/checkout/',
223
- req.cookies.get('pz-locale')?.value
224
- )}`,
225
- 303
226
- );
227
- }
228
- };
110
+ return NextResponse.redirect(
111
+ `${url.origin}${getUrlPathWithLocale('/orders/checkout/', req.cookies.get('pz-locale')?.value)}`,
112
+ 303
113
+ );
114
+ }
115
+ };
229
116
 
230
117
  export default withMasterpassRestCallback;
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": "2.0.16-beta.0",
4
+ "version": "2.0.16-rc.1",
5
5
  "private": false,
6
6
  "license": "MIT",
7
7
  "bin": {
@@ -36,7 +36,7 @@
36
36
  "set-cookie-parser": "2.6.0"
37
37
  },
38
38
  "devDependencies": {
39
- "@akinon/eslint-plugin-projectzero": "2.0.16-beta.0",
39
+ "@akinon/eslint-plugin-projectzero": "2.0.16-rc.1",
40
40
  "@babel/core": "7.26.10",
41
41
  "@babel/preset-env": "7.26.9",
42
42
  "@babel/preset-typescript": "7.27.0",
package/plugins.d.ts CHANGED
@@ -37,6 +37,16 @@ declare module '@akinon/pz-cybersource-uc/src/redux/middleware' {
37
37
  export default middleware as any;
38
38
  }
39
39
 
40
+ declare module '@akinon/pz-apple-pay' {}
41
+
42
+ declare module '@akinon/pz-similar-products' {
43
+ export const SimilarProductsModal: any;
44
+ export const SimilarProductsFilterSidebar: any;
45
+ export const SimilarProductsResultsGrid: any;
46
+ export const SimilarProductsPlugin: any;
47
+ export const SimilarProductsButtonPlugin: any;
48
+ }
49
+
40
50
  declare module '@akinon/pz-cybersource-uc/src/redux/reducer' {
41
51
  export default reducer as any;
42
52
  }
package/plugins.js CHANGED
@@ -16,6 +16,7 @@ module.exports = [
16
16
  'pz-tabby-extension',
17
17
  'pz-apple-pay',
18
18
  'pz-tamara-extension',
19
+ 'pz-similar-products',
19
20
  'pz-cybersource-uc',
20
21
  'pz-hepsipay',
21
22
  'pz-flow-payment',
@@ -26,13 +26,28 @@ import {
26
26
  } from '../../redux/reducers/checkout';
27
27
  import { RootState, TypedDispatch } from 'redux/store';
28
28
  import { checkoutApi } from '../../data/client/checkout';
29
- import { CheckoutContext, PreOrder } from '../../types';
29
+ import { CheckoutContext, MiddlewareAction, PreOrder } from '../../types';
30
30
  import { getCookie } from '../../utils';
31
31
  import settings from 'settings';
32
32
  import { LocaleUrlStrategy } from '../../localization';
33
33
  import { showMobile3dIframe } from '../../utils/mobile-3d-iframe';
34
34
  import { showRedirectionIframe } from '../../utils/redirection-iframe';
35
35
 
36
+ const IFRAME_REDIRECTION_KEY = 'pz-iframe-redirection-active';
37
+
38
+ const isIframeRedirectionActive = () =>
39
+ typeof window !== 'undefined' &&
40
+ sessionStorage.getItem(IFRAME_REDIRECTION_KEY) === 'true';
41
+
42
+ const setIframeRedirectionActive = (active: boolean) => {
43
+ if (typeof window === 'undefined') return;
44
+ if (active) {
45
+ sessionStorage.setItem(IFRAME_REDIRECTION_KEY, 'true');
46
+ } else {
47
+ sessionStorage.removeItem(IFRAME_REDIRECTION_KEY);
48
+ }
49
+ };
50
+
36
51
  interface CheckoutResult {
37
52
  payload: {
38
53
  errors?: Record<string, string[]>;
@@ -69,7 +84,7 @@ export const redirectUrlMiddleware: Middleware = () => {
69
84
  const result = next(action) as CheckoutResult;
70
85
  const redirectUrl = result?.payload?.redirect_url;
71
86
 
72
- if (redirectUrl) {
87
+ if (redirectUrl && !isIframeRedirectionActive()) {
73
88
  const currentLocale = getCookie('pz-locale');
74
89
 
75
90
  let url = redirectUrl;
@@ -99,8 +114,15 @@ export const contextListMiddleware: Middleware = ({
99
114
  const { isMobileApp, userPhoneNumber } = getState().root;
100
115
  const result = next(action) as CheckoutResult;
101
116
  const preOrder = result?.payload?.pre_order;
117
+ const act = action as MiddlewareAction;
102
118
 
103
119
  if (result?.payload?.context_list) {
120
+ const endpointName = act.meta?.arg?.endpointName;
121
+ const isBinNumberResponse = endpointName === 'setBinNumber';
122
+ const hasCardTypeInContextList = result.payload.context_list.some(
123
+ (ctx) => ctx.page_context.card_type
124
+ );
125
+
104
126
  result.payload.context_list.forEach((context) => {
105
127
  const redirectUrl = context.page_context.redirect_url;
106
128
  const isIframe = context.page_context.is_iframe ?? false;
@@ -134,6 +156,7 @@ export const contextListMiddleware: Middleware = ({
134
156
  if (isMobileDevice && isIframePaymentOptionIncluded) {
135
157
  showMobile3dIframe(urlObj.toString());
136
158
  } else if (isIframe) {
159
+ setIframeRedirectionActive(true);
137
160
  showRedirectionIframe(urlObj.toString());
138
161
  } else {
139
162
  window.location.href = urlObj.toString();
@@ -209,15 +232,34 @@ export const contextListMiddleware: Middleware = ({
209
232
  (ctx) => ctx.page_name === 'DeliveryOptionSelectionPage'
210
233
  )
211
234
  ) {
235
+ const isCreditCardPayment =
236
+ preOrder?.payment_option?.payment_type === 'credit_card' ||
237
+ preOrder?.payment_option?.payment_type === 'masterpass';
238
+
212
239
  if (context.page_context.card_type) {
213
240
  dispatch(setCardType(context.page_context.card_type));
241
+ } else if (
242
+ isCreditCardPayment &&
243
+ isBinNumberResponse &&
244
+ !hasCardTypeInContextList
245
+ ) {
246
+ dispatch(setCardType(null));
247
+ dispatch(setInstallmentOptions([]));
214
248
  }
215
249
 
216
250
  if (
217
251
  context.page_context.installments &&
218
252
  preOrder?.payment_option?.payment_type !== 'masterpass_rest'
219
253
  ) {
220
- dispatch(setInstallmentOptions(context.page_context.installments));
254
+ if (
255
+ !isCreditCardPayment ||
256
+ context.page_context.card_type ||
257
+ hasCardTypeInContextList
258
+ ) {
259
+ dispatch(
260
+ setInstallmentOptions(context.page_context.installments)
261
+ );
262
+ }
221
263
  }
222
264
  }
223
265
 
@@ -14,9 +14,17 @@ export const installmentOptionMiddleware: Middleware = ({
14
14
  return result;
15
15
  }
16
16
 
17
- const { installmentOptions } = getState().checkout;
17
+ const { installmentOptions, cardType } = getState().checkout;
18
18
  const { endpoints: apiEndpoints } = checkoutApi;
19
19
 
20
+ const isCreditCardPayment =
21
+ preOrder?.payment_option?.payment_type === 'credit_card' ||
22
+ preOrder?.payment_option?.payment_type === 'masterpass';
23
+
24
+ if (isCreditCardPayment && !cardType) {
25
+ return result;
26
+ }
27
+
20
28
  if (
21
29
  !preOrder?.installment &&
22
30
  preOrder?.payment_option?.payment_type !== 'saved_card' &&
package/types/index.ts CHANGED
@@ -85,6 +85,31 @@ export interface Settings {
85
85
  };
86
86
  usePrettyUrlRoute?: boolean;
87
87
  commerceUrl: string;
88
+ /**
89
+ * This option allows you to track Sentry events on the client side, in addition to server and edge environments.
90
+ *
91
+ * It overrides process.env.NEXT_PUBLIC_SENTRY_DSN and process.env.SENTRY_DSN.
92
+ */
93
+ sentryDsn?: string;
94
+ /**
95
+ * CSRF cookie hardening settings.
96
+ *
97
+ * When `httpOnly` is `true`, the `csrftoken` cookie is set with the
98
+ * `HttpOnly`, `Secure` (production) and `SameSite=Lax` flags. The token is
99
+ * never exposed to the browser: instead of the client mirroring the cookie
100
+ * into the `x-csrftoken` header, the Next.js proxy (BFF) reads the cookie
101
+ * server-side and injects the header before forwarding state-changing
102
+ * requests to the commerce backend, after validating the request `Origin`.
103
+ *
104
+ * Because the token no longer reaches JS, brand code must NOT rely on
105
+ * reading `csrftoken` from `document.cookie` or `getCookie('csrftoken')`
106
+ * when this flag is enabled — all CSRF handling happens on the server.
107
+ *
108
+ * @default false
109
+ */
110
+ csrf?: {
111
+ httpOnly?: boolean;
112
+ };
88
113
  redis: {
89
114
  defaultExpirationTime: number;
90
115
  };
@@ -217,6 +242,7 @@ export interface Settings {
217
242
  separator?: string;
218
243
  segments: PzSegmentDefinition[];
219
244
  };
245
+ payloadOptimization?: import('../utils/payload-optimizer').PayloadOptimizationConfig;
220
246
  }
221
247
 
222
248
  export interface CacheOptions {
@@ -276,7 +302,9 @@ export interface PzSegmentsConfig {
276
302
  }
277
303
 
278
304
  // Search params type compatible with both Next.js resolved searchParams and URLSearchParams
279
- export type SearchParams = Record<string, string | string[] | undefined> | URLSearchParams;
305
+ export type SearchParams =
306
+ | Record<string, string | string[] | undefined>
307
+ | URLSearchParams;
280
308
 
281
309
  // Raw Next 16 server prop shape, used at the middleware/HOC boundary before normalization
282
310
  export type RawSearchParams = Record<string, string | string[] | undefined>;
@@ -309,14 +337,16 @@ export interface RootLayoutProps<T = any> extends LayoutProps<T> {
309
337
 
310
338
  // Async versions for Next.js 16 generateMetadata and internal use
311
339
  export interface AsyncPageProps<T = any> {
312
- params: Promise<T & {
313
- pz?: string;
314
- commerce?: string;
315
- locale?: string;
316
- currency?: string;
317
- url?: string;
318
- [key: string]: any;
319
- }>;
340
+ params: Promise<
341
+ T & {
342
+ pz?: string;
343
+ commerce?: string;
344
+ locale?: string;
345
+ currency?: string;
346
+ url?: string;
347
+ [key: string]: any;
348
+ }
349
+ >;
320
350
  searchParams: Promise<{ [key: string]: string | string[] | undefined }>;
321
351
  }
322
352
 
package/utils/csrf.ts ADDED
@@ -0,0 +1,37 @@
1
+ import settings from 'settings';
2
+
3
+ /**
4
+ * Whether the Django `csrftoken` cookie should be hardened with the
5
+ * `HttpOnly` flag.
6
+ *
7
+ * When enabled, the token never reaches the browser: the Next.js proxy
8
+ * (BFF) reads the cookie server-side and injects the `x-csrftoken` header
9
+ * before forwarding state-changing requests to the commerce backend. See
10
+ * `api/client.ts`.
11
+ */
12
+ export function isCsrfHttpOnly(): boolean {
13
+ return settings.csrf?.httpOnly === true;
14
+ }
15
+
16
+ export interface CsrfCookieFlags {
17
+ httpOnly: boolean;
18
+ secure: boolean;
19
+ sameSite: 'lax';
20
+ }
21
+
22
+ /**
23
+ * Cookie attributes applied to the `csrftoken` cookie wherever it is set
24
+ * (middleware bootstrap, auth login rotation, proxy passthrough).
25
+ *
26
+ * `SameSite=Lax` is the Next.js-layer CSRF defense: the browser will not
27
+ * attach the cookie on cross-site state-changing requests, so the proxy's
28
+ * server-side header injection cannot be abused from a third-party origin.
29
+ */
30
+ export function getCsrfCookieFlags(): CsrfCookieFlags {
31
+ const httpOnly = isCsrfHttpOnly();
32
+ return {
33
+ httpOnly,
34
+ secure: process.env.NODE_ENV === 'production',
35
+ sameSite: 'lax'
36
+ };
37
+ }
@@ -1,4 +1,5 @@
1
1
  import Settings from 'settings';
2
+ import { LocaleUrlStrategy } from '../localization';
2
3
 
3
4
  export default function getRootHostname(
4
5
  url: string | undefined
@@ -26,3 +27,20 @@ export default function getRootHostname(
26
27
  return null;
27
28
  }
28
29
  }
30
+
31
+ interface HeaderLike {
32
+ get(name: string): string | null;
33
+ }
34
+
35
+ export function getRequestRootHostname(
36
+ headerStore: HeaderLike
37
+ ): string | null {
38
+ if (Settings.localization.localeUrlStrategy !== LocaleUrlStrategy.Subdomain) {
39
+ return null;
40
+ }
41
+
42
+ const fallbackHost =
43
+ headerStore.get('x-forwarded-host') || headerStore.get('host');
44
+ const hostname = process.env.NEXT_PUBLIC_URL || `https://${fallbackHost}`;
45
+ return getRootHostname(hostname);
46
+ }
package/utils/index.ts CHANGED
@@ -10,6 +10,7 @@ export * from './get-currency-label';
10
10
  export * from './pz-segments';
11
11
  export * from './get-checkout-path';
12
12
  export * from './format-error-message';
13
+ export * from './csrf';
13
14
 
14
15
  export function getCookie(name: string) {
15
16
  if (typeof document === 'undefined') {