@defra/flood-webchat 0.0.1-beta.47 → 0.0.1-beta.49

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/server.js CHANGED
@@ -1,303 +1 @@
1
- /******/ (() => { // webpackBootstrap
2
- /******/ var __webpack_modules__ = ({
3
-
4
- /***/ "./src/server/index.js":
5
- /*!*****************************!*\
6
- !*** ./src/server/index.js ***!
7
- \*****************************/
8
- /***/ ((module, __unused_webpack_exports, __webpack_require__) => {
9
-
10
- const {
11
- authenticate,
12
- getApiBaseUrl,
13
- getIsOpen,
14
- getActivity
15
- } = __webpack_require__(/*! ./lib/client.js */ "./src/server/lib/client.js");
16
-
17
- /**
18
- * Returns webchat availability
19
- * @param options {object}
20
- * @param options.clientId {string}
21
- * @param options.clientSecret {string}
22
- * @param options.accessKey {string}
23
- * @param options.accessSecret {string}
24
- * @param options.skillEndpoint {string}
25
- * @param options.hoursEndpoint {string}
26
- * @param options.authenticationUri {string}
27
- * @param options.wellKnownUri {string}
28
- * @param options.maxQueueCount {string}
29
- * @returns {Promise<{date: Date, availability: (string)}>}
30
- */
31
- module.exports = async function getAvailability({
32
- clientId,
33
- clientSecret,
34
- accessKey,
35
- accessSecret,
36
- maxQueueCount,
37
- skillEndpoint,
38
- hoursEndpoint,
39
- wellKnownUri = 'https://cxone.niceincontact.com/.well-known/cxone-configuration',
40
- authenticationUri = 'https://cxone.niceincontact.com/auth/token'
41
- }) {
42
- const authorisation = 'Basic ' + Buffer.from(`${encodeURIComponent(clientId)}:${encodeURIComponent(clientSecret)}`).toString('base64');
43
-
44
- // Cache authentication and re-authenticate when needed (lasts 1 hour?)
45
- const {
46
- tenantId,
47
- token,
48
- tokenType
49
- } = await authenticate({
50
- authenticationUri,
51
- authorisation,
52
- accessKey,
53
- accessSecret
54
- });
55
- const apiBaseUrl = await getApiBaseUrl({
56
- wellKnownUri,
57
- tenantId
58
- });
59
- const [{
60
- hasCapacity,
61
- hasAgentsAvailable
62
- }, isOpen] = await Promise.all([getActivity({
63
- baseUrl: apiBaseUrl,
64
- tokenType,
65
- token,
66
- skillEndpoint,
67
- maxQueueCount
68
- }), getIsOpen({
69
- baseUrl: apiBaseUrl,
70
- token,
71
- tokenType,
72
- hoursEndpoint
73
- })]);
74
- const isAvailable = isOpen && hasAgentsAvailable && hasCapacity;
75
- const isExistingOnly = isOpen && hasAgentsAvailable && !hasCapacity;
76
- let availability = 'UNAVAILABLE';
77
- if (isAvailable) {
78
- availability = 'AVAILABLE';
79
- } else if (isExistingOnly) {
80
- availability = 'EXISTING';
81
- }
82
- return {
83
- date: new Date(),
84
- availability
85
- };
86
- };
87
-
88
- /***/ }),
89
-
90
- /***/ "./src/server/lib/client.js":
91
- /*!**********************************!*\
92
- !*** ./src/server/lib/client.js ***!
93
- \**********************************/
94
- /***/ ((module, __unused_webpack_exports, __webpack_require__) => {
95
-
96
- const querystring = __webpack_require__(/*! querystring */ "querystring");
97
- const axios = __webpack_require__(/*! axios */ "axios");
98
- const jwtdecode = __webpack_require__(/*! jwt-decode */ "jwt-decode");
99
- const {
100
- isWithinHours
101
- } = __webpack_require__(/*! ./utils.js */ "./src/server/lib/utils.js");
102
- const contentType = 'application/x-www-form-urlencoded';
103
- const authenticate = async ({
104
- authenticationUri,
105
- authorisation,
106
- accessKey,
107
- accessSecret
108
- }) => {
109
- const url = new URL(authenticationUri);
110
- const body = querystring.stringify({
111
- grant_type: 'password',
112
- username: accessKey,
113
- password: accessSecret
114
- });
115
- const auth = await axios.post(url.href, body, {
116
- signal: AbortSignal.timeout(3000),
117
- headers: {
118
- Host: url.host,
119
- 'Content-Type': contentType,
120
- Authorization: authorisation
121
- }
122
- });
123
- return {
124
- token: auth.data.access_token,
125
- tokenType: auth.data.token_type,
126
- tenantId: jwtdecode(auth.data.id_token)?.tenantId
127
- };
128
- };
129
- const getApiBaseUrl = async ({
130
- wellKnownUri,
131
- tenantId
132
- }) => {
133
- const url = new URL(wellKnownUri);
134
- url.searchParams.set('tenantId', tenantId);
135
- const {
136
- data
137
- } = await axios.get(url.href, {
138
- headers: {
139
- Host: url.host
140
- },
141
- signal: AbortSignal.timeout(3000)
142
- });
143
- return data.api_endpoint;
144
- };
145
- const getActivity = async ({
146
- tokenType,
147
- token,
148
- baseUrl,
149
- skillEndpoint,
150
- maxQueueCount
151
- }) => {
152
- const url = new URL(skillEndpoint, baseUrl);
153
- const skill = await axios.get(url.href, {
154
- signal: AbortSignal.timeout(3000),
155
- headers: {
156
- Host: url.host,
157
- Authorization: `${tokenType} ${token}`,
158
- 'Content-Type': contentType
159
- }
160
- });
161
- const activity = skill.data.skillActivity[0];
162
- return {
163
- hasCapacity: activity.queueCount < maxQueueCount,
164
- hasAgentsAvailable: activity.agentsAvailable >= 1
165
- };
166
- };
167
- const getIsOpen = async ({
168
- baseUrl,
169
- token,
170
- tokenType,
171
- hoursEndpoint
172
- }) => {
173
- const url = new URL(hoursEndpoint, baseUrl);
174
- const hours = await axios.get(url.href, {
175
- signal: AbortSignal.timeout(3000),
176
- headers: {
177
- Host: url.host,
178
- Authorization: `${tokenType} ${token}`,
179
- 'Content-Type': contentType
180
- }
181
- });
182
- const days = hours.data.resultSet.hoursOfOperationProfiles[0].days;
183
- return isWithinHours(days);
184
- };
185
- module.exports = {
186
- authenticate,
187
- getApiBaseUrl,
188
- getIsOpen,
189
- getActivity
190
- };
191
-
192
- /***/ }),
193
-
194
- /***/ "./src/server/lib/utils.js":
195
- /*!*********************************!*\
196
- !*** ./src/server/lib/utils.js ***!
197
- \*********************************/
198
- /***/ ((module, __unused_webpack_exports, __webpack_require__) => {
199
-
200
- const {
201
- DateTime
202
- } = __webpack_require__(/*! luxon */ "luxon");
203
- const getHour = time => Number(time.split(':')[0]);
204
- const isWithinHours = (days, date) => {
205
- const now = date ? DateTime.fromISO(date) : DateTime.local();
206
- now.setZone('Europe/London');
207
- const newDate = date ? new Date(date) : new Date();
208
- const today = newDate.toLocaleDateString('en-GB', {
209
- weekday: 'long'
210
- });
211
- const dateParts = newDate.toLocaleDateString('en-GB').split('/');
212
- const todaysAvailability = days.find(item => item.day === today);
213
- const todaysDateTimeOpen = DateTime.local(Number(dateParts[2]), Number(dateParts[1]), Number(dateParts[0]), getHour(todaysAvailability.openTime));
214
- const todaysDateTimeClose = DateTime.local(Number(dateParts[2]), Number(dateParts[1]), Number(dateParts[0]), getHour(todaysAvailability.closeTime));
215
- return now.diff(todaysDateTimeOpen).milliseconds >= 0 && now.diff(todaysDateTimeClose).milliseconds <= 0;
216
- };
217
- module.exports = {
218
- isWithinHours
219
- };
220
-
221
- /***/ }),
222
-
223
- /***/ "axios":
224
- /*!************************!*\
225
- !*** external "axios" ***!
226
- \************************/
227
- /***/ ((module) => {
228
-
229
- "use strict";
230
- module.exports = require("axios");
231
-
232
- /***/ }),
233
-
234
- /***/ "jwt-decode":
235
- /*!*****************************!*\
236
- !*** external "jwt-decode" ***!
237
- \*****************************/
238
- /***/ ((module) => {
239
-
240
- "use strict";
241
- module.exports = require("jwt-decode");
242
-
243
- /***/ }),
244
-
245
- /***/ "luxon":
246
- /*!************************!*\
247
- !*** external "luxon" ***!
248
- \************************/
249
- /***/ ((module) => {
250
-
251
- "use strict";
252
- module.exports = require("luxon");
253
-
254
- /***/ }),
255
-
256
- /***/ "querystring":
257
- /*!******************************!*\
258
- !*** external "querystring" ***!
259
- \******************************/
260
- /***/ ((module) => {
261
-
262
- "use strict";
263
- module.exports = require("querystring");
264
-
265
- /***/ })
266
-
267
- /******/ });
268
- /************************************************************************/
269
- /******/ // The module cache
270
- /******/ var __webpack_module_cache__ = {};
271
- /******/
272
- /******/ // The require function
273
- /******/ function __webpack_require__(moduleId) {
274
- /******/ // Check if module is in cache
275
- /******/ var cachedModule = __webpack_module_cache__[moduleId];
276
- /******/ if (cachedModule !== undefined) {
277
- /******/ return cachedModule.exports;
278
- /******/ }
279
- /******/ // Create a new module (and put it into the cache)
280
- /******/ var module = __webpack_module_cache__[moduleId] = {
281
- /******/ // no module.id needed
282
- /******/ // no module.loaded needed
283
- /******/ exports: {}
284
- /******/ };
285
- /******/
286
- /******/ // Execute the module function
287
- /******/ __webpack_modules__[moduleId](module, module.exports, __webpack_require__);
288
- /******/
289
- /******/ // Return the exports of the module
290
- /******/ return module.exports;
291
- /******/ }
292
- /******/
293
- /************************************************************************/
294
- /******/
295
- /******/ // startup
296
- /******/ // Load entry module and return exports
297
- /******/ // This entry module is referenced by other modules so it can't be inlined
298
- /******/ var __webpack_exports__ = __webpack_require__("./src/server/index.js");
299
- /******/ module.exports = __webpack_exports__;
300
- /******/
301
- /******/ })()
302
- ;
303
- //# sourceMappingURL=server.js.map
1
+ (()=>{var e={553:(e,t,n)=>{const{authenticate:o,getApiBaseUrl:a,getIsOpen:i,getActivity:s}=n(980);e.exports=async function({clientId:e,clientSecret:t,accessKey:n,accessSecret:r,maxQueueCount:c,skillEndpoint:u,hoursEndpoint:l,wellKnownUri:p="https://cxone.niceincontact.com/.well-known/cxone-configuration",authenticationUri:d="https://cxone.niceincontact.com/auth/token"}){const h="Basic "+Buffer.from(`${encodeURIComponent(e)}:${encodeURIComponent(t)}`).toString("base64"),{tenantId:m,token:y,tokenType:w}=await o({authenticationUri:d,authorisation:h,accessKey:n,accessSecret:r}),g=await a({wellKnownUri:p,tenantId:m}),[{hasCapacity:A,hasAgentsAvailable:k},f]=await Promise.all([s({baseUrl:g,tokenType:w,token:y,skillEndpoint:u,maxQueueCount:c}),i({baseUrl:g,token:y,tokenType:w,hoursEndpoint:l})]);let x="UNAVAILABLE";return f&&k&&A?x="AVAILABLE":f&&k&&!A&&(x="EXISTING"),{date:new Date,availability:x}}},980:(e,t,n)=>{const o=n(819),a=n(167),i=n(567),{isWithinHours:s}=n(393),r="application/x-www-form-urlencoded";e.exports={authenticate:async({authenticationUri:e,authorisation:t,accessKey:n,accessSecret:s})=>{const c=new URL(e),u=o.stringify({grant_type:"password",username:n,password:s}),l=await a.post(c.href,u,{signal:AbortSignal.timeout(3e3),headers:{Host:c.host,"Content-Type":r,Authorization:t}});return{token:l.data.access_token,tokenType:l.data.token_type,tenantId:i(l.data.id_token)?.tenantId}},getApiBaseUrl:async({wellKnownUri:e,tenantId:t})=>{const n=new URL(e);n.searchParams.set("tenantId",t);const{data:o}=await a.get(n.href,{headers:{Host:n.host},signal:AbortSignal.timeout(3e3)});return o.api_endpoint},getIsOpen:async({baseUrl:e,token:t,tokenType:n,hoursEndpoint:o})=>{const i=new URL(o,e),c=(await a.get(i.href,{signal:AbortSignal.timeout(3e3),headers:{Host:i.host,Authorization:`${n} ${t}`,"Content-Type":r}})).data.resultSet.hoursOfOperationProfiles[0].days;return s(c)},getActivity:async({tokenType:e,token:t,baseUrl:n,skillEndpoint:o,maxQueueCount:i})=>{const s=new URL(o,n),c=(await a.get(s.href,{signal:AbortSignal.timeout(3e3),headers:{Host:s.host,Authorization:`${e} ${t}`,"Content-Type":r}})).data.skillActivity[0];return{hasCapacity:c.queueCount<i,hasAgentsAvailable:c.agentsAvailable>=1}}}},393:(e,t,n)=>{const{DateTime:o}=n(748),a=e=>Number(e.split(":")[0]);e.exports={isWithinHours:(e,t)=>{const n=t?o.fromISO(t):o.local();n.setZone("Europe/London");const i=t?new Date(t):new Date,s=i.toLocaleDateString("en-GB",{weekday:"long"}),r=i.toLocaleDateString("en-GB").split("/"),c=e.find((e=>e.day===s)),u=o.local(Number(r[2]),Number(r[1]),Number(r[0]),a(c.openTime)),l=o.local(Number(r[2]),Number(r[1]),Number(r[0]),a(c.closeTime));return n.diff(u).milliseconds>=0&&n.diff(l).milliseconds<=0}}},167:e=>{"use strict";e.exports=require("axios")},567:e=>{"use strict";e.exports=require("jwt-decode")},748:e=>{"use strict";e.exports=require("luxon")},819:e=>{"use strict";e.exports=require("querystring")}},t={},n=function n(o){var a=t[o];if(void 0!==a)return a.exports;var i=t[o]={exports:{}};return e[o](i,i.exports,n),i.exports}(553);module.exports=n})();
package/package.json CHANGED
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "@defra/flood-webchat",
3
- "version": "0.0.1-beta.47",
3
+ "version": "0.0.1-beta.49",
4
4
  "description": "",
5
5
  "main": "dist/server.js",
6
6
  "browser": "dist/client.js",
7
7
  "scripts": {
8
8
  "test": "npm run lint && npm run unit-test",
9
9
  "dev": "(cd demo && webpack server)",
10
- "build": "webpack",
10
+ "build": "webpack --config webpack.prod.mjs",
11
11
  "unit-test": "jest",
12
12
  "lint": "npm run lint:js && npm run lint:scss",
13
13
  "lint:fix": "npm run lint:js -- --fix && npm run lint:scss -- --fix",
@@ -53,6 +53,7 @@
53
53
  "webpack": "^5.88.2",
54
54
  "webpack-cli": "^5.1.4",
55
55
  "webpack-dev-server": "^4.15.1",
56
+ "webpack-merge": "^5.10.0",
56
57
  "webpack-node-externals": "^3.0.0"
57
58
  },
58
59
  "peerDependencies": {
@@ -30,7 +30,7 @@ export function Feedback ({ onCancel }) {
30
30
 
31
31
  <div className='wc-body'>
32
32
  <div className='wc-content'>
33
- <h3 id='wc-subtitle' className='wc-heading'>Give Feedback on Floodline webchat</h3>
33
+ <h3 id='wc-subtitle' className='wc-heading'>Give feedback on Floodline webchat</h3>
34
34
  <p>We're running webchat as a trial. you can&nbsp;
35
35
  <a
36
36
  id='feedback-send'
@@ -5,6 +5,7 @@ import { Availability } from './components/availability/availability.jsx'
5
5
  import { checkAvailability } from './lib/check-availability'
6
6
  import { AppProvider } from './store/AppProvider.jsx'
7
7
  import { CUSTOMER_ID_STORAGE_KEY } from './store/constants.js'
8
+ import { messageNotification } from './lib/message-notification.js'
8
9
 
9
10
  export async function init (container, options) {
10
11
  const sdk = new ChatSdk({
@@ -16,14 +17,16 @@ export async function init (container, options) {
16
17
 
17
18
  const root = createRoot(container)
18
19
  let availability
20
+ let playSound
19
21
  try {
20
22
  const result = await checkAvailability(options.availabilityEndpoint)
23
+ playSound = await messageNotification(options.audioUrl)
21
24
  availability = result.availability
22
25
  } catch (e) {
23
26
  availability = 'UNAVAILABLE'
24
27
  }
25
28
  root.render(
26
- <AppProvider sdk={sdk} availability={availability} options={options}>
29
+ <AppProvider sdk={sdk} availability={availability} playSound={playSound}>
27
30
  <Availability />
28
31
  </AppProvider>
29
32
  )
@@ -1,6 +1,4 @@
1
- export const messageNotification = audioUrl => {
2
- let buffer
3
-
1
+ export const messageNotification = async audioUrl => {
4
2
  const context = new (window.AudioContext || window.webkitAudioContext)()
5
3
 
6
4
  if (context.state === 'suspended') {
@@ -19,13 +17,9 @@ export const messageNotification = audioUrl => {
19
17
  })
20
18
  }
21
19
 
22
- fetch(audioUrl)
23
- .then(response => response.arrayBuffer())
24
- .then(data => context.decodeAudioData(data))
25
- .then(decodedData => {
26
- buffer = decodedData
27
- })
28
- .catch(console.error)
20
+ const response = await fetch(audioUrl)
21
+ const arrayBuffer = await response.arrayBuffer()
22
+ const buffer = await context.decodeAudioData(arrayBuffer)
29
23
 
30
24
  return () => {
31
25
  const source = context.createBufferSource()
@@ -1,18 +1,14 @@
1
1
  import React, { createContext, useEffect, useReducer, useMemo } from 'react'
2
2
  import { ChatEvent } from '@nice-devone/nice-cxone-chat-web-sdk'
3
3
 
4
- import { messageNotification } from '../lib/message-notification.js'
5
-
6
4
  import { initialState, reducer } from './reducer.js'
7
5
  import { CUSTOMER_ID_STORAGE_KEY, THREAD_ID_STORAGE_KEY, SETTINGS_STORAGE_KEY } from './constants.js'
8
6
 
9
7
  export const AppContext = createContext(initialState)
10
8
 
11
- export const AppProvider = ({ sdk, availability, options, children }) => {
9
+ export const AppProvider = ({ sdk, availability, playSound, children }) => {
12
10
  const [state, dispatch] = useReducer(reducer, initialState)
13
11
 
14
- const playSound = messageNotification(options.audioUrl)
15
-
16
12
  /**
17
13
  * SDK event handlers
18
14
  */
@@ -43,7 +39,7 @@ export const AppProvider = ({ sdk, availability, options, children }) => {
43
39
 
44
40
  const isAudioOn = JSON.parse(window.localStorage.getItem(SETTINGS_STORAGE_KEY)).audio
45
41
 
46
- if (isAudioOn && e.detail.data.message.direction === 'outbound') {
42
+ if (isAudioOn && e.detail.data.message.direction === 'outbound' && playSound) {
47
43
  playSound()
48
44
  }
49
45
  }
@@ -9,8 +9,6 @@ export default {
9
9
  client: path.join(__dirname, 'src/client/index.jsx'),
10
10
  server: path.join(__dirname, 'src/server/index.js')
11
11
  },
12
- devtool: 'source-map',
13
- mode: 'development',
14
12
  output: {
15
13
  path: path.resolve(__dirname, 'dist'),
16
14
  library: {
@@ -0,0 +1,7 @@
1
+ import { merge } from 'webpack-merge'
2
+
3
+ import common from './webpack.config.mjs'
4
+
5
+ export default merge(common, {
6
+ mode: 'production'
7
+ })