@duffel/react-native-components-assistant 0.2.0 → 0.4.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.
@@ -4,15 +4,35 @@ import React from 'react';
4
4
  import { Modal, Platform, StyleSheet, View } from 'react-native';
5
5
  import { WebView } from 'react-native-webview';
6
6
  import { hasJsonWebTokenFormat } from "./lib/hasJsonWebTokenFormat.js";
7
+ import { getClientKeyPayload } from "./lib/getClientKeyPayload.js";
7
8
  import { jsx as _jsx } from "react/jsx-runtime";
8
9
  export const DuffelAssistant = ({
9
10
  isOpen,
10
11
  onClose,
12
+ onNewMessage,
11
13
  ...properties
12
14
  }) => {
13
15
  if (!hasJsonWebTokenFormat(properties.clientKey)) {
14
16
  throw new Error('Duffel Assistant client key format must be a valid JWT.');
15
17
  }
18
+ const clientKeyPayload = getClientKeyPayload(properties.clientKey);
19
+ if (!clientKeyPayload) {
20
+ throw new Error('The client key property is invalid. A client key must follow the JWT format.');
21
+ }
22
+ const clientKeyIncludesResource = clientKeyPayload.order_id !== undefined || clientKeyPayload.booking_id !== undefined;
23
+ if (!('user_id' in clientKeyPayload)) {
24
+ 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.');
25
+ }
26
+ if (typeof properties.issueType !== 'undefined') {
27
+ console.warn('The issueType prop has been deprecated and will no longer be supported in the future. Please use the context prop instead.');
28
+ }
29
+ if (!clientKeyIncludesResource && typeof properties.issueType !== 'undefined') {
30
+ console.warn('The issueType prop is only supported when the client key includes a resource id, it will be ignored.');
31
+ delete properties.issueType;
32
+ }
33
+ if (!clientKeyIncludesResource && properties.context) {
34
+ throw new Error('The context prop is only supported when the client key includes a resource id.');
35
+ }
16
36
  return /*#__PURE__*/_jsx(Modal, {
17
37
  visible: isOpen,
18
38
  onRequestClose: () => onClose(),
@@ -20,7 +40,12 @@ export const DuffelAssistant = ({
20
40
  children: /*#__PURE__*/_jsx(View, {
21
41
  style: styles.container,
22
42
  children: Platform.OS === 'ios' ? /*#__PURE__*/_jsx(WebView, {
23
- injectedJavaScriptObject: properties,
43
+ injectedJavaScriptObject: properties
44
+ // When you are running this in development iOS doesn't allow request from hosts with invalid https certificates.
45
+ // If you try ngrok with the local esbuild server it will fail because ngrok requests in https and the dev server rejects http requests.
46
+ // Easiest solution is to use the production URL with test data.
47
+ // If that's not possible, you can also upload the assistant built assets to a different folder in assets.duffel and work with that.
48
+ ,
24
49
  source: {
25
50
  uri: 'https://assets.duffel.com/assistant/iframe.html'
26
51
  },
@@ -28,6 +53,17 @@ export const DuffelAssistant = ({
28
53
  if (event.nativeEvent.data === 'duffel-assistant-close') {
29
54
  onClose();
30
55
  }
56
+ try {
57
+ const parsedData = JSON.parse(event.nativeEvent.data);
58
+ if (parsedData.type === 'duffel-assistant-new-message' && typeof parsedData.userId === 'string' && typeof parsedData.resourceId === 'string' && onNewMessage !== undefined) {
59
+ onNewMessage({
60
+ userId: parsedData.userId,
61
+ resourceId: parsedData.resourceId
62
+ });
63
+ }
64
+ } catch (error) {
65
+ // do nothing, invalid JSON
66
+ }
31
67
  }
32
68
  }) : /*#__PURE__*/_jsx(WebView
33
69
  // Required for Android
@@ -1 +1 @@
1
- {"version":3,"names":["React","Modal","Platform","StyleSheet","View","WebView","hasJsonWebTokenFormat","jsx","_jsx","DuffelAssistant","isOpen","onClose","properties","clientKey","Error","visible","onRequestClose","animationType","children","style","styles","container","OS","injectedJavaScriptObject","source","uri","onMessage","event","nativeEvent","data","injectedJavaScript","JSON","stringify","create","height","width","paddingHorizontal","paddingTop","paddingBottom"],"sourceRoot":"../../src","sources":["index.tsx"],"mappings":";;AAAA,OAAOA,KAAK,MAAM,OAAO;AACzB,SAASC,KAAK,EAAEC,QAAQ,EAAEC,UAAU,EAAEC,IAAI,QAAQ,cAAc;AAChE,SAASC,OAAO,QAAQ,sBAAsB;AAE9C,SAASC,qBAAqB,QAAQ,gCAA6B;AAAC,SAAAC,GAAA,IAAAC,IAAA;AAIpE,OAAO,MAAMC,eAA+C,GAAGA,CAAC;EAC9DC,MAAM;EACNC,OAAO;EACP,GAAGC;AACL,CAAC,KAAK;EACJ,IAAI,CAACN,qBAAqB,CAACM,UAAU,CAACC,SAAS,CAAC,EAAE;IAChD,MAAM,IAAIC,KAAK,CAAC,yDAAyD,CAAC;EAC5E;EAEA,oBACEN,IAAA,CAACP,KAAK;IACJc,OAAO,EAAEL,MAAO;IAChBM,cAAc,EAAEA,CAAA,KAAML,OAAO,CAAC,CAAE;IAChCM,aAAa,EAAC,OAAO;IAAAC,QAAA,eAErBV,IAAA,CAACJ,IAAI;MAACe,KAAK,EAAEC,MAAM,CAACC,SAAU;MAAAH,QAAA,EAC3BhB,QAAQ,CAACoB,EAAE,KAAK,KAAK,gBACpBd,IAAA,CAACH,OAAO;QACNkB,wBAAwB,EAAEX,UAAW;QACrCY,MAAM,EAAE;UAAEC,GAAG,EAAE;QAAkD,CAAE;QACnEC,SAAS,EAAGC,KAAK,IAAK;UACpB,IAAIA,KAAK,CAACC,WAAW,CAACC,IAAI,KAAK,wBAAwB,EAAE;YACvDlB,OAAO,CAAC,CAAC;UACX;QACF;MAAE,CACH,CAAC,gBAEFH,IAAA,CAACH;MACC;MAAA;QACAyB,kBAAkB,EAAE;AAChC;AACA;AACA;AACA,4BAA4BC,IAAI,CAACC,SAAS,CAACpB,UAAU,CAAC;AACtD;AACA;AACA;AACA,gBAAiB;QACLY,MAAM,EAAE;UAAEC,GAAG,EAAE;QAAkD,CAAE;QACnEC,SAAS,EAAGC,KAAK,IAAK;UACpB,IAAIA,KAAK,CAACC,WAAW,CAACC,IAAI,KAAK,wBAAwB,EAAE;YACvDlB,OAAO,CAAC,CAAC;UACX;QACF;MAAE,CACH;IACF,CACG;EAAC,CACF,CAAC;AAEZ,CAAC;AAED,MAAMS,MAAM,GAAGjB,UAAU,CAAC8B,MAAM,CAAC;EAC/BZ,SAAS,EAAE;IACTa,MAAM,EAAE,MAAM;IACdC,KAAK,EAAE,MAAM;IACbC,iBAAiB,EAAE,EAAE;IACrBC,UAAU,EAAE,EAAE;IACdC,aAAa,EAAE;EACjB;AACF,CAAC,CAAC","ignoreList":[]}
1
+ {"version":3,"names":["React","Modal","Platform","StyleSheet","View","WebView","hasJsonWebTokenFormat","getClientKeyPayload","jsx","_jsx","DuffelAssistant","isOpen","onClose","onNewMessage","properties","clientKey","Error","clientKeyPayload","clientKeyIncludesResource","order_id","undefined","booking_id","issueType","console","warn","context","visible","onRequestClose","animationType","children","style","styles","container","OS","injectedJavaScriptObject","source","uri","onMessage","event","nativeEvent","data","parsedData","JSON","parse","type","userId","resourceId","error","injectedJavaScript","stringify","create","height","width","paddingHorizontal","paddingTop","paddingBottom"],"sourceRoot":"../../src","sources":["index.tsx"],"mappings":"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","ignoreList":[]}
@@ -0,0 +1,39 @@
1
+ "use strict";
2
+
3
+ export function getClientKeyPayload(clientKey) {
4
+ if (typeof clientKey !== 'string') {
5
+ return null;
6
+ }
7
+ const payload = clientKey.split('.')[1];
8
+ if (!payload) {
9
+ throw new Error('Malformed client key. Make sure the client key given to props has a valid JWT format.');
10
+ }
11
+ try {
12
+ const decodedPayload = atob(payload);
13
+ const json = JSON.parse(decodedPayload);
14
+ const data = {
15
+ ...(json.exp && {
16
+ exp: json.exp
17
+ }),
18
+ ...(json.user_id && {
19
+ user_id: json.user_id
20
+ }),
21
+ ...(json.live_mode && {
22
+ live_mode: json.live_mode
23
+ }),
24
+ ...(json.organisation_id && {
25
+ organisation_id: json.organisation_id
26
+ }),
27
+ ...(json.order_id && {
28
+ order_id: json.order_id
29
+ }),
30
+ ...(json.booking_id && {
31
+ booking_id: json.booking_id
32
+ })
33
+ };
34
+ return data;
35
+ } catch (error) {
36
+ return null;
37
+ }
38
+ }
39
+ //# sourceMappingURL=getClientKeyPayload.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["getClientKeyPayload","clientKey","payload","split","Error","decodedPayload","atob","json","JSON","parse","data","exp","user_id","live_mode","organisation_id","order_id","booking_id","error"],"sourceRoot":"../../../src","sources":["lib/getClientKeyPayload.ts"],"mappings":";;AAgCA,OAAO,SAASA,mBAAmBA,CACjCC,SAAiB,EACQ;EACzB,IAAI,OAAOA,SAAS,KAAK,QAAQ,EAAE;IACjC,OAAO,IAAI;EACb;EAEA,MAAMC,OAAO,GAAGD,SAAS,CAACE,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;EACvC,IAAI,CAACD,OAAO,EAAE;IACZ,MAAM,IAAIE,KAAK,CACb,uFACF,CAAC;EACH;EAEA,IAAI;IACF,MAAMC,cAAc,GAAGC,IAAI,CAACJ,OAAO,CAAC;IAEpC,MAAMK,IAAI,GAAGC,IAAI,CAACC,KAAK,CAACJ,cAAc,CAAC;IACvC,MAAMK,IAAI,GAAG;MACX,IAAIH,IAAI,CAACI,GAAG,IAAI;QAAEA,GAAG,EAAEJ,IAAI,CAACI;MAAI,CAAC,CAAC;MAClC,IAAIJ,IAAI,CAACK,OAAO,IAAI;QAAEA,OAAO,EAAEL,IAAI,CAACK;MAAQ,CAAC,CAAC;MAC9C,IAAIL,IAAI,CAACM,SAAS,IAAI;QAAEA,SAAS,EAAEN,IAAI,CAACM;MAAU,CAAC,CAAC;MACpD,IAAIN,IAAI,CAACO,eAAe,IAAI;QAC1BA,eAAe,EAAEP,IAAI,CAACO;MACxB,CAAC,CAAC;MACF,IAAIP,IAAI,CAACQ,QAAQ,IAAI;QAAEA,QAAQ,EAAER,IAAI,CAACQ;MAAS,CAAC,CAAC;MACjD,IAAIR,IAAI,CAACS,UAAU,IAAI;QAAEA,UAAU,EAAET,IAAI,CAACS;MAAW,CAAC;IACxD,CAAC;IACD,OAAON,IAAI;EACb,CAAC,CAAC,OAAOO,KAAK,EAAE;IACd,OAAO,IAAI;EACb;AACF","ignoreList":[]}
@@ -1,5 +1,28 @@
1
+ export type SupportIssueType = 'cancellation' | 'change' | 'other';
2
+ export type DuffelAssistantContext = {
3
+ /**
4
+ * A summary of the customer's request for handover to Duffel Support.
5
+ * This will only be displayed to the agent handling the chat.
6
+ *
7
+ * @default undefined
8
+ */
9
+ summary?: string;
10
+ /**
11
+ * When the intent is set to "chat", you may use this value to immediately open a support case for a specific issue type.
12
+ * When no issue type is provided, the user will be able to select the issue type in the chat UI.
13
+ *
14
+ * @default undefined
15
+ */
16
+ issueType?: SupportIssueType;
17
+ };
1
18
  export interface DuffelAssistantProps {
19
+ /**
20
+ * This prop is used to control whether the Assistant is open/visible or closed.
21
+ */
2
22
  isOpen: boolean;
23
+ /**
24
+ * This function callback is called when the user click to close/minimise the Assistant.
25
+ */
3
26
  onClose: () => void;
4
27
  /**
5
28
  * This is used to authenticate requests to the Duffel API.
@@ -21,12 +44,24 @@ export interface DuffelAssistantProps {
21
44
  */
22
45
  environment?: 'staging' | 'production' | 'development';
23
46
  /**
24
- * When the clientKey include both the user and resource IDs, the assistant will open directly on the chat window.
25
- * You may use `issueType` to immediately open a support case with a specific issue type.
26
- * When no issue type is provided, the user will be able to select the issue type in the chat UI.
47
+ * @deprecated Put this in `context.issueType` instead.
48
+ */
49
+ issueType?: SupportIssueType;
50
+ /**
51
+ * This callback is called when a new message is received from the Assistant.
52
+ * You can react to this to notify users that a new message has been received.
53
+ *
54
+ * @param eventPayload - The event payload containing the user ID and resource ID associated with the message.
55
+ */
56
+ onNewMessage?: (eventPayload: {
57
+ userId: string;
58
+ resourceId: string;
59
+ }) => void;
60
+ /**
61
+ * The context of the customer's request for handover to Duffel Support. This is used to pass additional information to the Assistant.
27
62
  *
28
63
  * @default undefined
29
64
  */
30
- issueType?: 'cancellation' | 'change' | 'other';
65
+ context?: DuffelAssistantContext;
31
66
  }
32
67
  //# sourceMappingURL=DuffelAssistantProps.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"DuffelAssistantProps.d.ts","sourceRoot":"","sources":["../../../src/DuffelAssistantProps.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,OAAO,CAAC;IAChB,OAAO,EAAE,MAAM,IAAI,CAAC;IAEpB;;;;;;OAMG;IACH,SAAS,EAAE,MAAM,CAAC;IAElB;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IAEtB;;;;OAIG;IACH,WAAW,CAAC,EAAE,SAAS,GAAG,YAAY,GAAG,aAAa,CAAC;IAEvD;;;;;;OAMG;IACH,SAAS,CAAC,EAAE,cAAc,GAAG,QAAQ,GAAG,OAAO,CAAC;CACjD"}
1
+ {"version":3,"file":"DuffelAssistantProps.d.ts","sourceRoot":"","sources":["../../../src/DuffelAssistantProps.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,gBAAgB,GAAG,cAAc,GAAG,QAAQ,GAAG,OAAO,CAAC;AAEnE,MAAM,MAAM,sBAAsB,GAAG;IACnC;;;;;OAKG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB;;;;;OAKG;IACH,SAAS,CAAC,EAAE,gBAAgB,CAAC;CAC9B,CAAC;AAEF,MAAM,WAAW,oBAAoB;IACnC;;OAEG;IACH,MAAM,EAAE,OAAO,CAAC;IAEhB;;OAEG;IACH,OAAO,EAAE,MAAM,IAAI,CAAC;IAEpB;;;;;;OAMG;IACH,SAAS,EAAE,MAAM,CAAC;IAElB;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IAEtB;;;;OAIG;IACH,WAAW,CAAC,EAAE,SAAS,GAAG,YAAY,GAAG,aAAa,CAAC;IAEvD;;OAEG;IACH,SAAS,CAAC,EAAE,gBAAgB,CAAC;IAE7B;;;;;OAKG;IACH,YAAY,CAAC,EAAE,CAAC,YAAY,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;IAE9E;;;;OAIG;IACH,OAAO,CAAC,EAAE,sBAAsB,CAAC;CAClC"}
@@ -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;AAGnG,MAAM,MAAM,oBAAoB,GAAG,4BAA4B,CAAC;AAEhE,eAAO,MAAM,eAAe,EAAE,KAAK,CAAC,EAAE,CAAC,oBAAoB,CAiD1D,CAAC"}
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;AAEhE,eAAO,MAAM,eAAe,EAAE,KAAK,CAAC,EAAE,CAAC,oBAAoB,CA6G1D,CAAC"}
@@ -0,0 +1,28 @@
1
+ export type ClientKeyPayload = {
2
+ /**
3
+ * When the token expires, in unix time.
4
+ */
5
+ exp: number;
6
+ /**
7
+ * The customer user ID.
8
+ */
9
+ user_id: string;
10
+ /**
11
+ * The live mode flag.
12
+ */
13
+ live_mode: boolean;
14
+ /**
15
+ * The organisation ID.
16
+ */
17
+ organisation_id: string;
18
+ /**
19
+ * The ID of an order when integrating the chat only mode.
20
+ */
21
+ order_id?: string;
22
+ /**
23
+ * The ID of a booking when integrating the chat only mode.
24
+ */
25
+ booking_id?: string;
26
+ };
27
+ export declare function getClientKeyPayload(clientKey: string): ClientKeyPayload | null;
28
+ //# sourceMappingURL=getClientKeyPayload.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"getClientKeyPayload.d.ts","sourceRoot":"","sources":["../../../../src/lib/getClientKeyPayload.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,gBAAgB,GAAG;IAC7B;;OAEG;IACH,GAAG,EAAE,MAAM,CAAC;IAEZ;;OAEG;IACH,OAAO,EAAE,MAAM,CAAC;IAEhB;;OAEG;IACH,SAAS,EAAE,OAAO,CAAC;IAEnB;;OAEG;IACH,eAAe,EAAE,MAAM,CAAC;IAExB;;OAEG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB;;OAEG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,CAAC;AAEF,wBAAgB,mBAAmB,CACjC,SAAS,EAAE,MAAM,GAChB,gBAAgB,GAAG,IAAI,CA8BzB"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@duffel/react-native-components-assistant",
3
- "version": "0.2.0",
3
+ "version": "0.4.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",
@@ -33,7 +33,7 @@
33
33
  ],
34
34
  "scripts": {
35
35
  "example": "yarn workspace duffel-react-native-components-assistant-example",
36
- "test": "jest",
36
+ "test": "jest --passWithNoTests",
37
37
  "typecheck": "tsc",
38
38
  "lint": "eslint \"**/*.{js,ts,tsx}\"",
39
39
  "clean": "del-cli lib",
@@ -76,10 +76,10 @@
76
76
  "eslint-plugin-prettier": "^5.2.3",
77
77
  "jest": "^29.7.0",
78
78
  "prettier": "^3.0.3",
79
- "react": "19.0.0",
80
- "react-native": "0.79.5",
79
+ "react": "19.1.0",
80
+ "react-native": "0.81.5",
81
81
  "react-native-builder-bob": "^0.40.12",
82
- "react-native-webview": "^13.16.0",
82
+ "react-native-webview": "13.15.0",
83
83
  "release-it": "^17.10.0",
84
84
  "typescript": "^5.8.3"
85
85
  },
@@ -152,5 +152,8 @@
152
152
  "languages": "js",
153
153
  "type": "library",
154
154
  "version": "0.51.1"
155
+ },
156
+ "dependencies": {
157
+ "expo": "^54.0.0"
155
158
  }
156
159
  }
@@ -1,5 +1,32 @@
1
+ export type SupportIssueType = 'cancellation' | 'change' | 'other';
2
+
3
+ export type DuffelAssistantContext = {
4
+ /**
5
+ * A summary of the customer's request for handover to Duffel Support.
6
+ * This will only be displayed to the agent handling the chat.
7
+ *
8
+ * @default undefined
9
+ */
10
+ summary?: string;
11
+
12
+ /**
13
+ * When the intent is set to "chat", you may use this value to immediately open a support case for a specific issue type.
14
+ * When no issue type is provided, the user will be able to select the issue type in the chat UI.
15
+ *
16
+ * @default undefined
17
+ */
18
+ issueType?: SupportIssueType;
19
+ };
20
+
1
21
  export interface DuffelAssistantProps {
22
+ /**
23
+ * This prop is used to control whether the Assistant is open/visible or closed.
24
+ */
2
25
  isOpen: boolean;
26
+
27
+ /**
28
+ * This function callback is called when the user click to close/minimise the Assistant.
29
+ */
3
30
  onClose: () => void;
4
31
 
5
32
  /**
@@ -25,11 +52,22 @@ export interface DuffelAssistantProps {
25
52
  environment?: 'staging' | 'production' | 'development';
26
53
 
27
54
  /**
28
- * When the clientKey include both the user and resource IDs, the assistant will open directly on the chat window.
29
- * You may use `issueType` to immediately open a support case with a specific issue type.
30
- * When no issue type is provided, the user will be able to select the issue type in the chat UI.
55
+ * @deprecated Put this in `context.issueType` instead.
56
+ */
57
+ issueType?: SupportIssueType;
58
+
59
+ /**
60
+ * This callback is called when a new message is received from the Assistant.
61
+ * You can react to this to notify users that a new message has been received.
62
+ *
63
+ * @param eventPayload - The event payload containing the user ID and resource ID associated with the message.
64
+ */
65
+ onNewMessage?: (eventPayload: { userId: string; resourceId: string }) => void;
66
+
67
+ /**
68
+ * The context of the customer's request for handover to Duffel Support. This is used to pass additional information to the Assistant.
31
69
  *
32
70
  * @default undefined
33
71
  */
34
- issueType?: 'cancellation' | 'change' | 'other';
72
+ context?: DuffelAssistantContext;
35
73
  }
package/src/index.tsx CHANGED
@@ -3,18 +3,59 @@ import { Modal, Platform, StyleSheet, View } from 'react-native';
3
3
  import { WebView } from 'react-native-webview';
4
4
  import { type DuffelAssistantProps as DuffelAssistantPropsImported } from './DuffelAssistantProps';
5
5
  import { hasJsonWebTokenFormat } from './lib/hasJsonWebTokenFormat';
6
+ import { getClientKeyPayload } from './lib/getClientKeyPayload';
6
7
 
7
8
  export type DuffelAssistantProps = DuffelAssistantPropsImported;
8
9
 
9
10
  export const DuffelAssistant: React.FC<DuffelAssistantProps> = ({
10
11
  isOpen,
11
12
  onClose,
13
+ onNewMessage,
12
14
  ...properties
13
15
  }) => {
14
16
  if (!hasJsonWebTokenFormat(properties.clientKey)) {
15
17
  throw new Error('Duffel Assistant client key format must be a valid JWT.');
16
18
  }
17
19
 
20
+ const clientKeyPayload = getClientKeyPayload(properties.clientKey);
21
+ if (!clientKeyPayload) {
22
+ throw new Error(
23
+ 'The client key property is invalid. A client key must follow the JWT format.'
24
+ );
25
+ }
26
+
27
+ const clientKeyIncludesResource =
28
+ clientKeyPayload.order_id !== undefined ||
29
+ clientKeyPayload.booking_id !== undefined;
30
+
31
+ if (!('user_id' in clientKeyPayload)) {
32
+ throw new Error(
33
+ '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.'
34
+ );
35
+ }
36
+
37
+ if (typeof properties.issueType !== 'undefined') {
38
+ console.warn(
39
+ 'The issueType prop has been deprecated and will no longer be supported in the future. Please use the context prop instead.'
40
+ );
41
+ }
42
+
43
+ if (
44
+ !clientKeyIncludesResource &&
45
+ typeof properties.issueType !== 'undefined'
46
+ ) {
47
+ console.warn(
48
+ 'The issueType prop is only supported when the client key includes a resource id, it will be ignored.'
49
+ );
50
+ delete properties.issueType;
51
+ }
52
+
53
+ if (!clientKeyIncludesResource && properties.context) {
54
+ throw new Error(
55
+ 'The context prop is only supported when the client key includes a resource id.'
56
+ );
57
+ }
58
+
18
59
  return (
19
60
  <Modal
20
61
  visible={isOpen}
@@ -25,11 +66,31 @@ export const DuffelAssistant: React.FC<DuffelAssistantProps> = ({
25
66
  {Platform.OS === 'ios' ? (
26
67
  <WebView
27
68
  injectedJavaScriptObject={properties}
69
+ // When you are running this in development iOS doesn't allow request from hosts with invalid https certificates.
70
+ // If you try ngrok with the local esbuild server it will fail because ngrok requests in https and the dev server rejects http requests.
71
+ // Easiest solution is to use the production URL with test data.
72
+ // If that's not possible, you can also upload the assistant built assets to a different folder in assets.duffel and work with that.
28
73
  source={{ uri: 'https://assets.duffel.com/assistant/iframe.html' }}
29
74
  onMessage={(event) => {
30
75
  if (event.nativeEvent.data === 'duffel-assistant-close') {
31
76
  onClose();
32
77
  }
78
+ try {
79
+ const parsedData = JSON.parse(event.nativeEvent.data);
80
+ if (
81
+ parsedData.type === 'duffel-assistant-new-message' &&
82
+ typeof parsedData.userId === 'string' &&
83
+ typeof parsedData.resourceId === 'string' &&
84
+ onNewMessage !== undefined
85
+ ) {
86
+ onNewMessage({
87
+ userId: parsedData.userId,
88
+ resourceId: parsedData.resourceId,
89
+ });
90
+ }
91
+ } catch (error) {
92
+ // do nothing, invalid JSON
93
+ }
33
94
  }}
34
95
  />
35
96
  ) : (
@@ -0,0 +1,65 @@
1
+ export type ClientKeyPayload = {
2
+ /**
3
+ * When the token expires, in unix time.
4
+ */
5
+ exp: number;
6
+
7
+ /**
8
+ * The customer user ID.
9
+ */
10
+ user_id: string;
11
+
12
+ /**
13
+ * The live mode flag.
14
+ */
15
+ live_mode: boolean;
16
+
17
+ /**
18
+ * The organisation ID.
19
+ */
20
+ organisation_id: string;
21
+
22
+ /**
23
+ * The ID of an order when integrating the chat only mode.
24
+ */
25
+ order_id?: string;
26
+
27
+ /**
28
+ * The ID of a booking when integrating the chat only mode.
29
+ */
30
+ booking_id?: string;
31
+ };
32
+
33
+ export function getClientKeyPayload(
34
+ clientKey: string
35
+ ): ClientKeyPayload | null {
36
+ if (typeof clientKey !== 'string') {
37
+ return null;
38
+ }
39
+
40
+ const payload = clientKey.split('.')[1];
41
+ if (!payload) {
42
+ throw new Error(
43
+ 'Malformed client key. Make sure the client key given to props has a valid JWT format.'
44
+ );
45
+ }
46
+
47
+ try {
48
+ const decodedPayload = atob(payload);
49
+
50
+ const json = JSON.parse(decodedPayload);
51
+ const data = {
52
+ ...(json.exp && { exp: json.exp }),
53
+ ...(json.user_id && { user_id: json.user_id }),
54
+ ...(json.live_mode && { live_mode: json.live_mode }),
55
+ ...(json.organisation_id && {
56
+ organisation_id: json.organisation_id,
57
+ }),
58
+ ...(json.order_id && { order_id: json.order_id }),
59
+ ...(json.booking_id && { booking_id: json.booking_id }),
60
+ };
61
+ return data as ClientKeyPayload;
62
+ } catch (error) {
63
+ return null;
64
+ }
65
+ }