@akinon/pz-masterpass-rest 1.126.5 → 1.126.6

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@akinon/pz-masterpass-rest",
3
- "version": "1.126.5",
3
+ "version": "1.126.6",
4
4
  "main": "src/index.ts",
5
5
  "types": "src/index.d.ts",
6
6
  "description": "Modern React-based Masterpass REST API integration package for ProjectZero e-commerce platform",
@@ -24,6 +24,7 @@ import type {
24
24
  CardModel
25
25
  } from '../types/account.types';
26
26
  import { useMasterpassToken } from './useMasterpassToken';
27
+ import { decodeMasterpassToken } from '../utils/wait-for-utils-ready';
27
28
 
28
29
  export const useMasterpassAccount = () => {
29
30
  const dispatch = useDispatch();
@@ -132,7 +133,10 @@ export const useMasterpassAccount = () => {
132
133
  }
133
134
  const result = await refetch(options);
134
135
  if (result?.data?.token) {
135
- const decodedToken = window.Utils.decodeJwt(result.data.token);
136
+ const decodedToken = decodeMasterpassToken(result.data.token);
137
+ if (!decodedToken) {
138
+ return null;
139
+ }
136
140
  return {
137
141
  tokenData: decodedToken,
138
142
  token: result.data.token,
@@ -25,6 +25,7 @@ import {
25
25
  } from '../redux/reducer';
26
26
  import { useMasterpassToken } from './useMasterpassToken';
27
27
  import { PaymentService } from '../services/payment';
28
+ import { decodeMasterpassToken } from '../utils/wait-for-utils-ready';
28
29
  import {
29
30
  createPaymentRequest,
30
31
  createDirectPaymentRequest,
@@ -93,7 +94,10 @@ export const useMasterpassPayment = (
93
94
  const refreshTokenForPayment = useCallback(async () => {
94
95
  const result = await refetchToken({ skipReset: true });
95
96
  if (result?.data?.token) {
96
- const decodedToken = window.Utils.decodeJwt(result.data.token);
97
+ const decodedToken = decodeMasterpassToken(result.data.token);
98
+ if (!decodedToken) {
99
+ return null;
100
+ }
97
101
  return {
98
102
  tokenData: decodedToken,
99
103
  token: result.data.token,
@@ -14,6 +14,10 @@ import {
14
14
  updatePaymentState
15
15
  } from '../redux/reducer';
16
16
  import { isTokenExpired } from '../utils/payment-utils';
17
+ import {
18
+ waitForUtilsReady,
19
+ decodeMasterpassToken
20
+ } from '../utils/wait-for-utils-ready';
17
21
 
18
22
  interface UseMasterpassTokenProps {
19
23
  useThreeD: boolean;
@@ -65,12 +69,37 @@ export const useMasterpassToken = ({
65
69
  return;
66
70
  }
67
71
 
68
- if (data?.token && data.token !== reduxToken) {
69
- dispatch(setMasterpassRestToken(data.token));
70
- dispatch(setMasterpassTerminalGroupId(data.extras.terminal_group_id));
71
- dispatch(setMasterpassBankIca(data.extras.bank_ica));
72
+ if (!(data?.token && data.token !== reduxToken)) {
73
+ return;
74
+ }
75
+
76
+ const token = data.token;
77
+ const extras = data.extras;
78
+ let cancelled = false;
79
+
80
+ // The token request can resolve before the Masterpass SDK script has
81
+ // loaded (common on slow/3G networks). Wait for `window.Utils.decodeJwt`
82
+ // to exist before decoding, otherwise the checkout crashes with
83
+ // `Cannot read properties of undefined (reading 'decodeJwt')`.
84
+ //
85
+ // Redux writes are deferred until after the wait resolves: dispatching
86
+ // `setMasterpassRestToken` synchronously would change the `reduxToken`
87
+ // dependency, re-run this effect and cancel the in-flight decode.
88
+ (async () => {
89
+ const ready = await waitForUtilsReady();
90
+ if (cancelled) {
91
+ return;
92
+ }
93
+
94
+ dispatch(setMasterpassRestToken(token));
95
+ dispatch(setMasterpassTerminalGroupId(extras.terminal_group_id));
96
+ dispatch(setMasterpassBankIca(extras.bank_ica));
97
+
98
+ const decodedToken = ready ? decodeMasterpassToken(token) : undefined;
99
+ if (!decodedToken) {
100
+ return;
101
+ }
72
102
 
73
- const decodedToken = window.Utils.decodeJwt(data.token);
74
103
  dispatch(setMasterpassRestTokenData(decodedToken));
75
104
 
76
105
  if (decodedToken?.AuthenticationMethod?.toLowerCase() === 'none') {
@@ -78,7 +107,11 @@ export const useMasterpassToken = ({
78
107
  }
79
108
 
80
109
  stableOnTokenReady(decodedToken);
81
- }
110
+ })();
111
+
112
+ return () => {
113
+ cancelled = true;
114
+ };
82
115
  }, [data?.token, reduxToken, dispatch, stableOnTokenReady]);
83
116
 
84
117
  const refetchToken = useCallback(
@@ -94,15 +127,19 @@ export const useMasterpassToken = ({
94
127
  );
95
128
  dispatch(setMasterpassBankIca(result.data.extras.bank_ica));
96
129
 
97
- const decodedToken = window.Utils.decodeJwt(result.data.token);
98
- dispatch(setMasterpassRestTokenData(decodedToken));
130
+ await waitForUtilsReady();
131
+ const decodedToken = decodeMasterpassToken(result.data.token);
99
132
 
100
- if (decodedToken?.AuthenticationMethod?.toLowerCase() === 'none') {
101
- dispatch(updatePaymentState({ useThreeD: false }));
102
- }
133
+ if (decodedToken) {
134
+ dispatch(setMasterpassRestTokenData(decodedToken));
135
+
136
+ if (decodedToken?.AuthenticationMethod?.toLowerCase() === 'none') {
137
+ dispatch(updatePaymentState({ useThreeD: false }));
138
+ }
103
139
 
104
- if (options?.three_d !== false && !options?.skipReset) {
105
- stableOnTokenReady(decodedToken);
140
+ if (options?.three_d !== false && !options?.skipReset) {
141
+ stableOnTokenReady(decodedToken);
142
+ }
106
143
  }
107
144
  }
108
145
 
package/src/index.ts CHANGED
@@ -19,6 +19,7 @@ export * from './utils/reward-utils';
19
19
  export * from './utils/card-utils';
20
20
  export * from './utils/validation-schemas';
21
21
  export * from './utils/masterpass-sdk';
22
+ export * from './utils/wait-for-utils-ready';
22
23
  export type { MasterpassEnvironment } from './utils/constants';
23
24
 
24
25
  export * from './types/payment.types';
@@ -0,0 +1,64 @@
1
+ const DEFAULT_TIMEOUT_MS = 10000;
2
+ const POLL_INTERVAL_MS = 100;
3
+
4
+ interface MasterpassUtils {
5
+ decodeJwt: (token: string) => any;
6
+ [key: string]: any;
7
+ }
8
+
9
+ // `window.Utils` is attached by the Masterpass REST SDK and is undefined until
10
+ // that script finishes loading, so it is resolved through this guarded getter
11
+ // rather than accessed directly.
12
+ const getMasterpassUtils = (): MasterpassUtils | undefined =>
13
+ (window as unknown as { Utils?: MasterpassUtils }).Utils;
14
+
15
+ // The Masterpass REST SDK (`/masterpass-javascript-sdk-web.min.js`) attaches
16
+ // both `window.Masterpass` and `window.Utils` when it finishes loading. That
17
+ // script is loaded asynchronously (see `useMasterpassScript`), so on slow
18
+ // networks the `/orders/masterpass-token` response can arrive before the SDK
19
+ // is ready. Calling `window.Utils.decodeJwt(...)` at that point crashes the
20
+ // checkout with `TypeError: Cannot read properties of undefined (reading
21
+ // 'decodeJwt')` (ZERO-4362 — same class of bug already fixed in the non-rest
22
+ // `@akinon/pz-masterpass` package).
23
+ //
24
+ // This helper polls until the SDK has exposed `window.Utils.decodeJwt`, so
25
+ // callers can await readiness before decoding instead of crashing.
26
+ export const waitForUtilsReady = (
27
+ timeoutMs: number = DEFAULT_TIMEOUT_MS
28
+ ): Promise<boolean> =>
29
+ new Promise((resolve) => {
30
+ const start = Date.now();
31
+
32
+ const check = () => {
33
+ if (typeof getMasterpassUtils()?.decodeJwt === 'function') {
34
+ resolve(true);
35
+ return;
36
+ }
37
+
38
+ if (Date.now() - start >= timeoutMs) {
39
+ resolve(false);
40
+ return;
41
+ }
42
+
43
+ setTimeout(check, POLL_INTERVAL_MS);
44
+ };
45
+
46
+ check();
47
+ });
48
+
49
+ // Guarded `decodeJwt` wrapper. Returns `undefined` (instead of throwing) when
50
+ // the SDK is not yet available, so every callsite degrades gracefully.
51
+ export const decodeMasterpassToken = <T = any>(
52
+ token: string
53
+ ): T | undefined => {
54
+ const utils = getMasterpassUtils();
55
+
56
+ if (typeof utils?.decodeJwt !== 'function') {
57
+ console.error(
58
+ '[pz-masterpass-rest] window.Utils.decodeJwt is not available'
59
+ );
60
+ return undefined;
61
+ }
62
+
63
+ return utils.decodeJwt(token);
64
+ };