@deuna/react-native-sdk 1.0.0 → 1.0.2
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/lib/module/DeunaSDK.js +37 -14
- package/lib/module/DeunaSDK.js.map +1 -1
- package/lib/module/components/DeunaWebView.js +2 -1
- package/lib/module/components/DeunaWebView.js.map +1 -1
- package/lib/module/components/DeunaWidget.js +7 -2
- package/lib/module/components/DeunaWidget.js.map +1 -1
- package/lib/module/components/DeviceFingerprintWebView.js +44 -0
- package/lib/module/components/DeviceFingerprintWebView.js.map +1 -0
- package/lib/module/components/NewTabWebView.js +2 -1
- package/lib/module/components/NewTabWebView.js.map +1 -1
- package/lib/module/controllers/BaseWebViewController.js +59 -17
- package/lib/module/controllers/BaseWebViewController.js.map +1 -1
- package/lib/module/controllers/DeviceFingerprintController.js +69 -0
- package/lib/module/controllers/DeviceFingerprintController.js.map +1 -0
- package/lib/module/controllers/OpenInNewTabController.js +24 -8
- package/lib/module/controllers/OpenInNewTabController.js.map +1 -1
- package/lib/module/interfaces/constants.js +1 -0
- package/lib/module/interfaces/constants.js.map +1 -1
- package/lib/module/types/helpers/buildElementsLink.js +63 -48
- package/lib/module/types/helpers/buildElementsLink.js.map +1 -1
- package/lib/module/types/helpers/buildNextActionLink.js +7 -3
- package/lib/module/types/helpers/buildNextActionLink.js.map +1 -1
- package/lib/module/types/helpers/buildPaymentLink.js +1 -3
- package/lib/module/types/helpers/buildPaymentLink.js.map +1 -1
- package/lib/module/types/helpers/buildVoucherLink.js +1 -3
- package/lib/module/types/helpers/buildVoucherLink.js.map +1 -1
- package/lib/typescript/deuna-sdk-react-native/src/DeunaSDK.d.ts +9 -1
- package/lib/typescript/deuna-sdk-react-native/src/DeunaSDK.d.ts.map +1 -1
- package/lib/typescript/deuna-sdk-react-native/src/components/DeunaWebView.d.ts +1 -0
- package/lib/typescript/deuna-sdk-react-native/src/components/DeunaWebView.d.ts.map +1 -1
- package/lib/typescript/deuna-sdk-react-native/src/components/DeunaWidget.d.ts.map +1 -1
- package/lib/typescript/deuna-sdk-react-native/src/components/DeviceFingerprintWebView.d.ts +7 -0
- package/lib/typescript/deuna-sdk-react-native/src/components/DeviceFingerprintWebView.d.ts.map +1 -0
- package/lib/typescript/deuna-sdk-react-native/src/components/NewTabWebView.d.ts.map +1 -1
- package/lib/typescript/deuna-sdk-react-native/src/controllers/BaseWebViewController.d.ts +26 -4
- package/lib/typescript/deuna-sdk-react-native/src/controllers/BaseWebViewController.d.ts.map +1 -1
- package/lib/typescript/deuna-sdk-react-native/src/controllers/DeviceFingerprintController.d.ts +15 -0
- package/lib/typescript/deuna-sdk-react-native/src/controllers/DeviceFingerprintController.d.ts.map +1 -0
- package/lib/typescript/deuna-sdk-react-native/src/controllers/OpenInNewTabController.d.ts +2 -2
- package/lib/typescript/deuna-sdk-react-native/src/controllers/OpenInNewTabController.d.ts.map +1 -1
- package/lib/typescript/deuna-sdk-react-native/src/interfaces/constants.d.ts +1 -0
- package/lib/typescript/deuna-sdk-react-native/src/interfaces/constants.d.ts.map +1 -1
- package/lib/typescript/deuna-sdk-react-native/src/types/helpers/buildElementsLink.d.ts +5 -0
- package/lib/typescript/deuna-sdk-react-native/src/types/helpers/buildElementsLink.d.ts.map +1 -1
- package/lib/typescript/deuna-sdk-react-native/src/types/helpers/buildNextActionLink.d.ts.map +1 -1
- package/lib/typescript/deuna-sdk-react-native/src/types/helpers/buildPaymentLink.d.ts.map +1 -1
- package/lib/typescript/deuna-sdk-react-native/src/types/helpers/buildVoucherLink.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/DeunaSDK.ts +42 -14
- package/src/components/DeunaWebView.tsx +2 -0
- package/src/components/DeunaWidget.tsx +9 -0
- package/src/components/DeviceFingerprintWebView.tsx +58 -0
- package/src/components/NewTabWebView.tsx +3 -0
- package/src/controllers/BaseWebViewController.ts +76 -19
- package/src/controllers/DeviceFingerprintController.ts +86 -0
- package/src/controllers/OpenInNewTabController.ts +30 -13
- package/src/interfaces/constants.ts +3 -0
- package/src/types/helpers/buildElementsLink.ts +89 -56
- package/src/types/helpers/buildNextActionLink.ts +8 -4
- package/src/types/helpers/buildPaymentLink.ts +1 -4
- package/src/types/helpers/buildVoucherLink.ts +1 -4
|
@@ -8,6 +8,7 @@ interface DeunaWebViewProps {
|
|
|
8
8
|
onMessage?: (event: any) => void;
|
|
9
9
|
onLoad?: () => void;
|
|
10
10
|
onError?: (error: any) => void;
|
|
11
|
+
onShouldStartLoadWithRequest?: (request: any) => boolean;
|
|
11
12
|
}
|
|
12
13
|
|
|
13
14
|
export const DeunaWebView = (props: DeunaWebViewProps) => {
|
|
@@ -30,6 +31,7 @@ export const DeunaWebView = (props: DeunaWebViewProps) => {
|
|
|
30
31
|
onError={props.onError}
|
|
31
32
|
setSupportMultipleWindows={false}
|
|
32
33
|
javaScriptCanOpenWindowsAutomatically={false}
|
|
34
|
+
onShouldStartLoadWithRequest={props.onShouldStartLoadWithRequest}
|
|
33
35
|
/>
|
|
34
36
|
</View>
|
|
35
37
|
);
|
|
@@ -6,6 +6,7 @@ import { DeunaWebView } from './DeunaWebView';
|
|
|
6
6
|
import { NewTabWebView } from './NewTabWebView';
|
|
7
7
|
import { DeunaWebViewController } from '../controllers/BaseWebViewController';
|
|
8
8
|
import { Mode } from '../interfaces/types';
|
|
9
|
+
import { DeviceFingerprintWebView } from './DeviceFingerprintWebView';
|
|
9
10
|
|
|
10
11
|
interface DeunaWidgetProps {
|
|
11
12
|
instance: DeunaSDK;
|
|
@@ -67,6 +68,10 @@ export const DeunaWidget = (props: DeunaWidgetProps) => {
|
|
|
67
68
|
onMessage={instanceRef.current.webViewController?.onMessage}
|
|
68
69
|
onLoad={instanceRef.current.webViewController?.onLoad}
|
|
69
70
|
onError={instanceRef.current.webViewController?.onError}
|
|
71
|
+
onShouldStartLoadWithRequest={
|
|
72
|
+
instanceRef.current.webViewController
|
|
73
|
+
?.onShouldStartLoadWithRequest
|
|
74
|
+
}
|
|
70
75
|
/>
|
|
71
76
|
<NewTabWebView instance={instanceRef.current} />
|
|
72
77
|
</View>
|
|
@@ -83,8 +88,12 @@ export const DeunaWidget = (props: DeunaWidgetProps) => {
|
|
|
83
88
|
onMessage={instanceRef.current.webViewController?.onMessage}
|
|
84
89
|
onLoad={instanceRef.current.webViewController?.onLoad}
|
|
85
90
|
onError={instanceRef.current.webViewController?.onError}
|
|
91
|
+
onShouldStartLoadWithRequest={
|
|
92
|
+
instanceRef.current.webViewController?.onShouldStartLoadWithRequest
|
|
93
|
+
}
|
|
86
94
|
/>
|
|
87
95
|
)}
|
|
96
|
+
<DeviceFingerprintWebView instance={instanceRef.current} />
|
|
88
97
|
</>
|
|
89
98
|
);
|
|
90
99
|
};
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { useEffect, useRef, useState } from 'react';
|
|
2
|
+
import { DeunaSDK } from '../DeunaSDK';
|
|
3
|
+
import { DeunaWebView } from './DeunaWebView';
|
|
4
|
+
import { View } from 'react-native';
|
|
5
|
+
|
|
6
|
+
interface DeviceFingerprintWebViewProps {
|
|
7
|
+
instance: DeunaSDK;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export const DeviceFingerprintWebView = (
|
|
11
|
+
props: DeviceFingerprintWebViewProps
|
|
12
|
+
) => {
|
|
13
|
+
const { instance } = props;
|
|
14
|
+
const instanceRef = useRef<DeunaSDK>(instance);
|
|
15
|
+
const [visible, setVisible] = useState(false);
|
|
16
|
+
|
|
17
|
+
useEffect(() => {
|
|
18
|
+
instanceRef.current = instance;
|
|
19
|
+
}, [instance]);
|
|
20
|
+
|
|
21
|
+
// Listen when the DeunaSDK instance configuration
|
|
22
|
+
// has changed
|
|
23
|
+
useEffect(() => {
|
|
24
|
+
const ref = instanceRef.current;
|
|
25
|
+
const listener = () => {
|
|
26
|
+
const isVisible = !!ref.deviceFingerprintController;
|
|
27
|
+
setVisible(isVisible);
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
instanceRef.current.addListener(listener);
|
|
31
|
+
|
|
32
|
+
return () => {
|
|
33
|
+
ref.removeListener(listener);
|
|
34
|
+
};
|
|
35
|
+
}, []);
|
|
36
|
+
|
|
37
|
+
const deviceFingerprintController =
|
|
38
|
+
instanceRef.current.deviceFingerprintController;
|
|
39
|
+
|
|
40
|
+
return (
|
|
41
|
+
<>
|
|
42
|
+
{visible && (
|
|
43
|
+
<View>
|
|
44
|
+
<DeunaWebView
|
|
45
|
+
url={deviceFingerprintController?.url ?? ''}
|
|
46
|
+
onWebView={deviceFingerprintController?.setWebView}
|
|
47
|
+
onMessage={deviceFingerprintController?.onMessage}
|
|
48
|
+
onLoad={deviceFingerprintController?.onLoad}
|
|
49
|
+
onError={deviceFingerprintController?.onError}
|
|
50
|
+
onShouldStartLoadWithRequest={
|
|
51
|
+
deviceFingerprintController?.onShouldStartLoadWithRequest
|
|
52
|
+
}
|
|
53
|
+
/>
|
|
54
|
+
</View>
|
|
55
|
+
)}
|
|
56
|
+
</>
|
|
57
|
+
);
|
|
58
|
+
};
|
|
@@ -51,6 +51,9 @@ export const NewTabWebView = (props: NewTabWebViewProps) => {
|
|
|
51
51
|
onMessage={newTabController?.onMessage}
|
|
52
52
|
onLoad={newTabController?.onLoad}
|
|
53
53
|
onError={newTabController?.onError}
|
|
54
|
+
onShouldStartLoadWithRequest={
|
|
55
|
+
newTabController?.onShouldStartLoadWithRequest
|
|
56
|
+
}
|
|
54
57
|
/>
|
|
55
58
|
</SafeAreaView>
|
|
56
59
|
)}
|
|
@@ -9,11 +9,15 @@ import {
|
|
|
9
9
|
SubmitResult,
|
|
10
10
|
} from '../types';
|
|
11
11
|
import { submitError } from '../interfaces';
|
|
12
|
+
import { Platform } from 'react-native';
|
|
13
|
+
import { ShouldStartLoadRequest } from 'react-native-webview/lib/WebViewTypes';
|
|
12
14
|
|
|
13
15
|
export interface WebViewDelegate {
|
|
14
16
|
onOpenInNewTab?: (url: string) => void;
|
|
15
17
|
onCloseButtonPressed?: () => void;
|
|
16
18
|
onCloseSubWebView?: () => void;
|
|
19
|
+
onFileDownload?: (url: string) => void;
|
|
20
|
+
onNewTabWindowClose?: () => void;
|
|
17
21
|
}
|
|
18
22
|
|
|
19
23
|
export enum WebViewEventType {
|
|
@@ -22,15 +26,26 @@ export enum WebViewEventType {
|
|
|
22
26
|
jsExecutor = 'jsExecutor',
|
|
23
27
|
openInNewTab = 'openInNewTab',
|
|
24
28
|
redirect = 'redirect',
|
|
29
|
+
newTabWindowClose = 'newTabWindowClose',
|
|
25
30
|
}
|
|
26
31
|
|
|
32
|
+
const DOWNLOAD_FILE_REGEX =
|
|
33
|
+
/\.(pdf|doc|docx|xls|xlsx|zip|rar|txt|mp3|mp4|jpg|jpeg|png|gif)$/i;
|
|
34
|
+
|
|
27
35
|
export abstract class BaseWebViewController {
|
|
36
|
+
initialized = false;
|
|
37
|
+
redirectUrl: string | null = null;
|
|
28
38
|
webView: WebView | null = null;
|
|
29
39
|
url: string | null = null;
|
|
30
40
|
|
|
31
41
|
delegate: WebViewDelegate | null = null;
|
|
42
|
+
jsExecutor = new JsExecutor();
|
|
43
|
+
|
|
44
|
+
setWebView = (webView: WebView) => {
|
|
45
|
+
this.webView = webView;
|
|
46
|
+
this.jsExecutor.webView = webView;
|
|
47
|
+
};
|
|
32
48
|
|
|
33
|
-
abstract setWebView: (webView: WebView) => void;
|
|
34
49
|
abstract onMessage: (event: WebViewMessageEvent) => void;
|
|
35
50
|
|
|
36
51
|
/**
|
|
@@ -43,6 +58,52 @@ export abstract class BaseWebViewController {
|
|
|
43
58
|
*/
|
|
44
59
|
abstract onError: (event: any) => void;
|
|
45
60
|
|
|
61
|
+
/**
|
|
62
|
+
* Called when the web view should load a URL in the same web view
|
|
63
|
+
*/
|
|
64
|
+
abstract urlMustBeLoadedInTheSameWebView: (url: string) => boolean;
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Checks if the URL is a download URL
|
|
68
|
+
* @param url - The URL to check
|
|
69
|
+
* @returns True if the URL is a download URL, false otherwise
|
|
70
|
+
*/
|
|
71
|
+
isDownloadUrl = (url: string) => {
|
|
72
|
+
return DOWNLOAD_FILE_REGEX.test(url);
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Determines if a URL should be loaded in the current WebView or opened in a new tab
|
|
77
|
+
* @param request The navigation request details
|
|
78
|
+
* @returns boolean indicating if the URL should be loaded in current WebView
|
|
79
|
+
*/
|
|
80
|
+
onShouldStartLoadWithRequest = (request: ShouldStartLoadRequest) => {
|
|
81
|
+
// Prevent loading if it's the redirect URL
|
|
82
|
+
if (this.redirectUrl === request.url) {
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const isNewNavigation = request.isTopFrame && request.url !== this.url;
|
|
87
|
+
const isClickNavigation = request.navigationType === 'click';
|
|
88
|
+
const shouldHandleInNewTab = !this.urlMustBeLoadedInTheSameWebView(
|
|
89
|
+
request.url
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
// For iOS, check both click navigation and new navigation
|
|
93
|
+
// For Android, only check new navigation
|
|
94
|
+
const shouldOpenInNewTab =
|
|
95
|
+
Platform.OS === 'ios'
|
|
96
|
+
? (isClickNavigation || isNewNavigation) && shouldHandleInNewTab
|
|
97
|
+
: isNewNavigation && shouldHandleInNewTab;
|
|
98
|
+
|
|
99
|
+
if (shouldOpenInNewTab) {
|
|
100
|
+
this.delegate?.onOpenInNewTab?.(request.url);
|
|
101
|
+
return false;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return true;
|
|
105
|
+
};
|
|
106
|
+
|
|
46
107
|
/**
|
|
47
108
|
* Release the web view resources
|
|
48
109
|
*/
|
|
@@ -56,19 +117,22 @@ export abstract class DeunaWebViewController extends BaseWebViewController {
|
|
|
56
117
|
hidePayButton = false;
|
|
57
118
|
redirectUrl: string | null = null;
|
|
58
119
|
closedAction: ClosedAction = 'systemAction';
|
|
59
|
-
jsExecutor = new JsExecutor();
|
|
60
120
|
|
|
61
121
|
abstract onEventDispatch: (event: Record<string, any>) => void;
|
|
62
122
|
|
|
63
|
-
setWebView = (webView: WebView) => {
|
|
64
|
-
this.webView = webView;
|
|
65
|
-
this.jsExecutor.webView = webView;
|
|
66
|
-
};
|
|
67
|
-
|
|
68
123
|
onLoad = () => {
|
|
69
124
|
this.setXprops();
|
|
70
125
|
};
|
|
71
126
|
|
|
127
|
+
urlMustBeLoadedInTheSameWebView = (url: string) => {
|
|
128
|
+
if (this.isDownloadUrl(url)) {
|
|
129
|
+
this.delegate?.onFileDownload?.(url);
|
|
130
|
+
} else {
|
|
131
|
+
this.redirectUrl = url;
|
|
132
|
+
}
|
|
133
|
+
return false;
|
|
134
|
+
};
|
|
135
|
+
|
|
72
136
|
onMessage = (event: WebViewMessageEvent) => {
|
|
73
137
|
const eventData = JSON.parse(event.nativeEvent.data);
|
|
74
138
|
|
|
@@ -100,6 +164,9 @@ export abstract class DeunaWebViewController extends BaseWebViewController {
|
|
|
100
164
|
this.redirectUrl = eventData.url;
|
|
101
165
|
this.delegate?.onOpenInNewTab?.(eventData.url);
|
|
102
166
|
},
|
|
167
|
+
[WebViewEventType.newTabWindowClose]: () => {
|
|
168
|
+
this.delegate?.onNewTabWindowClose?.();
|
|
169
|
+
},
|
|
103
170
|
};
|
|
104
171
|
|
|
105
172
|
mapper[eventData.type as WebViewEventType]?.();
|
|
@@ -108,19 +175,9 @@ export abstract class DeunaWebViewController extends BaseWebViewController {
|
|
|
108
175
|
setXprops = () => {
|
|
109
176
|
this.webView?.injectJavaScript(
|
|
110
177
|
`
|
|
111
|
-
document.addEventListener('click', function(event) {
|
|
112
|
-
if (event.target.tagName === 'A' && event.target.href) {
|
|
113
|
-
window.ReactNativeWebView.postMessage(JSON.stringify({ type: '${WebViewEventType.redirect}', url: event.target.href }));
|
|
114
|
-
event.preventDefault(); // Prevent default navigation
|
|
115
|
-
event.stopPropagation();
|
|
116
|
-
}
|
|
117
|
-
});
|
|
118
178
|
console.log = function(message) {
|
|
119
179
|
window.ReactNativeWebView.postMessage(JSON.stringify({ type: '${WebViewEventType.consoleLog}', message }));
|
|
120
180
|
};
|
|
121
|
-
window.open = function(url, target, features) {
|
|
122
|
-
window.ReactNativeWebView.postMessage(JSON.stringify({ type: '${WebViewEventType.openInNewTab}', url }));
|
|
123
|
-
};
|
|
124
181
|
window.xprops = {
|
|
125
182
|
hidePayButton: ${this.hidePayButton},
|
|
126
183
|
onEventDispatch: function (event) {
|
|
@@ -166,8 +223,8 @@ export abstract class DeunaWebViewController extends BaseWebViewController {
|
|
|
166
223
|
* @returns The order data | returns null if the order is not found or an error occurs
|
|
167
224
|
*/
|
|
168
225
|
refetchOrder = async (): Promise<Json | null> => {
|
|
169
|
-
const result = await this.jsExecutor
|
|
170
|
-
if(typeof window.deunaRefetchOrder !== 'function'){
|
|
226
|
+
const result = await this.jsExecutor
|
|
227
|
+
.execute(` if(typeof window.deunaRefetchOrder !== 'function'){
|
|
171
228
|
sendResult({order: null});
|
|
172
229
|
return;
|
|
173
230
|
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { WebViewMessageEvent } from 'react-native-webview';
|
|
2
|
+
import {
|
|
3
|
+
BaseWebViewController,
|
|
4
|
+
WebViewEventType,
|
|
5
|
+
} from './BaseWebViewController';
|
|
6
|
+
import { Completer } from '../helpers/Completer';
|
|
7
|
+
import { Environment } from '../types';
|
|
8
|
+
import { DeunaLogs } from '../DeunaLogs';
|
|
9
|
+
|
|
10
|
+
export class DeviceFingerprintController extends BaseWebViewController {
|
|
11
|
+
constructor(
|
|
12
|
+
private readonly publicApiKey: string,
|
|
13
|
+
private readonly environment: Environment
|
|
14
|
+
) {
|
|
15
|
+
super();
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
private completer = new Completer<boolean>();
|
|
19
|
+
|
|
20
|
+
onMessage = (event: WebViewMessageEvent) => {
|
|
21
|
+
const eventData = JSON.parse(event.nativeEvent.data);
|
|
22
|
+
|
|
23
|
+
const mapper = {
|
|
24
|
+
[WebViewEventType.consoleLog]: () => {
|
|
25
|
+
DeunaLogs.info(
|
|
26
|
+
`CONSOLE LOG`,
|
|
27
|
+
JSON.stringify(eventData.message, null, 2)
|
|
28
|
+
);
|
|
29
|
+
},
|
|
30
|
+
[WebViewEventType.jsExecutor]: () => {
|
|
31
|
+
const { data, requestId } = eventData as {
|
|
32
|
+
requestId: number;
|
|
33
|
+
data: Record<string, any> | null;
|
|
34
|
+
};
|
|
35
|
+
this.jsExecutor.requests.get(requestId)?.(data);
|
|
36
|
+
this.jsExecutor.requests.delete(requestId);
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
mapper[eventData.type as keyof typeof mapper]?.();
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
onLoad = () => {
|
|
43
|
+
this.webView?.injectJavaScript(`
|
|
44
|
+
console.log = function(message) {
|
|
45
|
+
window.ReactNativeWebView.postMessage(JSON.stringify({ type: '${WebViewEventType.consoleLog}', message:message }));
|
|
46
|
+
};
|
|
47
|
+
`);
|
|
48
|
+
this.completer.complete(true);
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
onError = (_: any) => {
|
|
52
|
+
this.completer.complete(false);
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
urlMustBeLoadedInTheSameWebView = (_: string) => true;
|
|
56
|
+
|
|
57
|
+
generateDeviceFingerprint = async (
|
|
58
|
+
params: Record<string, any>
|
|
59
|
+
): Promise<string | null> => {
|
|
60
|
+
const isLoaded = await this.completer.wait;
|
|
61
|
+
if (!isLoaded) {
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const data = await this.jsExecutor.execute(`
|
|
66
|
+
if (typeof window.generateFraudId !== 'function') {
|
|
67
|
+
console.log('❌ window.generateFraudId is not a function');
|
|
68
|
+
sendResult({fraudId: null});
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
window.generateFraudId({
|
|
73
|
+
publicApiKey: "${this.publicApiKey}",
|
|
74
|
+
env: "${this.environment}",
|
|
75
|
+
params: ${JSON.stringify(params)}
|
|
76
|
+
})
|
|
77
|
+
.then((fraudId) => {
|
|
78
|
+
sendResult({ fraudId: fraudId });
|
|
79
|
+
})
|
|
80
|
+
.catch((error) => {
|
|
81
|
+
sendResult({ fraudId: null });
|
|
82
|
+
});
|
|
83
|
+
`);
|
|
84
|
+
return data?.fraudId ?? null;
|
|
85
|
+
};
|
|
86
|
+
}
|
|
@@ -1,5 +1,8 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
1
|
+
import { WebViewMessageEvent } from 'react-native-webview';
|
|
2
|
+
import {
|
|
3
|
+
BaseWebViewController,
|
|
4
|
+
WebViewEventType,
|
|
5
|
+
} from './BaseWebViewController';
|
|
3
6
|
import { DeunaLogs } from '../DeunaLogs';
|
|
4
7
|
|
|
5
8
|
export class OpenInNewTabController extends BaseWebViewController {
|
|
@@ -8,10 +11,6 @@ export class OpenInNewTabController extends BaseWebViewController {
|
|
|
8
11
|
this.url = url;
|
|
9
12
|
}
|
|
10
13
|
|
|
11
|
-
setWebView = (webView: WebView) => {
|
|
12
|
-
this.webView = webView;
|
|
13
|
-
};
|
|
14
|
-
|
|
15
14
|
onLoad = () => {
|
|
16
15
|
this.setXprops();
|
|
17
16
|
};
|
|
@@ -20,6 +19,14 @@ export class OpenInNewTabController extends BaseWebViewController {
|
|
|
20
19
|
console.warn(event);
|
|
21
20
|
};
|
|
22
21
|
|
|
22
|
+
urlMustBeLoadedInTheSameWebView = (url: string) => {
|
|
23
|
+
if (this.isDownloadUrl(url)) {
|
|
24
|
+
this.delegate?.onFileDownload?.(url);
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
return true;
|
|
28
|
+
};
|
|
29
|
+
|
|
23
30
|
onMessage = (event: WebViewMessageEvent) => {
|
|
24
31
|
const eventData = JSON.parse(event.nativeEvent.data);
|
|
25
32
|
if (eventData.type === 'console_log') {
|
|
@@ -28,12 +35,22 @@ export class OpenInNewTabController extends BaseWebViewController {
|
|
|
28
35
|
};
|
|
29
36
|
|
|
30
37
|
setXprops = () => {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
+
// Hide the print button on the Efecty page
|
|
39
|
+
this.webView?.injectJavaScript(`
|
|
40
|
+
console.log = function(message) {
|
|
41
|
+
window.ReactNativeWebView.postMessage(JSON.stringify({ type: '${WebViewEventType.consoleLog}', message }));
|
|
42
|
+
};
|
|
43
|
+
window.close = function() {
|
|
44
|
+
window.ReactNativeWebView.postMessage(JSON.stringify({ type: '${WebViewEventType.newTabWindowClose}', data: '' }));
|
|
45
|
+
};
|
|
46
|
+
(function() {
|
|
47
|
+
setTimeout(function() {
|
|
48
|
+
var button = document.getElementById("cash_efecty_button_print");
|
|
49
|
+
if (button) {
|
|
50
|
+
button.style.display = "none";
|
|
51
|
+
}
|
|
52
|
+
}, 500); // time out 500 ms
|
|
53
|
+
})();
|
|
54
|
+
`);
|
|
38
55
|
};
|
|
39
56
|
}
|
|
@@ -2,86 +2,119 @@ import { Environment } from "../base";
|
|
|
2
2
|
import { hasKey } from "../utils/hasKey";
|
|
3
3
|
import { getIntegrationType, UrlConfig } from "./urlConfig";
|
|
4
4
|
|
|
5
|
-
const
|
|
5
|
+
const ELEMENTS_URLS: Record<Environment, string> = {
|
|
6
6
|
production: "https://elements.deuna.io",
|
|
7
7
|
sandbox: "https://elements.sandbox.deuna.io",
|
|
8
8
|
staging: "https://elements.stg.deuna.io",
|
|
9
9
|
develop: "https://elements.dev.deuna.io",
|
|
10
|
-
};
|
|
10
|
+
} as const;
|
|
11
11
|
|
|
12
|
-
const
|
|
12
|
+
const WIDGET_PATHS = {
|
|
13
13
|
click_to_pay: "/click_to_pay",
|
|
14
14
|
vault: "/vault",
|
|
15
|
+
} as const;
|
|
16
|
+
|
|
17
|
+
type WidgetPath = keyof typeof WIDGET_PATHS;
|
|
18
|
+
|
|
19
|
+
interface SearchParams {
|
|
20
|
+
publicApiKey: string;
|
|
21
|
+
orderToken: string;
|
|
22
|
+
email: string;
|
|
23
|
+
firstName: string;
|
|
24
|
+
lastName: string;
|
|
25
|
+
int: string;
|
|
26
|
+
language: string;
|
|
27
|
+
cssFile?: string;
|
|
28
|
+
userToken?: string;
|
|
29
|
+
showSavedCardsFlow?: string;
|
|
30
|
+
showDefaultCardFlow?: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Builds the base URL for the Elements widget based on environment and domain configuration
|
|
35
|
+
*/
|
|
36
|
+
const buildBaseUrl = (config: UrlConfig): string => {
|
|
37
|
+
if (config.domain) {
|
|
38
|
+
return config.domain;
|
|
39
|
+
}
|
|
40
|
+
return config.env ? ELEMENTS_URLS[config.env] : ELEMENTS_URLS.production;
|
|
15
41
|
};
|
|
16
42
|
|
|
17
|
-
|
|
18
|
-
|
|
43
|
+
/**
|
|
44
|
+
* Builds the search parameters for the Elements widget URL
|
|
45
|
+
*/
|
|
46
|
+
const buildSearchParams = (config: UrlConfig): URLSearchParams => {
|
|
47
|
+
const { userInfo, userToken, widgetExperience, styleFile, mode, language } =
|
|
19
48
|
config;
|
|
20
49
|
|
|
21
|
-
|
|
50
|
+
const searchParams: SearchParams = {
|
|
51
|
+
publicApiKey: config.publicApiKey,
|
|
52
|
+
orderToken: config.orderToken,
|
|
53
|
+
email: userInfo?.email || "",
|
|
54
|
+
firstName: userInfo?.firstName || "",
|
|
55
|
+
lastName: userInfo?.lastName || "",
|
|
56
|
+
int: getIntegrationType(mode),
|
|
57
|
+
language,
|
|
58
|
+
};
|
|
22
59
|
|
|
23
|
-
if (
|
|
24
|
-
|
|
60
|
+
if (styleFile) {
|
|
61
|
+
searchParams.cssFile = styleFile;
|
|
25
62
|
}
|
|
26
63
|
|
|
27
|
-
if (
|
|
28
|
-
|
|
64
|
+
if (userToken) {
|
|
65
|
+
searchParams.userToken = userToken;
|
|
29
66
|
}
|
|
30
67
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
const email = userInfo?.email || "";
|
|
37
|
-
|
|
38
|
-
const searchParams = new URLSearchParams({
|
|
39
|
-
publicApiKey,
|
|
40
|
-
orderToken,
|
|
41
|
-
email,
|
|
42
|
-
firstName,
|
|
43
|
-
lastName,
|
|
44
|
-
...(config.styleFile ? { cssFile: config.styleFile } : {}),
|
|
45
|
-
int: getIntegrationType(config.mode),
|
|
46
|
-
...(userToken ? { userToken } : {}),
|
|
47
|
-
...(widgetExperience?.userExperience?.showSavedCardsFlow
|
|
48
|
-
? {
|
|
49
|
-
showSavedCardsFlow: String(
|
|
50
|
-
widgetExperience?.userExperience?.showSavedCardsFlow
|
|
51
|
-
),
|
|
52
|
-
}
|
|
53
|
-
: {}),
|
|
54
|
-
...(widgetExperience?.userExperience?.defaultCardFlow
|
|
55
|
-
? {
|
|
56
|
-
showDefaultCardFlow: String(
|
|
57
|
-
widgetExperience?.userExperience?.defaultCardFlow
|
|
58
|
-
),
|
|
59
|
-
}
|
|
60
|
-
: {}),
|
|
61
|
-
language: config.language,
|
|
62
|
-
});
|
|
68
|
+
if (widgetExperience?.userExperience?.showSavedCardsFlow) {
|
|
69
|
+
searchParams.showSavedCardsFlow = String(
|
|
70
|
+
widgetExperience.userExperience.showSavedCardsFlow
|
|
71
|
+
);
|
|
72
|
+
}
|
|
63
73
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
74
|
+
if (widgetExperience?.userExperience?.defaultCardFlow) {
|
|
75
|
+
searchParams.showDefaultCardFlow = String(
|
|
76
|
+
widgetExperience.userExperience.defaultCardFlow
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return new URLSearchParams(searchParams as unknown as Record<string, string>);
|
|
81
|
+
};
|
|
68
82
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
83
|
+
/**
|
|
84
|
+
* Determines the widget path based on the provided types
|
|
85
|
+
*/
|
|
86
|
+
const getWidgetPath = (types?: Array<{ name: string }>): string => {
|
|
87
|
+
if (types && types.length > 0) {
|
|
88
|
+
const firstPath = types[0]?.name as WidgetPath;
|
|
89
|
+
return hasKey(WIDGET_PATHS, firstPath)
|
|
90
|
+
? WIDGET_PATHS[firstPath]
|
|
91
|
+
: WIDGET_PATHS.vault;
|
|
92
|
+
}
|
|
93
|
+
return WIDGET_PATHS.vault;
|
|
94
|
+
};
|
|
72
95
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
96
|
+
/**
|
|
97
|
+
* Builds a complete Elements widget URL with all necessary parameters
|
|
98
|
+
* @param config - Configuration object for building the Elements URL
|
|
99
|
+
* @returns The complete Elements widget URL as a string
|
|
100
|
+
*/
|
|
101
|
+
export const buildElementsLink = (config: UrlConfig): string => {
|
|
102
|
+
const baseUrl = buildBaseUrl(config);
|
|
103
|
+
const searchParams = buildSearchParams(config);
|
|
77
104
|
|
|
78
|
-
|
|
105
|
+
const xpropsB64 = {
|
|
106
|
+
id: config.id,
|
|
107
|
+
behavior: config.behavior || {},
|
|
79
108
|
};
|
|
80
109
|
|
|
81
110
|
searchParams.append("xpropsB64", btoa(JSON.stringify(xpropsB64)));
|
|
82
111
|
|
|
83
|
-
|
|
84
|
-
|
|
112
|
+
const widgetPath = getWidgetPath(config.types);
|
|
113
|
+
const normalizedPath = widgetPath.startsWith("/")
|
|
114
|
+
? widgetPath
|
|
115
|
+
: `/${widgetPath}`;
|
|
116
|
+
|
|
117
|
+
const url = new URL(`${baseUrl}${normalizedPath}?${searchParams.toString()}`);
|
|
85
118
|
|
|
86
119
|
return url.toString();
|
|
87
120
|
};
|
|
@@ -21,22 +21,26 @@ export const buildNextActionLink = (config: UrlConfig): string => {
|
|
|
21
21
|
baseUrl = urls[env];
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
+
const xpropsB64 = {
|
|
25
|
+
id: config.id
|
|
26
|
+
};
|
|
27
|
+
|
|
24
28
|
const searchParams = new URLSearchParams({
|
|
25
29
|
mode: "widget",
|
|
26
30
|
int: getIntegrationType(config.mode),
|
|
27
31
|
language: config.language,
|
|
28
32
|
});
|
|
29
33
|
|
|
34
|
+
//encode to base64
|
|
35
|
+
searchParams.append("xpropsB64", btoa(JSON.stringify(xpropsB64)));
|
|
36
|
+
|
|
30
37
|
configToQueryParams(config, searchParams);
|
|
31
38
|
|
|
32
39
|
if (config.domain) {
|
|
33
40
|
baseUrl = config.domain;
|
|
34
41
|
}
|
|
35
42
|
|
|
36
|
-
const url = new URL(baseUrl);
|
|
37
|
-
|
|
38
|
-
url.pathname = `/next-action-purchase/${orderToken}`;
|
|
39
|
-
url.search = searchParams.toString();
|
|
43
|
+
const url = new URL(`${baseUrl}/next-action-purchase/${orderToken}?${searchParams.toString()}`);
|
|
40
44
|
|
|
41
45
|
return url.toString();
|
|
42
46
|
};
|
|
@@ -54,10 +54,7 @@ export const buildPaymentLink = (config: UrlConfig): string => {
|
|
|
54
54
|
//encode to base64
|
|
55
55
|
searchParams.append("xpropsB64", btoa(JSON.stringify(xpropsB64)));
|
|
56
56
|
|
|
57
|
-
const url = new URL(baseUrl);
|
|
58
|
-
|
|
59
|
-
url.pathname = `/now/${orderToken}`;
|
|
60
|
-
url.search = searchParams.toString();
|
|
57
|
+
const url = new URL(`${baseUrl}/now/${orderToken}?${searchParams.toString()}`);
|
|
61
58
|
|
|
62
59
|
return url.toString();
|
|
63
60
|
};
|
|
@@ -44,10 +44,7 @@ export const buildVoucherLink = (config: UrlConfig): string => {
|
|
|
44
44
|
//encode to base64
|
|
45
45
|
searchParams.append("xpropsB64", btoa(JSON.stringify(xpropsB64)));
|
|
46
46
|
|
|
47
|
-
const url = new URL(baseUrl);
|
|
48
|
-
|
|
49
|
-
url.pathname = `/voucher`;
|
|
50
|
-
url.search = searchParams.toString();
|
|
47
|
+
const url = new URL(`${baseUrl}/voucher?${searchParams.toString()}`);
|
|
51
48
|
|
|
52
49
|
return url.toString();
|
|
53
50
|
};
|