@defra/flood-webchat 0.0.1-alpha.2 → 0.0.1-alpha.4
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/README.md +73 -1
- package/babel.config.cjs +3 -0
- package/dist/templates.js +554 -0
- package/docs/development-guide.md +2 -1
- package/main.scss +743 -2
- package/package.json +14 -4
- package/src/client/index.js +12 -2
- package/src/client/lib/availability.js +75 -0
- package/src/client/lib/config.js +17 -0
- package/src/client/lib/keyboard.js +149 -0
- package/src/client/lib/notification.js +62 -0
- package/src/client/lib/nunjucks.js +3 -0
- package/src/client/lib/panel.js +293 -0
- package/src/client/lib/provider.js +11 -0
- package/src/client/lib/skiplink.js +27 -0
- package/src/client/lib/state.js +82 -0
- package/src/client/lib/transcript.js +34 -0
- package/src/client/lib/utils.js +190 -0
- package/src/client/lib/webchat.js +1073 -0
- package/src/server/client.js +72 -0
- package/src/server/index.js +45 -2
- package/src/server/utils.js +19 -0
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import querystring from 'querystring'
|
|
2
|
+
import axios from 'axios'
|
|
3
|
+
import { isWithinHours, extractTenantId } from './utils.js'
|
|
4
|
+
|
|
5
|
+
export async function authenticate ({ authorisation, accessKey, accessSecret }) {
|
|
6
|
+
const uri = 'https://cxone.niceincontact.com/auth/token'
|
|
7
|
+
|
|
8
|
+
const config = {
|
|
9
|
+
signal: AbortSignal.timeout(3000),
|
|
10
|
+
headers: {
|
|
11
|
+
Host: 'eu1.niceincontact.com',
|
|
12
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
13
|
+
Authorization: authorisation
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const body = querystring.stringify({
|
|
18
|
+
grant_type: 'password',
|
|
19
|
+
username: accessKey,
|
|
20
|
+
password: accessSecret
|
|
21
|
+
})
|
|
22
|
+
// Cache authentication and re-authenticate when needed (lasts 1 hour?)
|
|
23
|
+
const auth = await axios.post(uri, body, config)
|
|
24
|
+
return {
|
|
25
|
+
token: auth.data.access_token,
|
|
26
|
+
tokenType: auth.data.token_type,
|
|
27
|
+
tenantId: extractTenantId(auth.data.id_token)
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export async function getHost ({ tenantId }) {
|
|
32
|
+
const uri = `https://cxone.niceincontact.com/.well-known/cxone-configuration?tenantId=${tenantId}`
|
|
33
|
+
const config = {
|
|
34
|
+
signal: AbortSignal.timeout(3000)
|
|
35
|
+
}
|
|
36
|
+
const api = await axios.get(uri, config)
|
|
37
|
+
return `api-${api.data.area}.niceincontact.com`
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export async function getActivity ({ tokenType, token, host, skillEndpoint, maxQueueCount }) {
|
|
41
|
+
const config = {
|
|
42
|
+
signal: AbortSignal.timeout(3000),
|
|
43
|
+
headers: {
|
|
44
|
+
Host: host,
|
|
45
|
+
Authorization: `${tokenType} ${token}`,
|
|
46
|
+
'Content-Type': 'application/x-www-form-urlencoded'
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
const uri = `https://${host}${skillEndpoint}`
|
|
50
|
+
const skill = await axios.get(uri, config)
|
|
51
|
+
const activity = skill.data.skillActivity[0]
|
|
52
|
+
return {
|
|
53
|
+
hasCapacity: activity.queueCount < maxQueueCount,
|
|
54
|
+
hasAgentsAvailable: activity.agentsAvailable >= 1
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export async function getIsOpen ({ host, token, tokenType, hoursEndpoint }) {
|
|
59
|
+
const config = {
|
|
60
|
+
signal: AbortSignal.timeout(3000),
|
|
61
|
+
headers: {
|
|
62
|
+
Host: 'api-l36.niceincontact.com',
|
|
63
|
+
Authorization: `${tokenType} ${token}`,
|
|
64
|
+
'Content-Type': 'application/x-www-form-urlencoded'
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const uri = `https://${host}${hoursEndpoint}`
|
|
69
|
+
const hours = await axios.get(uri, config)
|
|
70
|
+
const days = hours.data.resultSet.hoursOfOperationProfiles[0].days
|
|
71
|
+
return isWithinHours(days)
|
|
72
|
+
}
|
package/src/server/index.js
CHANGED
|
@@ -1,3 +1,46 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import { authenticate, getHost, getIsOpen, getActivity } from './client.js'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Returns webchat availability
|
|
5
|
+
* @param options {object}
|
|
6
|
+
* @param options.clientId {string}
|
|
7
|
+
* @param options.clientSecret {string}
|
|
8
|
+
* @param options.accessKey {string}
|
|
9
|
+
* @param options.accessSecret {string}
|
|
10
|
+
* @param options.skillEndpoint {string}
|
|
11
|
+
* @param options.hoursEndpoint {string}
|
|
12
|
+
* @param options.maxQueueCount {string}
|
|
13
|
+
* @returns {Promise<{date: Date, availability: (string)}>}
|
|
14
|
+
*/
|
|
15
|
+
export default async function getAvailability ({
|
|
16
|
+
clientId,
|
|
17
|
+
clientSecret,
|
|
18
|
+
accessKey,
|
|
19
|
+
accessSecret,
|
|
20
|
+
skillEndpoint,
|
|
21
|
+
hoursEndpoint,
|
|
22
|
+
maxQueueCount
|
|
23
|
+
}) {
|
|
24
|
+
const authorisation = 'Basic ' + Buffer.from(`${encodeURIComponent(clientId)}:${encodeURIComponent(clientSecret)}`).toString('base64')
|
|
25
|
+
|
|
26
|
+
// Cache authentication and re-authenticate when needed (lasts 1 hour?)
|
|
27
|
+
const { tenantId, token, tokenType } = await authenticate({ authorisation, accessKey, accessSecret })
|
|
28
|
+
const host = await getHost({ tenantId })
|
|
29
|
+
|
|
30
|
+
const [{ hasCapacity, hasAgentsAvailable }, isOpen] = await Promise.all([
|
|
31
|
+
getActivity({ tokenType, token, host, skillEndpoint, maxQueueCount }),
|
|
32
|
+
getIsOpen({ token, tokenType, host, hoursEndpoint })
|
|
33
|
+
])
|
|
34
|
+
|
|
35
|
+
// Hours of operation
|
|
36
|
+
|
|
37
|
+
// Availability
|
|
38
|
+
const isAvailable = isOpen && hasAgentsAvailable && hasCapacity
|
|
39
|
+
const isExistingOnly = isOpen && hasAgentsAvailable && !hasCapacity
|
|
40
|
+
const availability = isAvailable ? 'AVAILABLE' : isExistingOnly ? 'EXISTING' : 'UNAVAILABLE'
|
|
41
|
+
|
|
42
|
+
return {
|
|
43
|
+
date: new Date(),
|
|
44
|
+
availability
|
|
45
|
+
}
|
|
3
46
|
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export const extractTenantId = (token) => {
|
|
2
|
+
const base64Url = token.split('.')[1]
|
|
3
|
+
const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/')
|
|
4
|
+
const jsonPayload = decodeURIComponent(Buffer.from(base64, 'base64').toString('ascii').split('').map(c => {
|
|
5
|
+
return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2)
|
|
6
|
+
}).join(''))
|
|
7
|
+
|
|
8
|
+
return JSON.parse(jsonPayload).tenantId
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const isWithinHours = (days) => {
|
|
12
|
+
const now = new Date()
|
|
13
|
+
const name = now.toLocaleDateString('en-GB', { weekday: 'long' })
|
|
14
|
+
const day = days.find(d => d.day.toLowerCase() === name.toLowerCase())
|
|
15
|
+
const date = now.toLocaleDateString('en-GB').split('/')
|
|
16
|
+
const open = `${date[2]}-${date[1]}-${date[0]}T${day.openTime}`
|
|
17
|
+
const close = `${date[2]}-${date[1]}-${date[0]}T${day.closeTime}`
|
|
18
|
+
return now.getTime() >= Date.parse(open) && now.getTime() <= Date.parse(close)
|
|
19
|
+
}
|