@duffel/react-native-components-assistant 0.5.1 → 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.
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 +23 -113
  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 +21 -147
  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":";;AAAA,OAAOA,KAAK,IAAIC,SAAS,EAAEC,QAAQ,QAAQ,OAAO;AAClD,SAASC,QAAQ,EAAEC,OAAO,EAAEC,KAAK,EAAEC,UAAU,EAAEC,IAAI,QAAQ,cAAc;AACzE,SAASC,OAAO,QAAQ,sBAAsB;AAE9C,SAASC,kBAAkB,QAAQ,6BAA0B;AAC7D,SAASC,oBAAoB,QAAQ,+BAA4B;AACjE,SAASC,SAAS,QAAQ,oBAAiB;AAAC,SAAAC,GAAA,IAAAC,IAAA;AAI5C,OAAO,MAAMC,yBAAyD,GAAGA,CAAC;EACxEC,MAAM;EACNC,OAAO;EACPC,YAAY;EACZ,GAAGC;AACL,CAAC,KAAK;EACJ,MAAM,CAACC,qBAAqB,EAAEC,wBAAwB,CAAC,GAAGlB,QAAQ,CAAC,CAAC,CAAC;EAErED,SAAS,CAAC,MAAM;IACd,IAAI,CAACc,MAAM,EAAE;MACXK,wBAAwB,CAAC,CAAC,CAAC;MAC3B;IACF;IAEA,MAAMC,2BAA2B,GAAGlB,QAAQ,CAACmB,WAAW,CACtD,iBAAiB,EAChBC,KAAK,IAAK;MACTH,wBAAwB,CAACG,KAAK,CAACC,cAAc,EAAEC,MAAM,IAAI,CAAC,CAAC;IAC7D,CACF,CAAC;IACD,MAAMC,2BAA2B,GAAGvB,QAAQ,CAACmB,WAAW,CACtD,iBAAiB,EACjB,MAAM;MACJF,wBAAwB,CAAC,CAAC,CAAC;IAC7B,CACF,CAAC;IAED,OAAO,MAAM;MACXC,2BAA2B,CAACM,MAAM,CAAC,CAAC;MACpCD,2BAA2B,CAACC,MAAM,CAAC,CAAC;IACtC,CAAC;EACH,CAAC,EAAE,CAACZ,MAAM,CAAC,CAAC;EAEZ,MAAMa,aAAa,GAAIL,KAA0B,IAAK;IACpD,IAAIA,KAAK,CAACM,WAAW,CAACC,IAAI,KAAK,wBAAwB,EAAE;MACvDd,OAAO,CAAC,CAAC;IACX;IAEA,IACE,OAAOE,UAAU,CAACa,UAAU,KAAK,UAAU,IAC3CR,KAAK,CAACM,WAAW,CAACC,IAAI,KAAK,2BAA2B,EACtD;MACAZ,UAAU,CAACa,UAAU,CAAC,CAAC;IACzB;IAEA,IAAI;MACF,MAAMC,UAAU,GAAGC,IAAI,CAACC,KAAK,CAACX,KAAK,CAACM,WAAW,CAACC,IAAI,CAAC;MAErD,IACEE,UAAU,CAACG,IAAI,KAAK,8BAA8B,IAClD,OAAOH,UAAU,CAACI,MAAM,KAAK,QAAQ,IACrC,OAAOJ,UAAU,CAACK,UAAU,KAAK,QAAQ,IACzC,OAAOpB,YAAY,KAAK,UAAU,EAClC;QACAA,YAAY,CAAC;UACXmB,MAAM,EAAEJ,UAAU,CAACI,MAAM;UACzBC,UAAU,EAAEL,UAAU,CAACK;QACzB,CAAC,CAAC;MACJ;IACF,CAAC,CAAC,OAAOC,KAAK,EAAE;MACd;IAAA;EAEJ,CAAC;EAED,MAAMC,gCAAgC,GACpCC,OAA+B,IAC5B;IACH,IAAI,CAAC7B,SAAS,CAAC6B,OAAO,CAACC,GAAG,CAAC,IAAI/B,oBAAoB,CAAC8B,OAAO,CAACC,GAAG,CAAC,EAAE;MAChE,OAAO,IAAI;IACb;IAEArC,OAAO,CAACsC,OAAO,CAACF,OAAO,CAACC,GAAG,CAAC,CAACE,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC;IAE5C,OAAO,KAAK;EACd,CAAC;EAED,oBACE9B,IAAA,CAACR,KAAK;IACJuC,OAAO,EAAE7B,MAAO;IAChB8B,cAAc,EAAEA,CAAA,KAAM7B,OAAO,CAAC,CAAE;IAChC8B,aAAa,EAAC,OAAO;IAAAC,QAAA,eAErBlC,IAAA,CAACN,IAAI;MACHyC,KAAK,EAAE,CACLC,MAAM,CAACC,gBAAgB,EACvB;QAAEC,aAAa,EAAE,EAAE,GAAGhC;MAAsB,CAAC,CAC7C;MAAA4B,QAAA,eAEFlC,IAAA,CAACL;MACC;MAAA;QACA4C,kBAAkB,EAAE;AAC9B;AACA;AACA;AACA,4BAA4BnB,IAAI,CAACoB,SAAS,CAACnC,UAAU,CAAC;AACtD;AACA;AACA;AACA,gBAAiB;QACPoC,MAAM,EAAE;UAAEC,GAAG,EAAE9C;QAAmB,CAAE;QACpC+C,SAAS,EAAE5B,aAAc;QACzB6B,4BAA4B,EAAElB;QAC9B;QACA;QACA;QAAA;QACAmB,YAAY,EAAE,KAAM;QACpBC,SAAS,EAAC;MAAe,CAC1B;IAAC,CACE;EAAC,CACF,CAAC;AAEZ,CAAC;AAED,MAAMV,MAAM,GAAG3C,UAAU,CAACsD,MAAM,CAAC;EAC/BV,gBAAgB,EAAE;IAChBW,IAAI,EAAE,CAAC;IACPC,KAAK,EAAE,MAAM;IACbC,UAAU,EAAE,EAAE;IACdZ,aAAa,EAAE;EACjB;AACF,CAAC,CAAC","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,31 +1,17 @@
1
1
  "use strict";
2
2
 
3
3
  import React from 'react';
4
- import { Linking, Modal, Platform, StyleSheet, View } from 'react-native';
5
- import { WebView } from 'react-native-webview';
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
- if (!hasJsonWebTokenFormat(properties.clientKey)) {
10
+ export const DuffelAssistant = props => {
11
+ if (!hasJsonWebTokenFormat(props.clientKey)) {
26
12
  throw new Error('Duffel Assistant client key format must be a valid JWT.');
27
13
  }
28
- const clientKeyPayload = getClientKeyPayload(properties.clientKey);
14
+ const clientKeyPayload = getClientKeyPayload(props.clientKey);
29
15
  if (!clientKeyPayload) {
30
16
  throw new Error('The client key property is invalid. A client key must follow the JWT format.');
31
17
  }
@@ -33,108 +19,32 @@ export const DuffelAssistant = ({
33
19
  if (!('user_id' in clientKeyPayload)) {
34
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.');
35
21
  }
36
- if (typeof properties.issueType !== 'undefined') {
22
+ if (typeof props.issueType !== 'undefined') {
37
23
  console.warn('The issueType prop has been deprecated and will no longer be supported in the future. Please use the context prop instead.');
38
24
  }
39
- if (!clientKeyIncludesResource && typeof properties.issueType !== 'undefined') {
25
+ const sanitizedProps = {
26
+ ...props
27
+ };
28
+ if (!clientKeyIncludesResource && typeof sanitizedProps.issueType !== 'undefined') {
40
29
  console.warn('The issueType prop is only supported when the client key includes a resource id, it will be ignored.');
41
- delete properties.issueType;
30
+ delete sanitizedProps.issueType;
42
31
  }
43
- if (!clientKeyIncludesResource && properties.context) {
32
+ if (!clientKeyIncludesResource && sanitizedProps.context) {
44
33
  throw new Error('The context prop is only supported when the client key includes a resource id.');
45
34
  }
46
- if (typeof properties.supportChannels !== 'undefined' && properties.supportChannels.chat !== true) {
47
- 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.");
48
37
  }
49
- if (properties.showMinimiseButton && typeof properties.onMinimise !== 'function') {
38
+ if (sanitizedProps.showMinimiseButton && typeof sanitizedProps.onMinimise !== 'function') {
50
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.');
51
40
  }
52
- const handleMessage = event => {
53
- if (event.nativeEvent.data === 'duffel-assistant-close') {
54
- onClose();
55
- }
56
- if (typeof properties.onMinimise === 'function' && event.nativeEvent.data === 'duffel-assistant-minimise') {
57
- properties.onMinimise();
58
- }
59
- try {
60
- const parsedData = JSON.parse(event.nativeEvent.data);
61
- if (parsedData.type === 'duffel-assistant-new-message' && typeof parsedData.userId === 'string' && typeof parsedData.resourceId === 'string' && typeof onNewMessage === 'function') {
62
- onNewMessage({
63
- userId: parsedData.userId,
64
- resourceId: parsedData.resourceId
65
- });
66
- }
67
- } catch (error) {
68
- // do nothing, invalid JSON
69
- }
70
- };
71
- const handleShouldStartLoadWithRequest = request => {
72
- if (!isHttpUrl(request.url) || isAssistantIframeUrl(request.url)) {
73
- return true;
74
- }
75
- if (Platform.OS === 'ios' && request.navigationType !== 'click') {
76
- return true;
77
- }
78
- Linking.openURL(request.url).catch(() => {});
79
- return false;
80
- };
81
- return /*#__PURE__*/_jsx(Modal, {
82
- visible: isOpen,
83
- onRequestClose: () => onClose(),
84
- animationType: "slide",
85
- children: /*#__PURE__*/_jsx(View, {
86
- style: styles.container,
87
- children: Platform.OS === 'ios' ? /*#__PURE__*/_jsx(WebView, {
88
- injectedJavaScriptObject: properties
89
- // When you are running this in development iOS doesn't allow request from hosts with invalid https certificates.
90
- // If you try ngrok with the local esbuild server it will fail because ngrok requests in https and the dev server rejects http requests.
91
- // Easiest solution is to use the production URL with test data.
92
- // If that's not possible, you can also upload the assistant built assets to a different folder in assets.duffel and work with that.
93
- ,
94
- source: {
95
- uri: assistantIframeUrl
96
- },
97
- onMessage: handleMessage,
98
- onShouldStartLoadWithRequest: handleShouldStartLoadWithRequest
99
- // Always fetch the latest assistant bundle. The iframe.html shell
100
- // references `./iframe-app.js` by a stable path, so without this
101
- // the system WebView can serve a stale bundle for a long time.
102
- ,
103
- cacheEnabled: false
104
- }) : /*#__PURE__*/_jsx(WebView
105
- // Required for Android
106
- , {
107
- injectedJavaScript: `
108
- postMessage(
109
- {
110
- type: "duffel-assistant-open",
111
- properties: ${JSON.stringify(properties)},
112
- },
113
- "*",
114
- );
115
- true;`,
116
- source: {
117
- uri: assistantIframeUrl
118
- },
119
- onMessage: handleMessage,
120
- onShouldStartLoadWithRequest: handleShouldStartLoadWithRequest
121
- // Always fetch the latest assistant bundle. The iframe.html shell
122
- // references `./iframe-app.js` by a stable path, so without this
123
- // the system WebView can serve a stale bundle for a long time.
124
- ,
125
- cacheEnabled: false,
126
- cacheMode: "LOAD_NO_CACHE"
127
- })
128
- })
41
+ if (Platform.OS === 'android') {
42
+ return /*#__PURE__*/_jsx(DuffelAssistantForAndroid, {
43
+ ...sanitizedProps
44
+ });
45
+ }
46
+ return /*#__PURE__*/_jsx(DuffelAssistantForiOS, {
47
+ ...sanitizedProps
129
48
  });
130
49
  };
131
- const styles = StyleSheet.create({
132
- container: {
133
- height: '100%',
134
- width: '100%',
135
- paddingHorizontal: 12,
136
- paddingTop: 50,
137
- paddingBottom: 30
138
- }
139
- });
140
50
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"names":["React","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","clientKey","Error","clientKeyPayload","clientKeyIncludesResource","order_id","undefined","booking_id","cars_booking_id","issueType","console","warn","context","supportChannels","chat","showMinimiseButton","onMinimise","handleMessage","event","nativeEvent","data","parsedData","JSON","parse","type","userId","resourceId","handleShouldStartLoadWithRequest","request","OS","navigationType","openURL","catch","visible","onRequestClose","animationType","children","style","styles","container","injectedJavaScriptObject","source","uri","onMessage","onShouldStartLoadWithRequest","cacheEnabled","injectedJavaScript","stringify","cacheMode","create","height","width","paddingHorizontal","paddingTop","paddingBottom"],"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,KAAK,MAAM,OAAO,CAAC;AAG1B,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,CAkK1D,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.1",
3
+ "version": "0.6.0-canary.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,48 +1,19 @@
1
1
  import React from 'react';
2
- import { Linking, Modal, Platform, StyleSheet, View } from 'react-native';
3
- import { WebView } from 'react-native-webview';
2
+ import { Platform } from 'react-native';
4
3
  import { type DuffelAssistantProps as DuffelAssistantPropsImported } from './DuffelAssistantProps';
4
+ import { DuffelAssistantForAndroid } from './DuffelAssistantForAndroid';
5
+ import { DuffelAssistantForiOS } from './DuffelAssistantForiOS';
5
6
  import { hasJsonWebTokenFormat } from './lib/hasJsonWebTokenFormat';
6
7
  import { getClientKeyPayload } from './lib/getClientKeyPayload';
7
8
 
8
9
  export type DuffelAssistantProps = DuffelAssistantPropsImported;
9
10
 
10
- const assistantIframeUrl = 'https://assets.duffel.com/assistant/iframe.html';
11
-
12
- type WebViewMessageEvent = {
13
- nativeEvent: {
14
- data: string;
15
- };
16
- };
17
-
18
- type ShouldStartLoadRequest = {
19
- navigationType?: string;
20
- url: string;
21
- };
22
-
23
- const isHttpUrl = (url: string) => /^https?:\/\//.test(url);
24
-
25
- const isAssistantIframeUrl = (url: string) => {
26
- try {
27
- const parsedUrl = new URL(url);
28
-
29
- return `${parsedUrl.origin}${parsedUrl.pathname}` === assistantIframeUrl;
30
- } catch (error) {
31
- return false;
32
- }
33
- };
34
-
35
- export const DuffelAssistant: React.FC<DuffelAssistantProps> = ({
36
- isOpen,
37
- onClose,
38
- onNewMessage,
39
- ...properties
40
- }) => {
41
- if (!hasJsonWebTokenFormat(properties.clientKey)) {
11
+ export const DuffelAssistant: React.FC<DuffelAssistantProps> = (props) => {
12
+ if (!hasJsonWebTokenFormat(props.clientKey)) {
42
13
  throw new Error('Duffel Assistant client key format must be a valid JWT.');
43
14
  }
44
15
 
45
- const clientKeyPayload = getClientKeyPayload(properties.clientKey);
16
+ const clientKeyPayload = getClientKeyPayload(props.clientKey);
46
17
  if (!clientKeyPayload) {
47
18
  throw new Error(
48
19
  'The client key property is invalid. A client key must follow the JWT format.'
@@ -60,148 +31,51 @@ export const DuffelAssistant: React.FC<DuffelAssistantProps> = ({
60
31
  );
61
32
  }
62
33
 
63
- if (typeof properties.issueType !== 'undefined') {
34
+ if (typeof props.issueType !== 'undefined') {
64
35
  console.warn(
65
36
  'The issueType prop has been deprecated and will no longer be supported in the future. Please use the context prop instead.'
66
37
  );
67
38
  }
68
39
 
40
+ const sanitizedProps: DuffelAssistantProps = { ...props };
41
+
69
42
  if (
70
43
  !clientKeyIncludesResource &&
71
- typeof properties.issueType !== 'undefined'
44
+ typeof sanitizedProps.issueType !== 'undefined'
72
45
  ) {
73
46
  console.warn(
74
47
  'The issueType prop is only supported when the client key includes a resource id, it will be ignored.'
75
48
  );
76
- delete properties.issueType;
49
+ delete sanitizedProps.issueType;
77
50
  }
78
51
 
79
- if (!clientKeyIncludesResource && properties.context) {
52
+ if (!clientKeyIncludesResource && sanitizedProps.context) {
80
53
  throw new Error(
81
54
  'The context prop is only supported when the client key includes a resource id.'
82
55
  );
83
56
  }
84
57
 
85
58
  if (
86
- typeof properties.supportChannels !== 'undefined' &&
87
- properties.supportChannels.chat !== true
59
+ typeof sanitizedProps.supportChannels !== 'undefined' &&
60
+ sanitizedProps.supportChannels.chat !== true
88
61
  ) {
89
62
  throw new Error(
90
- '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."
91
64
  );
92
65
  }
93
66
 
94
67
  if (
95
- properties.showMinimiseButton &&
96
- typeof properties.onMinimise !== 'function'
68
+ sanitizedProps.showMinimiseButton &&
69
+ typeof sanitizedProps.onMinimise !== 'function'
97
70
  ) {
98
71
  console.warn(
99
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.'
100
73
  );
101
74
  }
102
75
 
103
- const handleMessage = (event: WebViewMessageEvent) => {
104
- if (event.nativeEvent.data === 'duffel-assistant-close') {
105
- onClose();
106
- }
107
-
108
- if (
109
- typeof properties.onMinimise === 'function' &&
110
- event.nativeEvent.data === 'duffel-assistant-minimise'
111
- ) {
112
- properties.onMinimise();
113
- }
114
-
115
- try {
116
- const parsedData = JSON.parse(event.nativeEvent.data);
117
-
118
- if (
119
- parsedData.type === 'duffel-assistant-new-message' &&
120
- typeof parsedData.userId === 'string' &&
121
- typeof parsedData.resourceId === 'string' &&
122
- typeof onNewMessage === 'function'
123
- ) {
124
- onNewMessage({
125
- userId: parsedData.userId,
126
- resourceId: parsedData.resourceId,
127
- });
128
- }
129
- } catch (error) {
130
- // do nothing, invalid JSON
131
- }
132
- };
133
-
134
- const handleShouldStartLoadWithRequest = (
135
- request: ShouldStartLoadRequest
136
- ) => {
137
- if (!isHttpUrl(request.url) || isAssistantIframeUrl(request.url)) {
138
- return true;
139
- }
140
-
141
- if (Platform.OS === 'ios' && request.navigationType !== 'click') {
142
- return true;
143
- }
144
-
145
- Linking.openURL(request.url).catch(() => {});
146
-
147
- return false;
148
- };
76
+ if (Platform.OS === 'android') {
77
+ return <DuffelAssistantForAndroid {...sanitizedProps} />;
78
+ }
149
79
 
150
- return (
151
- <Modal
152
- visible={isOpen}
153
- onRequestClose={() => onClose()}
154
- animationType="slide"
155
- >
156
- <View style={styles.container}>
157
- {Platform.OS === 'ios' ? (
158
- <WebView
159
- injectedJavaScriptObject={properties}
160
- // When you are running this in development iOS doesn't allow request from hosts with invalid https certificates.
161
- // If you try ngrok with the local esbuild server it will fail because ngrok requests in https and the dev server rejects http requests.
162
- // Easiest solution is to use the production URL with test data.
163
- // If that's not possible, you can also upload the assistant built assets to a different folder in assets.duffel and work with that.
164
- source={{ uri: assistantIframeUrl }}
165
- onMessage={handleMessage}
166
- onShouldStartLoadWithRequest={handleShouldStartLoadWithRequest}
167
- // Always fetch the latest assistant bundle. The iframe.html shell
168
- // references `./iframe-app.js` by a stable path, so without this
169
- // the system WebView can serve a stale bundle for a long time.
170
- cacheEnabled={false}
171
- />
172
- ) : (
173
- <WebView
174
- // Required for Android
175
- injectedJavaScript={`
176
- postMessage(
177
- {
178
- type: "duffel-assistant-open",
179
- properties: ${JSON.stringify(properties)},
180
- },
181
- "*",
182
- );
183
- true;`}
184
- source={{ uri: assistantIframeUrl }}
185
- onMessage={handleMessage}
186
- onShouldStartLoadWithRequest={handleShouldStartLoadWithRequest}
187
- // Always fetch the latest assistant bundle. The iframe.html shell
188
- // references `./iframe-app.js` by a stable path, so without this
189
- // the system WebView can serve a stale bundle for a long time.
190
- cacheEnabled={false}
191
- cacheMode="LOAD_NO_CACHE"
192
- />
193
- )}
194
- </View>
195
- </Modal>
196
- );
80
+ return <DuffelAssistantForiOS {...sanitizedProps} />;
197
81
  };
198
-
199
- const styles = StyleSheet.create({
200
- container: {
201
- height: '100%',
202
- width: '100%',
203
- paddingHorizontal: 12,
204
- paddingTop: 50,
205
- paddingBottom: 30,
206
- },
207
- });
@@ -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);