@duffel/react-native-components-assistant 0.5.2-canary.0 → 0.6.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.
Files changed (40) hide show
  1. package/lib/module/DuffelAssistantForAndroid.js +102 -0
  2. package/lib/module/DuffelAssistantForAndroid.js.map +1 -0
  3. package/lib/module/DuffelAssistantForiOS.js +85 -0
  4. package/lib/module/DuffelAssistantForiOS.js.map +1 -0
  5. package/lib/module/index.js +21 -150
  6. package/lib/module/index.js.map +1 -1
  7. package/lib/module/lib/ShouldStartLoadRequest.js +2 -0
  8. package/lib/module/lib/ShouldStartLoadRequest.js.map +1 -0
  9. package/lib/module/lib/WebViewMessageEvent.js +2 -0
  10. package/lib/module/lib/WebViewMessageEvent.js.map +1 -0
  11. package/lib/module/lib/assistantIframeUrl.js +4 -0
  12. package/lib/module/lib/assistantIframeUrl.js.map +1 -0
  13. package/lib/module/lib/isAssistantIframeUrl.js +12 -0
  14. package/lib/module/lib/isAssistantIframeUrl.js.map +1 -0
  15. package/lib/module/lib/isHttpUrl.js +4 -0
  16. package/lib/module/lib/isHttpUrl.js.map +1 -0
  17. package/lib/typescript/src/DuffelAssistantForAndroid.d.ts +4 -0
  18. package/lib/typescript/src/DuffelAssistantForAndroid.d.ts.map +1 -0
  19. package/lib/typescript/src/DuffelAssistantForiOS.d.ts +4 -0
  20. package/lib/typescript/src/DuffelAssistantForiOS.d.ts.map +1 -0
  21. package/lib/typescript/src/index.d.ts.map +1 -1
  22. package/lib/typescript/src/lib/ShouldStartLoadRequest.d.ts +5 -0
  23. package/lib/typescript/src/lib/ShouldStartLoadRequest.d.ts.map +1 -0
  24. package/lib/typescript/src/lib/WebViewMessageEvent.d.ts +6 -0
  25. package/lib/typescript/src/lib/WebViewMessageEvent.d.ts.map +1 -0
  26. package/lib/typescript/src/lib/assistantIframeUrl.d.ts +2 -0
  27. package/lib/typescript/src/lib/assistantIframeUrl.d.ts.map +1 -0
  28. package/lib/typescript/src/lib/isAssistantIframeUrl.d.ts +2 -0
  29. package/lib/typescript/src/lib/isAssistantIframeUrl.d.ts.map +1 -0
  30. package/lib/typescript/src/lib/isHttpUrl.d.ts +2 -0
  31. package/lib/typescript/src/lib/isHttpUrl.d.ts.map +1 -0
  32. package/package.json +1 -1
  33. package/src/DuffelAssistantForAndroid.tsx +131 -0
  34. package/src/DuffelAssistantForiOS.tsx +101 -0
  35. package/src/index.tsx +20 -205
  36. package/src/lib/ShouldStartLoadRequest.ts +4 -0
  37. package/src/lib/WebViewMessageEvent.ts +5 -0
  38. package/src/lib/assistantIframeUrl.ts +2 -0
  39. package/src/lib/isAssistantIframeUrl.ts +11 -0
  40. 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":[]}
@@ -1,51 +1,17 @@
1
1
  "use strict";
2
2
 
3
- import React, { useEffect, useState } from 'react';
4
- import { Keyboard, Linking, Modal, Platform, StyleSheet, View } from 'react-native';
5
- import { WebView } from 'react-native-webview';
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 assistantIframeUrl = 'https://assets.duffel.com/assistant/iframe.html';
10
- const isHttpUrl = url => /^https?:\/\//.test(url);
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(properties.clientKey);
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 properties.issueType !== 'undefined') {
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
- if (!clientKeyIncludesResource && typeof properties.issueType !== 'undefined') {
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 properties.issueType;
30
+ delete sanitizedProps.issueType;
62
31
  }
63
- if (!clientKeyIncludesResource && properties.context) {
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 properties.supportChannels !== 'undefined' && properties.supportChannels.chat !== true) {
67
- throw new Error('The supportChannels prop must keep chat enabled. Phone-only support isnt offered.');
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 (properties.showMinimiseButton && typeof properties.onMinimise !== 'function') {
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(Modal, {
103
- visible: isOpen,
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(Modal, {
138
- visible: isOpen,
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
@@ -1 +1 @@
1
- {"version":3,"names":["React","useEffect","useState","Keyboard","Linking","Modal","Platform","StyleSheet","View","WebView","hasJsonWebTokenFormat","getClientKeyPayload","jsx","_jsx","assistantIframeUrl","isHttpUrl","url","test","isAssistantIframeUrl","parsedUrl","URL","origin","pathname","error","DuffelAssistant","isOpen","onClose","onNewMessage","properties","androidKeyboardHeight","setAndroidKeyboardHeight","OS","keyboardDidShowSubscription","addListener","event","endCoordinates","height","keyboardDidHideSubscription","remove","clientKey","Error","clientKeyPayload","clientKeyIncludesResource","order_id","undefined","booking_id","cars_booking_id","issueType","console","warn","context","supportChannels","chat","showMinimiseButton","onMinimise","handleMessage","nativeEvent","data","parsedData","JSON","parse","type","userId","resourceId","handleShouldStartLoadWithRequest","request","navigationType","openURL","catch","visible","onRequestClose","animationType","children","style","styles","androidContainer","paddingBottom","injectedJavaScript","stringify","source","uri","onMessage","onShouldStartLoadWithRequest","cacheEnabled","cacheMode","container","injectedJavaScriptObject","create","width","paddingHorizontal","paddingTop","flex"],"sourceRoot":"../../src","sources":["index.tsx"],"mappings":"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","ignoreList":[]}
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,2 @@
1
+ "use strict";
2
+ //# sourceMappingURL=ShouldStartLoadRequest.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":[],"sourceRoot":"../../../src","sources":["lib/ShouldStartLoadRequest.ts"],"mappings":"","ignoreList":[]}
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ //# sourceMappingURL=WebViewMessageEvent.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":[],"sourceRoot":"../../../src","sources":["lib/WebViewMessageEvent.ts"],"mappings":"","ignoreList":[]}
@@ -0,0 +1,4 @@
1
+ "use strict";
2
+
3
+ export const assistantIframeUrl = 'https://assets.duffel.com/assistant/iframe.html';
4
+ //# sourceMappingURL=assistantIframeUrl.js.map
@@ -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,4 @@
1
+ "use strict";
2
+
3
+ export const isHttpUrl = url => /^https?:\/\//.test(url);
4
+ //# sourceMappingURL=isHttpUrl.js.map
@@ -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,4 @@
1
+ import React from 'react';
2
+ import { type DuffelAssistantProps } from './DuffelAssistantProps';
3
+ export declare const DuffelAssistantForAndroid: React.FC<DuffelAssistantProps>;
4
+ //# sourceMappingURL=DuffelAssistantForAndroid.d.ts.map
@@ -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,4 @@
1
+ import React from 'react';
2
+ import { type DuffelAssistantProps } from './DuffelAssistantProps';
3
+ export declare const DuffelAssistantForiOS: React.FC<DuffelAssistantProps>;
4
+ //# sourceMappingURL=DuffelAssistantForiOS.d.ts.map
@@ -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,KAA8B,MAAM,OAAO,CAAC;AAUnD,OAAO,EAAE,KAAK,oBAAoB,IAAI,4BAA4B,EAAE,MAAM,wBAAwB,CAAC;AAInG,MAAM,MAAM,oBAAoB,GAAG,4BAA4B,CAAC;AA2BhE,eAAO,MAAM,eAAe,EAAE,KAAK,CAAC,EAAE,CAAC,oBAAoB,CAgN1D,CAAC"}
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,5 @@
1
+ export type ShouldStartLoadRequest = {
2
+ navigationType?: string;
3
+ url: string;
4
+ };
5
+ //# sourceMappingURL=ShouldStartLoadRequest.d.ts.map
@@ -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,6 @@
1
+ export type WebViewMessageEvent = {
2
+ nativeEvent: {
3
+ data: string;
4
+ };
5
+ };
6
+ //# sourceMappingURL=WebViewMessageEvent.d.ts.map
@@ -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,2 @@
1
+ export declare const assistantIframeUrl = "https://assets.duffel.com/assistant/iframe.html";
2
+ //# sourceMappingURL=assistantIframeUrl.d.ts.map
@@ -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,2 @@
1
+ export declare const isAssistantIframeUrl: (url: string) => boolean;
2
+ //# sourceMappingURL=isAssistantIframeUrl.d.ts.map
@@ -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,2 @@
1
+ export declare const isHttpUrl: (url: string) => boolean;
2
+ //# sourceMappingURL=isHttpUrl.d.ts.map
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@duffel/react-native-components-assistant",
3
- "version": "0.5.2-canary.0",
3
+ "version": "0.6.0",
4
4
  "description": "Duffel Assistant component in React Native",
5
5
  "main": "./lib/module/index.js",
6
6
  "types": "./lib/typescript/src/index.d.ts",
@@ -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, { useEffect, useState } from '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 assistantIframeUrl = 'https://assets.duffel.com/assistant/iframe.html';
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(properties.clientKey);
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 properties.issueType !== 'undefined') {
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 properties.issueType !== 'undefined'
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 properties.issueType;
49
+ delete sanitizedProps.issueType;
115
50
  }
116
51
 
117
- if (!clientKeyIncludesResource && properties.context) {
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 properties.supportChannels !== 'undefined' &&
125
- properties.supportChannels.chat !== true
59
+ typeof sanitizedProps.supportChannels !== 'undefined' &&
60
+ sanitizedProps.supportChannels.chat !== true
126
61
  ) {
127
62
  throw new Error(
128
- 'The supportChannels prop must keep chat enabled. Phone-only support isnt offered.'
63
+ "The supportChannels prop must keep chat enabled. Phone-only support isn't offered."
129
64
  );
130
65
  }
131
66
 
132
67
  if (
133
- properties.showMinimiseButton &&
134
- typeof properties.onMinimise !== 'function'
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,4 @@
1
+ export type ShouldStartLoadRequest = {
2
+ navigationType?: string;
3
+ url: string;
4
+ };
@@ -0,0 +1,5 @@
1
+ export type WebViewMessageEvent = {
2
+ nativeEvent: {
3
+ data: string;
4
+ };
5
+ };
@@ -0,0 +1,2 @@
1
+ export const assistantIframeUrl =
2
+ 'https://assets.duffel.com/assistant/iframe.html';
@@ -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);