@duffel/react-native-components-assistant 0.5.2-canary.0 → 0.6.0-canary.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/lib/module/DuffelAssistantForAndroid.js +102 -0
- package/lib/module/DuffelAssistantForAndroid.js.map +1 -0
- package/lib/module/DuffelAssistantForiOS.js +85 -0
- package/lib/module/DuffelAssistantForiOS.js.map +1 -0
- package/lib/module/index.js +21 -150
- package/lib/module/index.js.map +1 -1
- package/lib/module/lib/ShouldStartLoadRequest.js +2 -0
- package/lib/module/lib/ShouldStartLoadRequest.js.map +1 -0
- package/lib/module/lib/WebViewMessageEvent.js +2 -0
- package/lib/module/lib/WebViewMessageEvent.js.map +1 -0
- package/lib/module/lib/assistantIframeUrl.js +4 -0
- package/lib/module/lib/assistantIframeUrl.js.map +1 -0
- package/lib/module/lib/isAssistantIframeUrl.js +12 -0
- package/lib/module/lib/isAssistantIframeUrl.js.map +1 -0
- package/lib/module/lib/isHttpUrl.js +4 -0
- package/lib/module/lib/isHttpUrl.js.map +1 -0
- package/lib/typescript/src/DuffelAssistantForAndroid.d.ts +4 -0
- package/lib/typescript/src/DuffelAssistantForAndroid.d.ts.map +1 -0
- package/lib/typescript/src/DuffelAssistantForiOS.d.ts +4 -0
- package/lib/typescript/src/DuffelAssistantForiOS.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/lib/ShouldStartLoadRequest.d.ts +5 -0
- package/lib/typescript/src/lib/ShouldStartLoadRequest.d.ts.map +1 -0
- package/lib/typescript/src/lib/WebViewMessageEvent.d.ts +6 -0
- package/lib/typescript/src/lib/WebViewMessageEvent.d.ts.map +1 -0
- package/lib/typescript/src/lib/assistantIframeUrl.d.ts +2 -0
- package/lib/typescript/src/lib/assistantIframeUrl.d.ts.map +1 -0
- package/lib/typescript/src/lib/isAssistantIframeUrl.d.ts +2 -0
- package/lib/typescript/src/lib/isAssistantIframeUrl.d.ts.map +1 -0
- package/lib/typescript/src/lib/isHttpUrl.d.ts +2 -0
- package/lib/typescript/src/lib/isHttpUrl.d.ts.map +1 -0
- package/package.json +1 -1
- package/src/DuffelAssistantForAndroid.tsx +131 -0
- package/src/DuffelAssistantForiOS.tsx +101 -0
- package/src/index.tsx +20 -205
- package/src/lib/ShouldStartLoadRequest.ts +4 -0
- package/src/lib/WebViewMessageEvent.ts +5 -0
- package/src/lib/assistantIframeUrl.ts +2 -0
- package/src/lib/isAssistantIframeUrl.ts +11 -0
- package/src/lib/isHttpUrl.ts +1 -0
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
import React, { useEffect, useState } from 'react';
|
|
4
|
+
import { Keyboard, Linking, Modal, StyleSheet, View } from 'react-native';
|
|
5
|
+
import { WebView } from 'react-native-webview';
|
|
6
|
+
import { assistantIframeUrl } from "./lib/assistantIframeUrl.js";
|
|
7
|
+
import { isAssistantIframeUrl } from "./lib/isAssistantIframeUrl.js";
|
|
8
|
+
import { isHttpUrl } from "./lib/isHttpUrl.js";
|
|
9
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
10
|
+
export const DuffelAssistantForAndroid = ({
|
|
11
|
+
isOpen,
|
|
12
|
+
onClose,
|
|
13
|
+
onNewMessage,
|
|
14
|
+
...properties
|
|
15
|
+
}) => {
|
|
16
|
+
const [androidKeyboardHeight, setAndroidKeyboardHeight] = useState(0);
|
|
17
|
+
useEffect(() => {
|
|
18
|
+
if (!isOpen) {
|
|
19
|
+
setAndroidKeyboardHeight(0);
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
const keyboardDidShowSubscription = Keyboard.addListener('keyboardDidShow', event => {
|
|
23
|
+
setAndroidKeyboardHeight(event.endCoordinates?.height ?? 0);
|
|
24
|
+
});
|
|
25
|
+
const keyboardDidHideSubscription = Keyboard.addListener('keyboardDidHide', () => {
|
|
26
|
+
setAndroidKeyboardHeight(0);
|
|
27
|
+
});
|
|
28
|
+
return () => {
|
|
29
|
+
keyboardDidShowSubscription.remove();
|
|
30
|
+
keyboardDidHideSubscription.remove();
|
|
31
|
+
};
|
|
32
|
+
}, [isOpen]);
|
|
33
|
+
const handleMessage = event => {
|
|
34
|
+
if (event.nativeEvent.data === 'duffel-assistant-close') {
|
|
35
|
+
onClose();
|
|
36
|
+
}
|
|
37
|
+
if (typeof properties.onMinimise === 'function' && event.nativeEvent.data === 'duffel-assistant-minimise') {
|
|
38
|
+
properties.onMinimise();
|
|
39
|
+
}
|
|
40
|
+
try {
|
|
41
|
+
const parsedData = JSON.parse(event.nativeEvent.data);
|
|
42
|
+
if (parsedData.type === 'duffel-assistant-new-message' && typeof parsedData.userId === 'string' && typeof parsedData.resourceId === 'string' && typeof onNewMessage === 'function') {
|
|
43
|
+
onNewMessage({
|
|
44
|
+
userId: parsedData.userId,
|
|
45
|
+
resourceId: parsedData.resourceId
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
} catch (error) {
|
|
49
|
+
// do nothing, invalid JSON
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
const handleShouldStartLoadWithRequest = request => {
|
|
53
|
+
if (!isHttpUrl(request.url) || isAssistantIframeUrl(request.url)) {
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
Linking.openURL(request.url).catch(() => {});
|
|
57
|
+
return false;
|
|
58
|
+
};
|
|
59
|
+
return /*#__PURE__*/_jsx(Modal, {
|
|
60
|
+
visible: isOpen,
|
|
61
|
+
onRequestClose: () => onClose(),
|
|
62
|
+
animationType: "slide",
|
|
63
|
+
children: /*#__PURE__*/_jsx(View, {
|
|
64
|
+
style: [styles.androidContainer, {
|
|
65
|
+
paddingBottom: 15 + androidKeyboardHeight
|
|
66
|
+
}],
|
|
67
|
+
children: /*#__PURE__*/_jsx(WebView
|
|
68
|
+
// Required for Android
|
|
69
|
+
, {
|
|
70
|
+
injectedJavaScript: `
|
|
71
|
+
postMessage(
|
|
72
|
+
{
|
|
73
|
+
type: "duffel-assistant-open",
|
|
74
|
+
properties: ${JSON.stringify(properties)},
|
|
75
|
+
},
|
|
76
|
+
"*",
|
|
77
|
+
);
|
|
78
|
+
true;`,
|
|
79
|
+
source: {
|
|
80
|
+
uri: assistantIframeUrl
|
|
81
|
+
},
|
|
82
|
+
onMessage: handleMessage,
|
|
83
|
+
onShouldStartLoadWithRequest: handleShouldStartLoadWithRequest
|
|
84
|
+
// Always fetch the latest assistant bundle. The iframe.html shell
|
|
85
|
+
// references `./iframe-app.js` by a stable path, so without this
|
|
86
|
+
// the system WebView can serve a stale bundle for a long time.
|
|
87
|
+
,
|
|
88
|
+
cacheEnabled: false,
|
|
89
|
+
cacheMode: "LOAD_NO_CACHE"
|
|
90
|
+
})
|
|
91
|
+
})
|
|
92
|
+
});
|
|
93
|
+
};
|
|
94
|
+
const styles = StyleSheet.create({
|
|
95
|
+
androidContainer: {
|
|
96
|
+
flex: 1,
|
|
97
|
+
width: '100%',
|
|
98
|
+
paddingTop: 45,
|
|
99
|
+
paddingBottom: 15
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
//# sourceMappingURL=DuffelAssistantForAndroid.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["React","useEffect","useState","Keyboard","Linking","Modal","StyleSheet","View","WebView","assistantIframeUrl","isAssistantIframeUrl","isHttpUrl","jsx","_jsx","DuffelAssistantForAndroid","isOpen","onClose","onNewMessage","properties","androidKeyboardHeight","setAndroidKeyboardHeight","keyboardDidShowSubscription","addListener","event","endCoordinates","height","keyboardDidHideSubscription","remove","handleMessage","nativeEvent","data","onMinimise","parsedData","JSON","parse","type","userId","resourceId","error","handleShouldStartLoadWithRequest","request","url","openURL","catch","visible","onRequestClose","animationType","children","style","styles","androidContainer","paddingBottom","injectedJavaScript","stringify","source","uri","onMessage","onShouldStartLoadWithRequest","cacheEnabled","cacheMode","create","flex","width","paddingTop"],"sourceRoot":"../../src","sources":["DuffelAssistantForAndroid.tsx"],"mappings":"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","ignoreList":[]}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
import React from 'react';
|
|
4
|
+
import { KeyboardAvoidingView, Linking, Modal, StyleSheet } from 'react-native';
|
|
5
|
+
import { WebView } from 'react-native-webview';
|
|
6
|
+
import { assistantIframeUrl } from "./lib/assistantIframeUrl.js";
|
|
7
|
+
import { isAssistantIframeUrl } from "./lib/isAssistantIframeUrl.js";
|
|
8
|
+
import { isHttpUrl } from "./lib/isHttpUrl.js";
|
|
9
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
10
|
+
export const DuffelAssistantForiOS = ({
|
|
11
|
+
isOpen,
|
|
12
|
+
onClose,
|
|
13
|
+
onNewMessage,
|
|
14
|
+
...properties
|
|
15
|
+
}) => {
|
|
16
|
+
const handleMessage = event => {
|
|
17
|
+
if (event.nativeEvent.data === 'duffel-assistant-close') {
|
|
18
|
+
onClose();
|
|
19
|
+
}
|
|
20
|
+
if (typeof properties.onMinimise === 'function' && event.nativeEvent.data === 'duffel-assistant-minimise') {
|
|
21
|
+
properties.onMinimise();
|
|
22
|
+
}
|
|
23
|
+
try {
|
|
24
|
+
const parsedData = JSON.parse(event.nativeEvent.data);
|
|
25
|
+
if (parsedData.type === 'duffel-assistant-new-message' && typeof parsedData.userId === 'string' && typeof parsedData.resourceId === 'string' && typeof onNewMessage === 'function') {
|
|
26
|
+
onNewMessage({
|
|
27
|
+
userId: parsedData.userId,
|
|
28
|
+
resourceId: parsedData.resourceId
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
} catch (error) {
|
|
32
|
+
// do nothing, invalid JSON
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
const handleShouldStartLoadWithRequest = request => {
|
|
36
|
+
if (!isHttpUrl(request.url) || isAssistantIframeUrl(request.url)) {
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
39
|
+
if (request.navigationType !== 'click') {
|
|
40
|
+
return true;
|
|
41
|
+
}
|
|
42
|
+
Linking.openURL(request.url).catch(() => {});
|
|
43
|
+
return false;
|
|
44
|
+
};
|
|
45
|
+
return /*#__PURE__*/_jsx(Modal, {
|
|
46
|
+
visible: isOpen,
|
|
47
|
+
onRequestClose: () => onClose(),
|
|
48
|
+
animationType: "slide",
|
|
49
|
+
children: /*#__PURE__*/_jsx(KeyboardAvoidingView, {
|
|
50
|
+
behavior: "padding",
|
|
51
|
+
style: styles.container,
|
|
52
|
+
children: /*#__PURE__*/_jsx(WebView, {
|
|
53
|
+
style: styles.webView,
|
|
54
|
+
injectedJavaScriptObject: properties
|
|
55
|
+
// When you are running this in development iOS doesn't allow request from hosts with invalid https certificates.
|
|
56
|
+
// If you try ngrok with the local esbuild server it will fail because ngrok requests in https and the dev server rejects http requests.
|
|
57
|
+
// Easiest solution is to use the production URL with test data.
|
|
58
|
+
// If that's not possible, you can also upload the assistant built assets to a different folder in assets.duffel and work with that.
|
|
59
|
+
,
|
|
60
|
+
source: {
|
|
61
|
+
uri: assistantIframeUrl
|
|
62
|
+
},
|
|
63
|
+
onMessage: handleMessage,
|
|
64
|
+
onShouldStartLoadWithRequest: handleShouldStartLoadWithRequest
|
|
65
|
+
// Always fetch the latest assistant bundle. The iframe.html shell
|
|
66
|
+
// references `./iframe-app.js` by a stable path, so without this
|
|
67
|
+
// the system WebView can serve a stale bundle for a long time.
|
|
68
|
+
,
|
|
69
|
+
cacheEnabled: false
|
|
70
|
+
})
|
|
71
|
+
})
|
|
72
|
+
});
|
|
73
|
+
};
|
|
74
|
+
const styles = StyleSheet.create({
|
|
75
|
+
container: {
|
|
76
|
+
height: '100%',
|
|
77
|
+
width: '100%',
|
|
78
|
+
paddingHorizontal: 12,
|
|
79
|
+
paddingTop: 50
|
|
80
|
+
},
|
|
81
|
+
webView: {
|
|
82
|
+
paddingBottom: 7
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
//# sourceMappingURL=DuffelAssistantForiOS.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["React","KeyboardAvoidingView","Linking","Modal","StyleSheet","WebView","assistantIframeUrl","isAssistantIframeUrl","isHttpUrl","jsx","_jsx","DuffelAssistantForiOS","isOpen","onClose","onNewMessage","properties","handleMessage","event","nativeEvent","data","onMinimise","parsedData","JSON","parse","type","userId","resourceId","error","handleShouldStartLoadWithRequest","request","url","navigationType","openURL","catch","visible","onRequestClose","animationType","children","behavior","style","styles","container","webView","injectedJavaScriptObject","source","uri","onMessage","onShouldStartLoadWithRequest","cacheEnabled","create","height","width","paddingHorizontal","paddingTop","paddingBottom"],"sourceRoot":"../../src","sources":["DuffelAssistantForiOS.tsx"],"mappings":"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","ignoreList":[]}
|
package/lib/module/index.js
CHANGED
|
@@ -1,51 +1,17 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
-
import React
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
3
|
+
import React from 'react';
|
|
4
|
+
import { Platform } from 'react-native';
|
|
5
|
+
import { DuffelAssistantForAndroid } from "./DuffelAssistantForAndroid.js";
|
|
6
|
+
import { DuffelAssistantForiOS } from "./DuffelAssistantForiOS.js";
|
|
6
7
|
import { hasJsonWebTokenFormat } from "./lib/hasJsonWebTokenFormat.js";
|
|
7
8
|
import { getClientKeyPayload } from "./lib/getClientKeyPayload.js";
|
|
8
9
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
const isAssistantIframeUrl = url => {
|
|
12
|
-
try {
|
|
13
|
-
const parsedUrl = new URL(url);
|
|
14
|
-
return `${parsedUrl.origin}${parsedUrl.pathname}` === assistantIframeUrl;
|
|
15
|
-
} catch (error) {
|
|
16
|
-
return false;
|
|
17
|
-
}
|
|
18
|
-
};
|
|
19
|
-
export const DuffelAssistant = ({
|
|
20
|
-
isOpen,
|
|
21
|
-
onClose,
|
|
22
|
-
onNewMessage,
|
|
23
|
-
...properties
|
|
24
|
-
}) => {
|
|
25
|
-
const [androidKeyboardHeight, setAndroidKeyboardHeight] = useState(0);
|
|
26
|
-
useEffect(() => {
|
|
27
|
-
if (Platform.OS !== 'android') {
|
|
28
|
-
return;
|
|
29
|
-
}
|
|
30
|
-
if (!isOpen) {
|
|
31
|
-
setAndroidKeyboardHeight(0);
|
|
32
|
-
return;
|
|
33
|
-
}
|
|
34
|
-
const keyboardDidShowSubscription = Keyboard.addListener('keyboardDidShow', event => {
|
|
35
|
-
setAndroidKeyboardHeight(event.endCoordinates?.height ?? 0);
|
|
36
|
-
});
|
|
37
|
-
const keyboardDidHideSubscription = Keyboard.addListener('keyboardDidHide', () => {
|
|
38
|
-
setAndroidKeyboardHeight(0);
|
|
39
|
-
});
|
|
40
|
-
return () => {
|
|
41
|
-
keyboardDidShowSubscription.remove();
|
|
42
|
-
keyboardDidHideSubscription.remove();
|
|
43
|
-
};
|
|
44
|
-
}, [isOpen]);
|
|
45
|
-
if (!hasJsonWebTokenFormat(properties.clientKey)) {
|
|
10
|
+
export const DuffelAssistant = props => {
|
|
11
|
+
if (!hasJsonWebTokenFormat(props.clientKey)) {
|
|
46
12
|
throw new Error('Duffel Assistant client key format must be a valid JWT.');
|
|
47
13
|
}
|
|
48
|
-
const clientKeyPayload = getClientKeyPayload(
|
|
14
|
+
const clientKeyPayload = getClientKeyPayload(props.clientKey);
|
|
49
15
|
if (!clientKeyPayload) {
|
|
50
16
|
throw new Error('The client key property is invalid. A client key must follow the JWT format.');
|
|
51
17
|
}
|
|
@@ -53,127 +19,32 @@ export const DuffelAssistant = ({
|
|
|
53
19
|
if (!('user_id' in clientKeyPayload)) {
|
|
54
20
|
throw new Error('The client key in the props does not include a user_id. Make sure a user_id is included in the payload of the client key creation.');
|
|
55
21
|
}
|
|
56
|
-
if (typeof
|
|
22
|
+
if (typeof props.issueType !== 'undefined') {
|
|
57
23
|
console.warn('The issueType prop has been deprecated and will no longer be supported in the future. Please use the context prop instead.');
|
|
58
24
|
}
|
|
59
|
-
|
|
25
|
+
const sanitizedProps = {
|
|
26
|
+
...props
|
|
27
|
+
};
|
|
28
|
+
if (!clientKeyIncludesResource && typeof sanitizedProps.issueType !== 'undefined') {
|
|
60
29
|
console.warn('The issueType prop is only supported when the client key includes a resource id, it will be ignored.');
|
|
61
|
-
delete
|
|
30
|
+
delete sanitizedProps.issueType;
|
|
62
31
|
}
|
|
63
|
-
if (!clientKeyIncludesResource &&
|
|
32
|
+
if (!clientKeyIncludesResource && sanitizedProps.context) {
|
|
64
33
|
throw new Error('The context prop is only supported when the client key includes a resource id.');
|
|
65
34
|
}
|
|
66
|
-
if (typeof
|
|
67
|
-
throw new Error(
|
|
35
|
+
if (typeof sanitizedProps.supportChannels !== 'undefined' && sanitizedProps.supportChannels.chat !== true) {
|
|
36
|
+
throw new Error("The supportChannels prop must keep chat enabled. Phone-only support isn't offered.");
|
|
68
37
|
}
|
|
69
|
-
if (
|
|
38
|
+
if (sanitizedProps.showMinimiseButton && typeof sanitizedProps.onMinimise !== 'function') {
|
|
70
39
|
console.warn('The showMinimiseButton prop is set to true, but the onMinimise callback is not provided. Make sure to listen to the onMinimise callback to handle the minimisation of the Assistant.');
|
|
71
40
|
}
|
|
72
|
-
const handleMessage = event => {
|
|
73
|
-
if (event.nativeEvent.data === 'duffel-assistant-close') {
|
|
74
|
-
onClose();
|
|
75
|
-
}
|
|
76
|
-
if (typeof properties.onMinimise === 'function' && event.nativeEvent.data === 'duffel-assistant-minimise') {
|
|
77
|
-
properties.onMinimise();
|
|
78
|
-
}
|
|
79
|
-
try {
|
|
80
|
-
const parsedData = JSON.parse(event.nativeEvent.data);
|
|
81
|
-
if (parsedData.type === 'duffel-assistant-new-message' && typeof parsedData.userId === 'string' && typeof parsedData.resourceId === 'string' && typeof onNewMessage === 'function') {
|
|
82
|
-
onNewMessage({
|
|
83
|
-
userId: parsedData.userId,
|
|
84
|
-
resourceId: parsedData.resourceId
|
|
85
|
-
});
|
|
86
|
-
}
|
|
87
|
-
} catch (error) {
|
|
88
|
-
// do nothing, invalid JSON
|
|
89
|
-
}
|
|
90
|
-
};
|
|
91
|
-
const handleShouldStartLoadWithRequest = request => {
|
|
92
|
-
if (!isHttpUrl(request.url) || isAssistantIframeUrl(request.url)) {
|
|
93
|
-
return true;
|
|
94
|
-
}
|
|
95
|
-
if (Platform.OS === 'ios' && request.navigationType !== 'click') {
|
|
96
|
-
return true;
|
|
97
|
-
}
|
|
98
|
-
Linking.openURL(request.url).catch(() => {});
|
|
99
|
-
return false;
|
|
100
|
-
};
|
|
101
41
|
if (Platform.OS === 'android') {
|
|
102
|
-
return /*#__PURE__*/_jsx(
|
|
103
|
-
|
|
104
|
-
onRequestClose: () => onClose(),
|
|
105
|
-
animationType: "slide",
|
|
106
|
-
children: /*#__PURE__*/_jsx(View, {
|
|
107
|
-
style: [styles.androidContainer, {
|
|
108
|
-
paddingBottom: 15 + androidKeyboardHeight
|
|
109
|
-
}],
|
|
110
|
-
children: /*#__PURE__*/_jsx(WebView
|
|
111
|
-
// Required for Android
|
|
112
|
-
, {
|
|
113
|
-
injectedJavaScript: `
|
|
114
|
-
postMessage(
|
|
115
|
-
{
|
|
116
|
-
type: "duffel-assistant-open",
|
|
117
|
-
properties: ${JSON.stringify(properties)},
|
|
118
|
-
},
|
|
119
|
-
"*",
|
|
120
|
-
);
|
|
121
|
-
true;`,
|
|
122
|
-
source: {
|
|
123
|
-
uri: assistantIframeUrl
|
|
124
|
-
},
|
|
125
|
-
onMessage: handleMessage,
|
|
126
|
-
onShouldStartLoadWithRequest: handleShouldStartLoadWithRequest
|
|
127
|
-
// Always fetch the latest assistant bundle. The iframe.html shell
|
|
128
|
-
// references `./iframe-app.js` by a stable path, so without this
|
|
129
|
-
// the system WebView can serve a stale bundle for a long time.
|
|
130
|
-
,
|
|
131
|
-
cacheEnabled: false,
|
|
132
|
-
cacheMode: "LOAD_NO_CACHE"
|
|
133
|
-
})
|
|
134
|
-
})
|
|
42
|
+
return /*#__PURE__*/_jsx(DuffelAssistantForAndroid, {
|
|
43
|
+
...sanitizedProps
|
|
135
44
|
});
|
|
136
45
|
}
|
|
137
|
-
return /*#__PURE__*/_jsx(
|
|
138
|
-
|
|
139
|
-
onRequestClose: () => onClose(),
|
|
140
|
-
animationType: "slide",
|
|
141
|
-
children: /*#__PURE__*/_jsx(View, {
|
|
142
|
-
style: styles.container,
|
|
143
|
-
children: /*#__PURE__*/_jsx(WebView, {
|
|
144
|
-
injectedJavaScriptObject: properties
|
|
145
|
-
// When you are running this in development iOS doesn't allow request from hosts with invalid https certificates.
|
|
146
|
-
// If you try ngrok with the local esbuild server it will fail because ngrok requests in https and the dev server rejects http requests.
|
|
147
|
-
// Easiest solution is to use the production URL with test data.
|
|
148
|
-
// If that's not possible, you can also upload the assistant built assets to a different folder in assets.duffel and work with that.
|
|
149
|
-
,
|
|
150
|
-
source: {
|
|
151
|
-
uri: assistantIframeUrl
|
|
152
|
-
},
|
|
153
|
-
onMessage: handleMessage,
|
|
154
|
-
onShouldStartLoadWithRequest: handleShouldStartLoadWithRequest
|
|
155
|
-
// Always fetch the latest assistant bundle. The iframe.html shell
|
|
156
|
-
// references `./iframe-app.js` by a stable path, so without this
|
|
157
|
-
// the system WebView can serve a stale bundle for a long time.
|
|
158
|
-
,
|
|
159
|
-
cacheEnabled: false
|
|
160
|
-
})
|
|
161
|
-
})
|
|
46
|
+
return /*#__PURE__*/_jsx(DuffelAssistantForiOS, {
|
|
47
|
+
...sanitizedProps
|
|
162
48
|
});
|
|
163
49
|
};
|
|
164
|
-
const styles = StyleSheet.create({
|
|
165
|
-
container: {
|
|
166
|
-
height: '100%',
|
|
167
|
-
width: '100%',
|
|
168
|
-
paddingHorizontal: 12,
|
|
169
|
-
paddingTop: 50,
|
|
170
|
-
paddingBottom: 30
|
|
171
|
-
},
|
|
172
|
-
androidContainer: {
|
|
173
|
-
flex: 1,
|
|
174
|
-
width: '100%',
|
|
175
|
-
paddingTop: 45,
|
|
176
|
-
paddingBottom: 15
|
|
177
|
-
}
|
|
178
|
-
});
|
|
179
50
|
//# sourceMappingURL=index.js.map
|
package/lib/module/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["React","
|
|
1
|
+
{"version":3,"names":["React","Platform","DuffelAssistantForAndroid","DuffelAssistantForiOS","hasJsonWebTokenFormat","getClientKeyPayload","jsx","_jsx","DuffelAssistant","props","clientKey","Error","clientKeyPayload","clientKeyIncludesResource","order_id","undefined","booking_id","cars_booking_id","issueType","console","warn","sanitizedProps","context","supportChannels","chat","showMinimiseButton","onMinimise","OS"],"sourceRoot":"../../src","sources":["index.tsx"],"mappings":";;AAAA,OAAOA,KAAK,MAAM,OAAO;AACzB,SAASC,QAAQ,QAAQ,cAAc;AAEvC,SAASC,yBAAyB,QAAQ,gCAA6B;AACvE,SAASC,qBAAqB,QAAQ,4BAAyB;AAC/D,SAASC,qBAAqB,QAAQ,gCAA6B;AACnE,SAASC,mBAAmB,QAAQ,8BAA2B;AAAC,SAAAC,GAAA,IAAAC,IAAA;AAIhE,OAAO,MAAMC,eAA+C,GAAIC,KAAK,IAAK;EACxE,IAAI,CAACL,qBAAqB,CAACK,KAAK,CAACC,SAAS,CAAC,EAAE;IAC3C,MAAM,IAAIC,KAAK,CAAC,yDAAyD,CAAC;EAC5E;EAEA,MAAMC,gBAAgB,GAAGP,mBAAmB,CAACI,KAAK,CAACC,SAAS,CAAC;EAC7D,IAAI,CAACE,gBAAgB,EAAE;IACrB,MAAM,IAAID,KAAK,CACb,8EACF,CAAC;EACH;EAEA,MAAME,yBAAyB,GAC7BD,gBAAgB,CAACE,QAAQ,KAAKC,SAAS,IACvCH,gBAAgB,CAACI,UAAU,KAAKD,SAAS,IACzCH,gBAAgB,CAACK,eAAe,KAAKF,SAAS;EAEhD,IAAI,EAAE,SAAS,IAAIH,gBAAgB,CAAC,EAAE;IACpC,MAAM,IAAID,KAAK,CACb,oIACF,CAAC;EACH;EAEA,IAAI,OAAOF,KAAK,CAACS,SAAS,KAAK,WAAW,EAAE;IAC1CC,OAAO,CAACC,IAAI,CACV,4HACF,CAAC;EACH;EAEA,MAAMC,cAAoC,GAAG;IAAE,GAAGZ;EAAM,CAAC;EAEzD,IACE,CAACI,yBAAyB,IAC1B,OAAOQ,cAAc,CAACH,SAAS,KAAK,WAAW,EAC/C;IACAC,OAAO,CAACC,IAAI,CACV,sGACF,CAAC;IACD,OAAOC,cAAc,CAACH,SAAS;EACjC;EAEA,IAAI,CAACL,yBAAyB,IAAIQ,cAAc,CAACC,OAAO,EAAE;IACxD,MAAM,IAAIX,KAAK,CACb,gFACF,CAAC;EACH;EAEA,IACE,OAAOU,cAAc,CAACE,eAAe,KAAK,WAAW,IACrDF,cAAc,CAACE,eAAe,CAACC,IAAI,KAAK,IAAI,EAC5C;IACA,MAAM,IAAIb,KAAK,CACb,oFACF,CAAC;EACH;EAEA,IACEU,cAAc,CAACI,kBAAkB,IACjC,OAAOJ,cAAc,CAACK,UAAU,KAAK,UAAU,EAC/C;IACAP,OAAO,CAACC,IAAI,CACV,sLACF,CAAC;EACH;EAEA,IAAInB,QAAQ,CAAC0B,EAAE,KAAK,SAAS,EAAE;IAC7B,oBAAOpB,IAAA,CAACL,yBAAyB;MAAA,GAAKmB;IAAc,CAAG,CAAC;EAC1D;EAEA,oBAAOd,IAAA,CAACJ,qBAAqB;IAAA,GAAKkB;EAAc,CAAG,CAAC;AACtD,CAAC","ignoreList":[]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":[],"sourceRoot":"../../../src","sources":["lib/ShouldStartLoadRequest.ts"],"mappings":"","ignoreList":[]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":[],"sourceRoot":"../../../src","sources":["lib/WebViewMessageEvent.ts"],"mappings":"","ignoreList":[]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["assistantIframeUrl"],"sourceRoot":"../../../src","sources":["lib/assistantIframeUrl.ts"],"mappings":";;AAAA,OAAO,MAAMA,kBAAkB,GAC7B,iDAAiD","ignoreList":[]}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
import { assistantIframeUrl } from "./assistantIframeUrl.js";
|
|
4
|
+
export const isAssistantIframeUrl = url => {
|
|
5
|
+
try {
|
|
6
|
+
const parsedUrl = new URL(url);
|
|
7
|
+
return `${parsedUrl.origin}${parsedUrl.pathname}` === assistantIframeUrl;
|
|
8
|
+
} catch (error) {
|
|
9
|
+
return false;
|
|
10
|
+
}
|
|
11
|
+
};
|
|
12
|
+
//# sourceMappingURL=isAssistantIframeUrl.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["assistantIframeUrl","isAssistantIframeUrl","url","parsedUrl","URL","origin","pathname","error"],"sourceRoot":"../../../src","sources":["lib/isAssistantIframeUrl.ts"],"mappings":";;AAAA,SAASA,kBAAkB,QAAQ,yBAAsB;AAEzD,OAAO,MAAMC,oBAAoB,GAAIC,GAAW,IAAK;EACnD,IAAI;IACF,MAAMC,SAAS,GAAG,IAAIC,GAAG,CAACF,GAAG,CAAC;IAE9B,OAAO,GAAGC,SAAS,CAACE,MAAM,GAAGF,SAAS,CAACG,QAAQ,EAAE,KAAKN,kBAAkB;EAC1E,CAAC,CAAC,OAAOO,KAAK,EAAE;IACd,OAAO,KAAK;EACd;AACF,CAAC","ignoreList":[]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["isHttpUrl","url","test"],"sourceRoot":"../../../src","sources":["lib/isHttpUrl.ts"],"mappings":";;AAAA,OAAO,MAAMA,SAAS,GAAIC,GAAW,IAAK,cAAc,CAACC,IAAI,CAACD,GAAG,CAAC","ignoreList":[]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DuffelAssistantForAndroid.d.ts","sourceRoot":"","sources":["../../../src/DuffelAssistantForAndroid.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA8B,MAAM,OAAO,CAAC;AAGnD,OAAO,EAAE,KAAK,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAOnE,eAAO,MAAM,yBAAyB,EAAE,KAAK,CAAC,EAAE,CAAC,oBAAoB,CA+GpE,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DuffelAssistantForiOS.d.ts","sourceRoot":"","sources":["../../../src/DuffelAssistantForiOS.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAG1B,OAAO,EAAE,KAAK,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAOnE,eAAO,MAAM,qBAAqB,EAAE,KAAK,CAAC,EAAE,CAAC,oBAAoB,CA8EhE,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.tsx"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,OAAO,EAAE,KAAK,oBAAoB,IAAI,4BAA4B,EAAE,MAAM,wBAAwB,CAAC;AAMnG,MAAM,MAAM,oBAAoB,GAAG,4BAA4B,CAAC;AAEhE,eAAO,MAAM,eAAe,EAAE,KAAK,CAAC,EAAE,CAAC,oBAAoB,CAsE1D,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ShouldStartLoadRequest.d.ts","sourceRoot":"","sources":["../../../../src/lib/ShouldStartLoadRequest.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,sBAAsB,GAAG;IACnC,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,GAAG,EAAE,MAAM,CAAC;CACb,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"WebViewMessageEvent.d.ts","sourceRoot":"","sources":["../../../../src/lib/WebViewMessageEvent.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,mBAAmB,GAAG;IAChC,WAAW,EAAE;QACX,IAAI,EAAE,MAAM,CAAC;KACd,CAAC;CACH,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"assistantIframeUrl.d.ts","sourceRoot":"","sources":["../../../../src/lib/assistantIframeUrl.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,kBAAkB,oDACoB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"isAssistantIframeUrl.d.ts","sourceRoot":"","sources":["../../../../src/lib/isAssistantIframeUrl.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,oBAAoB,GAAI,KAAK,MAAM,YAQ/C,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"isHttpUrl.d.ts","sourceRoot":"","sources":["../../../../src/lib/isHttpUrl.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,SAAS,GAAI,KAAK,MAAM,YAA6B,CAAC"}
|
package/package.json
CHANGED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import React, { useEffect, useState } from 'react';
|
|
2
|
+
import { Keyboard, Linking, Modal, StyleSheet, View } from 'react-native';
|
|
3
|
+
import { WebView } from 'react-native-webview';
|
|
4
|
+
import { type DuffelAssistantProps } from './DuffelAssistantProps';
|
|
5
|
+
import { assistantIframeUrl } from './lib/assistantIframeUrl';
|
|
6
|
+
import { isAssistantIframeUrl } from './lib/isAssistantIframeUrl';
|
|
7
|
+
import { isHttpUrl } from './lib/isHttpUrl';
|
|
8
|
+
import { type ShouldStartLoadRequest } from './lib/ShouldStartLoadRequest';
|
|
9
|
+
import { type WebViewMessageEvent } from './lib/WebViewMessageEvent';
|
|
10
|
+
|
|
11
|
+
export const DuffelAssistantForAndroid: React.FC<DuffelAssistantProps> = ({
|
|
12
|
+
isOpen,
|
|
13
|
+
onClose,
|
|
14
|
+
onNewMessage,
|
|
15
|
+
...properties
|
|
16
|
+
}) => {
|
|
17
|
+
const [androidKeyboardHeight, setAndroidKeyboardHeight] = useState(0);
|
|
18
|
+
|
|
19
|
+
useEffect(() => {
|
|
20
|
+
if (!isOpen) {
|
|
21
|
+
setAndroidKeyboardHeight(0);
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const keyboardDidShowSubscription = Keyboard.addListener(
|
|
26
|
+
'keyboardDidShow',
|
|
27
|
+
(event) => {
|
|
28
|
+
setAndroidKeyboardHeight(event.endCoordinates?.height ?? 0);
|
|
29
|
+
}
|
|
30
|
+
);
|
|
31
|
+
const keyboardDidHideSubscription = Keyboard.addListener(
|
|
32
|
+
'keyboardDidHide',
|
|
33
|
+
() => {
|
|
34
|
+
setAndroidKeyboardHeight(0);
|
|
35
|
+
}
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
return () => {
|
|
39
|
+
keyboardDidShowSubscription.remove();
|
|
40
|
+
keyboardDidHideSubscription.remove();
|
|
41
|
+
};
|
|
42
|
+
}, [isOpen]);
|
|
43
|
+
|
|
44
|
+
const handleMessage = (event: WebViewMessageEvent) => {
|
|
45
|
+
if (event.nativeEvent.data === 'duffel-assistant-close') {
|
|
46
|
+
onClose();
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (
|
|
50
|
+
typeof properties.onMinimise === 'function' &&
|
|
51
|
+
event.nativeEvent.data === 'duffel-assistant-minimise'
|
|
52
|
+
) {
|
|
53
|
+
properties.onMinimise();
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
try {
|
|
57
|
+
const parsedData = JSON.parse(event.nativeEvent.data);
|
|
58
|
+
|
|
59
|
+
if (
|
|
60
|
+
parsedData.type === 'duffel-assistant-new-message' &&
|
|
61
|
+
typeof parsedData.userId === 'string' &&
|
|
62
|
+
typeof parsedData.resourceId === 'string' &&
|
|
63
|
+
typeof onNewMessage === 'function'
|
|
64
|
+
) {
|
|
65
|
+
onNewMessage({
|
|
66
|
+
userId: parsedData.userId,
|
|
67
|
+
resourceId: parsedData.resourceId,
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
} catch (error) {
|
|
71
|
+
// do nothing, invalid JSON
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const handleShouldStartLoadWithRequest = (
|
|
76
|
+
request: ShouldStartLoadRequest
|
|
77
|
+
) => {
|
|
78
|
+
if (!isHttpUrl(request.url) || isAssistantIframeUrl(request.url)) {
|
|
79
|
+
return true;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
Linking.openURL(request.url).catch(() => {});
|
|
83
|
+
|
|
84
|
+
return false;
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
return (
|
|
88
|
+
<Modal
|
|
89
|
+
visible={isOpen}
|
|
90
|
+
onRequestClose={() => onClose()}
|
|
91
|
+
animationType="slide"
|
|
92
|
+
>
|
|
93
|
+
<View
|
|
94
|
+
style={[
|
|
95
|
+
styles.androidContainer,
|
|
96
|
+
{ paddingBottom: 15 + androidKeyboardHeight },
|
|
97
|
+
]}
|
|
98
|
+
>
|
|
99
|
+
<WebView
|
|
100
|
+
// Required for Android
|
|
101
|
+
injectedJavaScript={`
|
|
102
|
+
postMessage(
|
|
103
|
+
{
|
|
104
|
+
type: "duffel-assistant-open",
|
|
105
|
+
properties: ${JSON.stringify(properties)},
|
|
106
|
+
},
|
|
107
|
+
"*",
|
|
108
|
+
);
|
|
109
|
+
true;`}
|
|
110
|
+
source={{ uri: assistantIframeUrl }}
|
|
111
|
+
onMessage={handleMessage}
|
|
112
|
+
onShouldStartLoadWithRequest={handleShouldStartLoadWithRequest}
|
|
113
|
+
// Always fetch the latest assistant bundle. The iframe.html shell
|
|
114
|
+
// references `./iframe-app.js` by a stable path, so without this
|
|
115
|
+
// the system WebView can serve a stale bundle for a long time.
|
|
116
|
+
cacheEnabled={false}
|
|
117
|
+
cacheMode="LOAD_NO_CACHE"
|
|
118
|
+
/>
|
|
119
|
+
</View>
|
|
120
|
+
</Modal>
|
|
121
|
+
);
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
const styles = StyleSheet.create({
|
|
125
|
+
androidContainer: {
|
|
126
|
+
flex: 1,
|
|
127
|
+
width: '100%',
|
|
128
|
+
paddingTop: 45,
|
|
129
|
+
paddingBottom: 15,
|
|
130
|
+
},
|
|
131
|
+
});
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { KeyboardAvoidingView, Linking, Modal, StyleSheet } from 'react-native';
|
|
3
|
+
import { WebView } from 'react-native-webview';
|
|
4
|
+
import { type DuffelAssistantProps } from './DuffelAssistantProps';
|
|
5
|
+
import { assistantIframeUrl } from './lib/assistantIframeUrl';
|
|
6
|
+
import { isAssistantIframeUrl } from './lib/isAssistantIframeUrl';
|
|
7
|
+
import { isHttpUrl } from './lib/isHttpUrl';
|
|
8
|
+
import { type ShouldStartLoadRequest } from './lib/ShouldStartLoadRequest';
|
|
9
|
+
import { type WebViewMessageEvent } from './lib/WebViewMessageEvent';
|
|
10
|
+
|
|
11
|
+
export const DuffelAssistantForiOS: React.FC<DuffelAssistantProps> = ({
|
|
12
|
+
isOpen,
|
|
13
|
+
onClose,
|
|
14
|
+
onNewMessage,
|
|
15
|
+
...properties
|
|
16
|
+
}) => {
|
|
17
|
+
const handleMessage = (event: WebViewMessageEvent) => {
|
|
18
|
+
if (event.nativeEvent.data === 'duffel-assistant-close') {
|
|
19
|
+
onClose();
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (
|
|
23
|
+
typeof properties.onMinimise === 'function' &&
|
|
24
|
+
event.nativeEvent.data === 'duffel-assistant-minimise'
|
|
25
|
+
) {
|
|
26
|
+
properties.onMinimise();
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
try {
|
|
30
|
+
const parsedData = JSON.parse(event.nativeEvent.data);
|
|
31
|
+
|
|
32
|
+
if (
|
|
33
|
+
parsedData.type === 'duffel-assistant-new-message' &&
|
|
34
|
+
typeof parsedData.userId === 'string' &&
|
|
35
|
+
typeof parsedData.resourceId === 'string' &&
|
|
36
|
+
typeof onNewMessage === 'function'
|
|
37
|
+
) {
|
|
38
|
+
onNewMessage({
|
|
39
|
+
userId: parsedData.userId,
|
|
40
|
+
resourceId: parsedData.resourceId,
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
} catch (error) {
|
|
44
|
+
// do nothing, invalid JSON
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const handleShouldStartLoadWithRequest = (
|
|
49
|
+
request: ShouldStartLoadRequest
|
|
50
|
+
) => {
|
|
51
|
+
if (!isHttpUrl(request.url) || isAssistantIframeUrl(request.url)) {
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (request.navigationType !== 'click') {
|
|
56
|
+
return true;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
Linking.openURL(request.url).catch(() => {});
|
|
60
|
+
|
|
61
|
+
return false;
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
return (
|
|
65
|
+
<Modal
|
|
66
|
+
visible={isOpen}
|
|
67
|
+
onRequestClose={() => onClose()}
|
|
68
|
+
animationType="slide"
|
|
69
|
+
>
|
|
70
|
+
<KeyboardAvoidingView behavior="padding" style={styles.container}>
|
|
71
|
+
<WebView
|
|
72
|
+
style={styles.webView}
|
|
73
|
+
injectedJavaScriptObject={properties}
|
|
74
|
+
// When you are running this in development iOS doesn't allow request from hosts with invalid https certificates.
|
|
75
|
+
// If you try ngrok with the local esbuild server it will fail because ngrok requests in https and the dev server rejects http requests.
|
|
76
|
+
// Easiest solution is to use the production URL with test data.
|
|
77
|
+
// If that's not possible, you can also upload the assistant built assets to a different folder in assets.duffel and work with that.
|
|
78
|
+
source={{ uri: assistantIframeUrl }}
|
|
79
|
+
onMessage={handleMessage}
|
|
80
|
+
onShouldStartLoadWithRequest={handleShouldStartLoadWithRequest}
|
|
81
|
+
// Always fetch the latest assistant bundle. The iframe.html shell
|
|
82
|
+
// references `./iframe-app.js` by a stable path, so without this
|
|
83
|
+
// the system WebView can serve a stale bundle for a long time.
|
|
84
|
+
cacheEnabled={false}
|
|
85
|
+
/>
|
|
86
|
+
</KeyboardAvoidingView>
|
|
87
|
+
</Modal>
|
|
88
|
+
);
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
const styles = StyleSheet.create({
|
|
92
|
+
container: {
|
|
93
|
+
height: '100%',
|
|
94
|
+
width: '100%',
|
|
95
|
+
paddingHorizontal: 12,
|
|
96
|
+
paddingTop: 50,
|
|
97
|
+
},
|
|
98
|
+
webView: {
|
|
99
|
+
paddingBottom: 7,
|
|
100
|
+
},
|
|
101
|
+
});
|
package/src/index.tsx
CHANGED
|
@@ -1,86 +1,19 @@
|
|
|
1
|
-
import React
|
|
2
|
-
import {
|
|
3
|
-
Keyboard,
|
|
4
|
-
Linking,
|
|
5
|
-
Modal,
|
|
6
|
-
Platform,
|
|
7
|
-
StyleSheet,
|
|
8
|
-
View,
|
|
9
|
-
} from 'react-native';
|
|
10
|
-
import { WebView } from 'react-native-webview';
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Platform } from 'react-native';
|
|
11
3
|
import { type DuffelAssistantProps as DuffelAssistantPropsImported } from './DuffelAssistantProps';
|
|
4
|
+
import { DuffelAssistantForAndroid } from './DuffelAssistantForAndroid';
|
|
5
|
+
import { DuffelAssistantForiOS } from './DuffelAssistantForiOS';
|
|
12
6
|
import { hasJsonWebTokenFormat } from './lib/hasJsonWebTokenFormat';
|
|
13
7
|
import { getClientKeyPayload } from './lib/getClientKeyPayload';
|
|
14
8
|
|
|
15
9
|
export type DuffelAssistantProps = DuffelAssistantPropsImported;
|
|
16
10
|
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
type WebViewMessageEvent = {
|
|
20
|
-
nativeEvent: {
|
|
21
|
-
data: string;
|
|
22
|
-
};
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
type ShouldStartLoadRequest = {
|
|
26
|
-
navigationType?: string;
|
|
27
|
-
url: string;
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
const isHttpUrl = (url: string) => /^https?:\/\//.test(url);
|
|
31
|
-
|
|
32
|
-
const isAssistantIframeUrl = (url: string) => {
|
|
33
|
-
try {
|
|
34
|
-
const parsedUrl = new URL(url);
|
|
35
|
-
|
|
36
|
-
return `${parsedUrl.origin}${parsedUrl.pathname}` === assistantIframeUrl;
|
|
37
|
-
} catch (error) {
|
|
38
|
-
return false;
|
|
39
|
-
}
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
export const DuffelAssistant: React.FC<DuffelAssistantProps> = ({
|
|
43
|
-
isOpen,
|
|
44
|
-
onClose,
|
|
45
|
-
onNewMessage,
|
|
46
|
-
...properties
|
|
47
|
-
}) => {
|
|
48
|
-
const [androidKeyboardHeight, setAndroidKeyboardHeight] = useState(0);
|
|
49
|
-
|
|
50
|
-
useEffect(() => {
|
|
51
|
-
if (Platform.OS !== 'android') {
|
|
52
|
-
return;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
if (!isOpen) {
|
|
56
|
-
setAndroidKeyboardHeight(0);
|
|
57
|
-
return;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
const keyboardDidShowSubscription = Keyboard.addListener(
|
|
61
|
-
'keyboardDidShow',
|
|
62
|
-
(event) => {
|
|
63
|
-
setAndroidKeyboardHeight(event.endCoordinates?.height ?? 0);
|
|
64
|
-
}
|
|
65
|
-
);
|
|
66
|
-
const keyboardDidHideSubscription = Keyboard.addListener(
|
|
67
|
-
'keyboardDidHide',
|
|
68
|
-
() => {
|
|
69
|
-
setAndroidKeyboardHeight(0);
|
|
70
|
-
}
|
|
71
|
-
);
|
|
72
|
-
|
|
73
|
-
return () => {
|
|
74
|
-
keyboardDidShowSubscription.remove();
|
|
75
|
-
keyboardDidHideSubscription.remove();
|
|
76
|
-
};
|
|
77
|
-
}, [isOpen]);
|
|
78
|
-
|
|
79
|
-
if (!hasJsonWebTokenFormat(properties.clientKey)) {
|
|
11
|
+
export const DuffelAssistant: React.FC<DuffelAssistantProps> = (props) => {
|
|
12
|
+
if (!hasJsonWebTokenFormat(props.clientKey)) {
|
|
80
13
|
throw new Error('Duffel Assistant client key format must be a valid JWT.');
|
|
81
14
|
}
|
|
82
15
|
|
|
83
|
-
const clientKeyPayload = getClientKeyPayload(
|
|
16
|
+
const clientKeyPayload = getClientKeyPayload(props.clientKey);
|
|
84
17
|
if (!clientKeyPayload) {
|
|
85
18
|
throw new Error(
|
|
86
19
|
'The client key property is invalid. A client key must follow the JWT format.'
|
|
@@ -98,169 +31,51 @@ export const DuffelAssistant: React.FC<DuffelAssistantProps> = ({
|
|
|
98
31
|
);
|
|
99
32
|
}
|
|
100
33
|
|
|
101
|
-
if (typeof
|
|
34
|
+
if (typeof props.issueType !== 'undefined') {
|
|
102
35
|
console.warn(
|
|
103
36
|
'The issueType prop has been deprecated and will no longer be supported in the future. Please use the context prop instead.'
|
|
104
37
|
);
|
|
105
38
|
}
|
|
106
39
|
|
|
40
|
+
const sanitizedProps: DuffelAssistantProps = { ...props };
|
|
41
|
+
|
|
107
42
|
if (
|
|
108
43
|
!clientKeyIncludesResource &&
|
|
109
|
-
typeof
|
|
44
|
+
typeof sanitizedProps.issueType !== 'undefined'
|
|
110
45
|
) {
|
|
111
46
|
console.warn(
|
|
112
47
|
'The issueType prop is only supported when the client key includes a resource id, it will be ignored.'
|
|
113
48
|
);
|
|
114
|
-
delete
|
|
49
|
+
delete sanitizedProps.issueType;
|
|
115
50
|
}
|
|
116
51
|
|
|
117
|
-
if (!clientKeyIncludesResource &&
|
|
52
|
+
if (!clientKeyIncludesResource && sanitizedProps.context) {
|
|
118
53
|
throw new Error(
|
|
119
54
|
'The context prop is only supported when the client key includes a resource id.'
|
|
120
55
|
);
|
|
121
56
|
}
|
|
122
57
|
|
|
123
58
|
if (
|
|
124
|
-
typeof
|
|
125
|
-
|
|
59
|
+
typeof sanitizedProps.supportChannels !== 'undefined' &&
|
|
60
|
+
sanitizedProps.supportChannels.chat !== true
|
|
126
61
|
) {
|
|
127
62
|
throw new Error(
|
|
128
|
-
|
|
63
|
+
"The supportChannels prop must keep chat enabled. Phone-only support isn't offered."
|
|
129
64
|
);
|
|
130
65
|
}
|
|
131
66
|
|
|
132
67
|
if (
|
|
133
|
-
|
|
134
|
-
typeof
|
|
68
|
+
sanitizedProps.showMinimiseButton &&
|
|
69
|
+
typeof sanitizedProps.onMinimise !== 'function'
|
|
135
70
|
) {
|
|
136
71
|
console.warn(
|
|
137
72
|
'The showMinimiseButton prop is set to true, but the onMinimise callback is not provided. Make sure to listen to the onMinimise callback to handle the minimisation of the Assistant.'
|
|
138
73
|
);
|
|
139
74
|
}
|
|
140
75
|
|
|
141
|
-
const handleMessage = (event: WebViewMessageEvent) => {
|
|
142
|
-
if (event.nativeEvent.data === 'duffel-assistant-close') {
|
|
143
|
-
onClose();
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
if (
|
|
147
|
-
typeof properties.onMinimise === 'function' &&
|
|
148
|
-
event.nativeEvent.data === 'duffel-assistant-minimise'
|
|
149
|
-
) {
|
|
150
|
-
properties.onMinimise();
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
try {
|
|
154
|
-
const parsedData = JSON.parse(event.nativeEvent.data);
|
|
155
|
-
|
|
156
|
-
if (
|
|
157
|
-
parsedData.type === 'duffel-assistant-new-message' &&
|
|
158
|
-
typeof parsedData.userId === 'string' &&
|
|
159
|
-
typeof parsedData.resourceId === 'string' &&
|
|
160
|
-
typeof onNewMessage === 'function'
|
|
161
|
-
) {
|
|
162
|
-
onNewMessage({
|
|
163
|
-
userId: parsedData.userId,
|
|
164
|
-
resourceId: parsedData.resourceId,
|
|
165
|
-
});
|
|
166
|
-
}
|
|
167
|
-
} catch (error) {
|
|
168
|
-
// do nothing, invalid JSON
|
|
169
|
-
}
|
|
170
|
-
};
|
|
171
|
-
|
|
172
|
-
const handleShouldStartLoadWithRequest = (
|
|
173
|
-
request: ShouldStartLoadRequest
|
|
174
|
-
) => {
|
|
175
|
-
if (!isHttpUrl(request.url) || isAssistantIframeUrl(request.url)) {
|
|
176
|
-
return true;
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
if (Platform.OS === 'ios' && request.navigationType !== 'click') {
|
|
180
|
-
return true;
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
Linking.openURL(request.url).catch(() => {});
|
|
184
|
-
|
|
185
|
-
return false;
|
|
186
|
-
};
|
|
187
|
-
|
|
188
76
|
if (Platform.OS === 'android') {
|
|
189
|
-
return
|
|
190
|
-
<Modal
|
|
191
|
-
visible={isOpen}
|
|
192
|
-
onRequestClose={() => onClose()}
|
|
193
|
-
animationType="slide"
|
|
194
|
-
>
|
|
195
|
-
<View
|
|
196
|
-
style={[
|
|
197
|
-
styles.androidContainer,
|
|
198
|
-
{ paddingBottom: 15 + androidKeyboardHeight },
|
|
199
|
-
]}
|
|
200
|
-
>
|
|
201
|
-
<WebView
|
|
202
|
-
// Required for Android
|
|
203
|
-
injectedJavaScript={`
|
|
204
|
-
postMessage(
|
|
205
|
-
{
|
|
206
|
-
type: "duffel-assistant-open",
|
|
207
|
-
properties: ${JSON.stringify(properties)},
|
|
208
|
-
},
|
|
209
|
-
"*",
|
|
210
|
-
);
|
|
211
|
-
true;`}
|
|
212
|
-
source={{ uri: assistantIframeUrl }}
|
|
213
|
-
onMessage={handleMessage}
|
|
214
|
-
onShouldStartLoadWithRequest={handleShouldStartLoadWithRequest}
|
|
215
|
-
// Always fetch the latest assistant bundle. The iframe.html shell
|
|
216
|
-
// references `./iframe-app.js` by a stable path, so without this
|
|
217
|
-
// the system WebView can serve a stale bundle for a long time.
|
|
218
|
-
cacheEnabled={false}
|
|
219
|
-
cacheMode="LOAD_NO_CACHE"
|
|
220
|
-
/>
|
|
221
|
-
</View>
|
|
222
|
-
</Modal>
|
|
223
|
-
);
|
|
77
|
+
return <DuffelAssistantForAndroid {...sanitizedProps} />;
|
|
224
78
|
}
|
|
225
79
|
|
|
226
|
-
return
|
|
227
|
-
<Modal
|
|
228
|
-
visible={isOpen}
|
|
229
|
-
onRequestClose={() => onClose()}
|
|
230
|
-
animationType="slide"
|
|
231
|
-
>
|
|
232
|
-
<View style={styles.container}>
|
|
233
|
-
<WebView
|
|
234
|
-
injectedJavaScriptObject={properties}
|
|
235
|
-
// When you are running this in development iOS doesn't allow request from hosts with invalid https certificates.
|
|
236
|
-
// If you try ngrok with the local esbuild server it will fail because ngrok requests in https and the dev server rejects http requests.
|
|
237
|
-
// Easiest solution is to use the production URL with test data.
|
|
238
|
-
// If that's not possible, you can also upload the assistant built assets to a different folder in assets.duffel and work with that.
|
|
239
|
-
source={{ uri: assistantIframeUrl }}
|
|
240
|
-
onMessage={handleMessage}
|
|
241
|
-
onShouldStartLoadWithRequest={handleShouldStartLoadWithRequest}
|
|
242
|
-
// Always fetch the latest assistant bundle. The iframe.html shell
|
|
243
|
-
// references `./iframe-app.js` by a stable path, so without this
|
|
244
|
-
// the system WebView can serve a stale bundle for a long time.
|
|
245
|
-
cacheEnabled={false}
|
|
246
|
-
/>
|
|
247
|
-
</View>
|
|
248
|
-
</Modal>
|
|
249
|
-
);
|
|
80
|
+
return <DuffelAssistantForiOS {...sanitizedProps} />;
|
|
250
81
|
};
|
|
251
|
-
|
|
252
|
-
const styles = StyleSheet.create({
|
|
253
|
-
container: {
|
|
254
|
-
height: '100%',
|
|
255
|
-
width: '100%',
|
|
256
|
-
paddingHorizontal: 12,
|
|
257
|
-
paddingTop: 50,
|
|
258
|
-
paddingBottom: 30,
|
|
259
|
-
},
|
|
260
|
-
androidContainer: {
|
|
261
|
-
flex: 1,
|
|
262
|
-
width: '100%',
|
|
263
|
-
paddingTop: 45,
|
|
264
|
-
paddingBottom: 15,
|
|
265
|
-
},
|
|
266
|
-
});
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { assistantIframeUrl } from './assistantIframeUrl';
|
|
2
|
+
|
|
3
|
+
export const isAssistantIframeUrl = (url: string) => {
|
|
4
|
+
try {
|
|
5
|
+
const parsedUrl = new URL(url);
|
|
6
|
+
|
|
7
|
+
return `${parsedUrl.origin}${parsedUrl.pathname}` === assistantIframeUrl;
|
|
8
|
+
} catch (error) {
|
|
9
|
+
return false;
|
|
10
|
+
}
|
|
11
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const isHttpUrl = (url: string) => /^https?:\/\//.test(url);
|