@hellotext/hellotext 2.4.52 → 2.5.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.
- package/README.md +1 -0
- package/dist/hellotext.js +1 -1
- package/index.d.ts +18 -0
- package/lib/api/acks.cjs +2 -1
- package/lib/api/acks.js +6 -2
- package/lib/api/index.cjs +6 -0
- package/lib/api/index.js +6 -0
- package/lib/api/whatsapp_widgets.cjs +87 -0
- package/lib/api/whatsapp_widgets.js +99 -0
- package/lib/core/configuration/whatsapp.cjs +150 -0
- package/lib/core/configuration/whatsapp.js +147 -0
- package/lib/core/configuration.cjs +6 -1
- package/lib/core/configuration.js +5 -0
- package/lib/core/index.cjs +7 -0
- package/lib/core/index.js +1 -0
- package/lib/hellotext.cjs +13 -2
- package/lib/hellotext.js +14 -3
- package/lib/models/index.cjs +8 -1
- package/lib/models/index.js +2 -1
- package/lib/models/session.cjs +17 -2
- package/lib/models/session.js +17 -1
- package/lib/models/webchat.cjs +10 -0
- package/lib/models/webchat.js +10 -0
- package/lib/models/whatsapp_widget.cjs +77 -0
- package/lib/models/whatsapp_widget.js +84 -0
- package/package.json +1 -1
- package/src/api/acks.js +2 -1
- package/src/api/index.js +5 -0
- package/src/api/whatsapp_widgets.js +71 -0
- package/src/core/configuration/whatsapp.js +143 -0
- package/src/core/configuration.js +5 -0
- package/src/core/index.js +1 -0
- package/src/hellotext.js +40 -14
- package/src/models/index.js +1 -0
- package/src/models/session.js +11 -2
- package/src/models/webchat.js +11 -0
- package/src/models/whatsapp_widget.js +68 -0
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {'top-left' | 'top-right' | 'bottom-left' | 'bottom-right'} Placement
|
|
3
|
+
* @description Valid placements for the WhatsApp widget.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @enum {Placement}
|
|
8
|
+
*/
|
|
9
|
+
const placements = {
|
|
10
|
+
TOP_LEFT: 'top-left',
|
|
11
|
+
TOP_RIGHT: 'top-right',
|
|
12
|
+
BOTTOM_LEFT: 'bottom-left',
|
|
13
|
+
BOTTOM_RIGHT: 'bottom-right'
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* @typedef {Object} WhatsAppWidgetAppearance
|
|
18
|
+
* @property {Object} [launcher] - Launcher appearance overrides.
|
|
19
|
+
* @property {string} [launcher.iconUrl] - Image URL used for the launcher icon.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* @class WhatsApp
|
|
24
|
+
* @classdesc Configuration for the standalone WhatsApp widget.
|
|
25
|
+
* @property {String} id - The id of the WhatsApp widget.
|
|
26
|
+
* @property {String} container - The container to append the widget to, defaults to 'body'.
|
|
27
|
+
* @property {Placement} placement - The placement of the widget, defaults to 'bottom-right'.
|
|
28
|
+
* @property {String} number - WhatsApp number used by the widget link.
|
|
29
|
+
* @property {String} body - Prefilled WhatsApp compose text.
|
|
30
|
+
* @property {WhatsAppWidgetAppearance} appearance - Appearance overrides.
|
|
31
|
+
*/
|
|
32
|
+
class WhatsApp {
|
|
33
|
+
static _id
|
|
34
|
+
static _container = 'body'
|
|
35
|
+
static _placement = 'bottom-right'
|
|
36
|
+
static _appearance = {}
|
|
37
|
+
static _number = null
|
|
38
|
+
static _body = null
|
|
39
|
+
|
|
40
|
+
static set id(value) {
|
|
41
|
+
this._id = value
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
static get id() {
|
|
45
|
+
return this._id
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
static set container(value) {
|
|
49
|
+
this._container = value
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
static get container() {
|
|
53
|
+
return this._container
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
static set placement(value) {
|
|
57
|
+
if (!Object.values(placements).includes(value)) {
|
|
58
|
+
throw new Error(`Invalid placement value: ${value}`)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
this._placement = value
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
static get placement() {
|
|
65
|
+
return this._placement
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
static get appearance() {
|
|
69
|
+
return this._appearance
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
static set appearance(value) {
|
|
73
|
+
if (!this.isPlainObject(value)) {
|
|
74
|
+
throw new Error('Appearance must be an object')
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
Object.entries(value).forEach(([key, nestedValue]) => {
|
|
78
|
+
if (key !== 'launcher') {
|
|
79
|
+
throw new Error(`Invalid appearance property: ${key}`)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (!this.isPlainObject(nestedValue)) {
|
|
83
|
+
throw new Error(`Appearance ${key} must be an object`)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
Object.entries(nestedValue).forEach(([nestedKey, propertyValue]) => {
|
|
87
|
+
if (nestedKey !== 'iconUrl') {
|
|
88
|
+
throw new Error(`Invalid appearance launcher property: ${nestedKey}`)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (propertyValue == null) {
|
|
92
|
+
return
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (typeof propertyValue !== 'string') {
|
|
96
|
+
throw new Error(`Invalid appearance ${key}.${nestedKey} value: ${propertyValue}`)
|
|
97
|
+
}
|
|
98
|
+
})
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
this._appearance = value
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
static get number() {
|
|
105
|
+
return this._number
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
static set number(value) {
|
|
109
|
+
if (value != null && typeof value !== 'string') {
|
|
110
|
+
throw new Error(`Invalid number value: ${value}`)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
this._number = value
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
static get body() {
|
|
117
|
+
return this._body
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
static set body(value) {
|
|
121
|
+
if (value != null && typeof value !== 'string') {
|
|
122
|
+
throw new Error(`Invalid body value: ${value}`)
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
this._body = value
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
static assign(props) {
|
|
129
|
+
if (props) {
|
|
130
|
+
Object.entries(props).forEach(([key, value]) => {
|
|
131
|
+
this[key] = value
|
|
132
|
+
})
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return this
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
static isPlainObject(value) {
|
|
139
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value)
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export { WhatsApp }
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Forms } from './configuration/forms'
|
|
2
2
|
import { Locale } from './configuration/locale'
|
|
3
3
|
import { Webchat } from './configuration/webchat'
|
|
4
|
+
import { WhatsApp } from './configuration/whatsapp'
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* @class Configuration
|
|
@@ -10,6 +11,7 @@ import { Webchat } from './configuration/webchat'
|
|
|
10
11
|
* @property {String} [session] - session id
|
|
11
12
|
* @property {Forms} [forms] - form configuration
|
|
12
13
|
* @property {Webchat} [webchat] - webchat configuration
|
|
14
|
+
* @property {WhatsApp} [whatsappWidget] - WhatsApp widget configuration
|
|
13
15
|
* @property {Locale} [locale] - locale configuration
|
|
14
16
|
*/
|
|
15
17
|
class Configuration {
|
|
@@ -21,6 +23,7 @@ class Configuration {
|
|
|
21
23
|
|
|
22
24
|
static forms = Forms
|
|
23
25
|
static webchat = Webchat
|
|
26
|
+
static whatsapp = WhatsApp
|
|
24
27
|
|
|
25
28
|
/**
|
|
26
29
|
*
|
|
@@ -42,6 +45,8 @@ class Configuration {
|
|
|
42
45
|
this.forms = Forms.assign(value)
|
|
43
46
|
} else if (key === 'webchat') {
|
|
44
47
|
this.webchat = Webchat.assign(value)
|
|
48
|
+
} else if (key === 'whatsappWidget') {
|
|
49
|
+
this.whatsapp = WhatsApp.assign(value)
|
|
45
50
|
} else {
|
|
46
51
|
this[key] = value
|
|
47
52
|
}
|
package/src/core/index.js
CHANGED
package/src/hellotext.js
CHANGED
|
@@ -1,7 +1,17 @@
|
|
|
1
1
|
import { Configuration, Event } from './core'
|
|
2
2
|
|
|
3
3
|
import API, { Response, keepaliveFor } from './api'
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
Business,
|
|
6
|
+
Fingerprint,
|
|
7
|
+
FormCollection,
|
|
8
|
+
Page,
|
|
9
|
+
Query,
|
|
10
|
+
Session,
|
|
11
|
+
User,
|
|
12
|
+
Webchat,
|
|
13
|
+
WhatsAppWidget,
|
|
14
|
+
} from './models'
|
|
5
15
|
|
|
6
16
|
import { NotInitializedError } from './errors'
|
|
7
17
|
|
|
@@ -10,6 +20,7 @@ class Hellotext {
|
|
|
10
20
|
static forms
|
|
11
21
|
static business
|
|
12
22
|
static webchat
|
|
23
|
+
static whatsapp
|
|
13
24
|
|
|
14
25
|
/**
|
|
15
26
|
* initialize the module.
|
|
@@ -18,25 +29,35 @@ class Hellotext {
|
|
|
18
29
|
*/
|
|
19
30
|
static async initialize(business, config = {}) {
|
|
20
31
|
this.business = new Business(business)
|
|
32
|
+
this.page = new Page()
|
|
21
33
|
|
|
22
34
|
Configuration.assign(config)
|
|
23
|
-
Session.initialize()
|
|
35
|
+
Session.initialize(this.page)
|
|
24
36
|
|
|
25
|
-
this.page = new Page()
|
|
26
37
|
this.forms = new FormCollection()
|
|
27
38
|
|
|
28
39
|
this.query = new Query()
|
|
29
40
|
|
|
30
41
|
const businessData = await this.business.hydrate()
|
|
31
|
-
const webchatConfig =
|
|
32
|
-
false
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
42
|
+
const webchatConfig =
|
|
43
|
+
config.webchat === false
|
|
44
|
+
? false
|
|
45
|
+
: this.mergeWebchatConfig(
|
|
46
|
+
(businessData && businessData.webchat) || {},
|
|
47
|
+
config.webchat || {},
|
|
48
|
+
)
|
|
49
|
+
const whatsappConfig =
|
|
50
|
+
config.whatsappWidget === false
|
|
51
|
+
? false
|
|
52
|
+
: this.mergeWhatsAppConfig(
|
|
53
|
+
(businessData && businessData.whatsapp) || {},
|
|
54
|
+
config.whatsappWidget || {},
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
const hasExplicitBehaviourOverride =
|
|
36
58
|
config.webchat &&
|
|
37
59
|
config.webchat !== false &&
|
|
38
60
|
Object.prototype.hasOwnProperty.call(config.webchat, 'behaviour')
|
|
39
|
-
)
|
|
40
61
|
Configuration.webchat.behaviourOverride = hasExplicitBehaviourOverride
|
|
41
62
|
|
|
42
63
|
if (webchatConfig && webchatConfig.id) {
|
|
@@ -44,6 +65,11 @@ class Hellotext {
|
|
|
44
65
|
this.webchat = await Webchat.load(webchatConfig.id)
|
|
45
66
|
}
|
|
46
67
|
|
|
68
|
+
if (whatsappConfig && whatsappConfig.id) {
|
|
69
|
+
Configuration.whatsapp.assign(whatsappConfig)
|
|
70
|
+
this.whatsapp = await WhatsAppWidget.load(whatsappConfig.id)
|
|
71
|
+
}
|
|
72
|
+
|
|
47
73
|
if (typeof MutationObserver !== 'undefined') {
|
|
48
74
|
this.forms.collectExistingFormsOnPage()
|
|
49
75
|
}
|
|
@@ -53,6 +79,10 @@ class Hellotext {
|
|
|
53
79
|
return this.deepMergePlainObjects(dashboardConfig, localConfig)
|
|
54
80
|
}
|
|
55
81
|
|
|
82
|
+
static mergeWhatsAppConfig(dashboardConfig, localConfig) {
|
|
83
|
+
return this.deepMergePlainObjects(dashboardConfig, localConfig)
|
|
84
|
+
}
|
|
85
|
+
|
|
56
86
|
static deepMergePlainObjects(base, override) {
|
|
57
87
|
const result = { ...base }
|
|
58
88
|
|
|
@@ -147,11 +177,7 @@ class Hellotext {
|
|
|
147
177
|
})
|
|
148
178
|
|
|
149
179
|
if (response.succeeded) {
|
|
150
|
-
User.remember(
|
|
151
|
-
externalId,
|
|
152
|
-
options.source,
|
|
153
|
-
fingerprint,
|
|
154
|
-
)
|
|
180
|
+
User.remember(externalId, options.source, fingerprint)
|
|
155
181
|
}
|
|
156
182
|
|
|
157
183
|
return response
|
package/src/models/index.js
CHANGED
package/src/models/session.js
CHANGED
|
@@ -1,16 +1,24 @@
|
|
|
1
1
|
import { Configuration } from '../core'
|
|
2
2
|
import { Cookies } from './cookies'
|
|
3
|
+
import { Page } from './page'
|
|
3
4
|
import { Query } from './query'
|
|
4
5
|
import API from '../api'
|
|
5
6
|
|
|
6
7
|
class Session {
|
|
7
8
|
static #session
|
|
8
9
|
static #query
|
|
10
|
+
static #page
|
|
9
11
|
|
|
10
12
|
static get session() {
|
|
11
13
|
return this.#session
|
|
12
14
|
}
|
|
13
15
|
|
|
16
|
+
static get ackPayload() {
|
|
17
|
+
return {
|
|
18
|
+
utm_params: this.#page?.utmParams || {},
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
14
22
|
static set session(value) {
|
|
15
23
|
const oldSession = Cookies.get('hello_session')
|
|
16
24
|
|
|
@@ -22,14 +30,15 @@ class Session {
|
|
|
22
30
|
}
|
|
23
31
|
|
|
24
32
|
if (!Cookies.get('hello_session_ack_at')) {
|
|
25
|
-
API.acks.send()
|
|
33
|
+
API.acks.send(this.ackPayload)
|
|
26
34
|
Cookies.set('hello_session_ack_at', new Date().toISOString())
|
|
27
35
|
}
|
|
28
36
|
|
|
29
37
|
return this.#session
|
|
30
38
|
}
|
|
31
39
|
|
|
32
|
-
static initialize() {
|
|
40
|
+
static initialize(page = new Page()) {
|
|
41
|
+
this.#page = page
|
|
33
42
|
this.#query = new Query()
|
|
34
43
|
|
|
35
44
|
this.session = this.#query.session || Configuration.session || Cookies.get('hello_session')
|
package/src/models/webchat.js
CHANGED
|
@@ -30,6 +30,7 @@ class Webchat {
|
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
this.containerToAppendTo.appendChild(this.data.html)
|
|
33
|
+
this.markCoexistingWidgets()
|
|
33
34
|
this.mounted = true
|
|
34
35
|
|
|
35
36
|
return true
|
|
@@ -69,6 +70,16 @@ class Webchat {
|
|
|
69
70
|
get stylesheetLoaded() {
|
|
70
71
|
return Business.waitForStylesheet(Business.latestStylesheet)
|
|
71
72
|
}
|
|
73
|
+
|
|
74
|
+
markCoexistingWidgets() {
|
|
75
|
+
const webchat = this.data.html
|
|
76
|
+
const whatsapp = document.querySelector('.hellotext--whatsapp-widget')
|
|
77
|
+
|
|
78
|
+
if (!webchat || !whatsapp) return
|
|
79
|
+
|
|
80
|
+
webchat.classList.add('hellotext--with-whatsapp-widget')
|
|
81
|
+
whatsapp.classList.add('hellotext--with-webchat')
|
|
82
|
+
}
|
|
72
83
|
}
|
|
73
84
|
|
|
74
85
|
export { Webchat }
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { Configuration } from '../core'
|
|
2
|
+
|
|
3
|
+
import API from '../api'
|
|
4
|
+
import { Business } from './business'
|
|
5
|
+
|
|
6
|
+
class WhatsAppWidget {
|
|
7
|
+
static async load(id) {
|
|
8
|
+
const widget = new WhatsAppWidget({
|
|
9
|
+
id,
|
|
10
|
+
html: await API.whatsappWidgets.get(id)
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
widget.rendered = widget.render()
|
|
14
|
+
|
|
15
|
+
return widget
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
constructor(data) {
|
|
19
|
+
this.data = data
|
|
20
|
+
this.mounted = false
|
|
21
|
+
this.rendered = Promise.resolve(false)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async render() {
|
|
25
|
+
if (!this.data.html) return false
|
|
26
|
+
|
|
27
|
+
const container = this.containerToAppendTo
|
|
28
|
+
if (!container) {
|
|
29
|
+
console.warn(`Hellotext WhatsApp widget was not mounted because the container ${Configuration.whatsapp.container} was not found.`)
|
|
30
|
+
return false
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (!await this.stylesheetLoaded) {
|
|
34
|
+
console.warn('Hellotext WhatsApp widget was not mounted because its stylesheet failed to load.')
|
|
35
|
+
return false
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
container.appendChild(this.data.html)
|
|
39
|
+
this.markCoexistingWidgets()
|
|
40
|
+
this.mounted = true
|
|
41
|
+
|
|
42
|
+
return true
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
get containerToAppendTo() {
|
|
46
|
+
try {
|
|
47
|
+
return document.querySelector(Configuration.whatsapp.container)
|
|
48
|
+
} catch (_) {
|
|
49
|
+
return null
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
get stylesheetLoaded() {
|
|
54
|
+
return Business.waitForStylesheet(Business.latestStylesheet)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
markCoexistingWidgets() {
|
|
58
|
+
const webchat = document.querySelector('.hellotext--webchat:not(.hellotext--whatsapp-widget)')
|
|
59
|
+
const whatsapp = this.data.html
|
|
60
|
+
|
|
61
|
+
if (!webchat || !whatsapp) return
|
|
62
|
+
|
|
63
|
+
webchat.classList.add('hellotext--with-whatsapp-widget')
|
|
64
|
+
whatsapp.classList.add('hellotext--with-webchat')
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export { WhatsAppWidget }
|