@hellotext/hellotext 2.1.3 → 2.1.5

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.
@@ -0,0 +1,41 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.UTM = void 0;
7
+ var _cookies = require("./cookies");
8
+ function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
9
+ function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, _toPropertyKey(descriptor.key), descriptor); } }
10
+ function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); Object.defineProperty(Constructor, "prototype", { writable: false }); return Constructor; }
11
+ function _toPropertyKey(arg) { var key = _toPrimitive(arg, "string"); return typeof key === "symbol" ? key : String(key); }
12
+ function _toPrimitive(input, hint) { if (typeof input !== "object" || input === null) return input; var prim = input[Symbol.toPrimitive]; if (prim !== undefined) { var res = prim.call(input, hint || "default"); if (typeof res !== "object") return res; throw new TypeError("@@toPrimitive must return a primitive value."); } return (hint === "string" ? String : Number)(input); }
13
+ let UTM = /*#__PURE__*/function () {
14
+ function UTM() {
15
+ _classCallCheck(this, UTM);
16
+ const urlSearchParams = new URLSearchParams(window.location.search);
17
+ const utmsFromUrl = {
18
+ source: urlSearchParams.get('utm_source'),
19
+ medium: urlSearchParams.get('utm_medium'),
20
+ campaign: urlSearchParams.get('utm_campaign'),
21
+ term: urlSearchParams.get('utm_term'),
22
+ content: urlSearchParams.get('utm_content')
23
+ };
24
+ if (utmsFromUrl.source && utmsFromUrl.medium) {
25
+ const cleanUtms = Object.fromEntries(Object.entries(utmsFromUrl).filter(([_, value]) => value));
26
+ _cookies.Cookies.set('hello_utm', JSON.stringify(cleanUtms));
27
+ }
28
+ }
29
+ _createClass(UTM, [{
30
+ key: "current",
31
+ get: function () {
32
+ try {
33
+ return JSON.parse(_cookies.Cookies.get('hello_utm')) || {};
34
+ } catch (e) {
35
+ return {};
36
+ }
37
+ }
38
+ }]);
39
+ return UTM;
40
+ }();
41
+ exports.UTM = UTM;
@@ -0,0 +1,38 @@
1
+ function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
2
+ function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, _toPropertyKey(descriptor.key), descriptor); } }
3
+ function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); Object.defineProperty(Constructor, "prototype", { writable: false }); return Constructor; }
4
+ function _toPropertyKey(arg) { var key = _toPrimitive(arg, "string"); return typeof key === "symbol" ? key : String(key); }
5
+ function _toPrimitive(input, hint) { if (typeof input !== "object" || input === null) return input; var prim = input[Symbol.toPrimitive]; if (prim !== undefined) { var res = prim.call(input, hint || "default"); if (typeof res !== "object") return res; throw new TypeError("@@toPrimitive must return a primitive value."); } return (hint === "string" ? String : Number)(input); }
6
+ import { Cookies } from './cookies';
7
+ var UTM = /*#__PURE__*/function () {
8
+ function UTM() {
9
+ _classCallCheck(this, UTM);
10
+ var urlSearchParams = new URLSearchParams(window.location.search);
11
+ var utmsFromUrl = {
12
+ source: urlSearchParams.get('utm_source'),
13
+ medium: urlSearchParams.get('utm_medium'),
14
+ campaign: urlSearchParams.get('utm_campaign'),
15
+ term: urlSearchParams.get('utm_term'),
16
+ content: urlSearchParams.get('utm_content')
17
+ };
18
+ if (utmsFromUrl.source && utmsFromUrl.medium) {
19
+ var cleanUtms = Object.fromEntries(Object.entries(utmsFromUrl).filter(_ref => {
20
+ var [_, value] = _ref;
21
+ return value;
22
+ }));
23
+ Cookies.set('hello_utm', JSON.stringify(cleanUtms));
24
+ }
25
+ }
26
+ _createClass(UTM, [{
27
+ key: "current",
28
+ get: function get() {
29
+ try {
30
+ return JSON.parse(Cookies.get('hello_utm')) || {};
31
+ } catch (e) {
32
+ return {};
33
+ }
34
+ }
35
+ }]);
36
+ return UTM;
37
+ }();
38
+ export { UTM };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hellotext/hellotext",
3
- "version": "2.1.3",
3
+ "version": "2.1.5",
4
4
  "description": "Hellotext JavaScript Client",
5
5
  "source": "src/index.js",
6
6
  "main": "lib/index.cjs",
@@ -1,28 +1,31 @@
1
+ import { Configuration } from '../core'
2
+
1
3
  class ApplicationChannel {
2
- static webSocket;
4
+ static webSocket
3
5
 
4
- send({ command, identifier }) {
5
- const data = {
6
+ send({ command, identifier, data }) {
7
+ const payload = {
6
8
  command,
7
- identifier: JSON.stringify(identifier)
9
+ identifier: JSON.stringify(identifier),
10
+ data: JSON.stringify(data || {}),
8
11
  }
9
12
 
10
13
  if (this.webSocket.readyState === WebSocket.OPEN) {
11
- this.webSocket.send(JSON.stringify(data))
14
+ this.webSocket.send(JSON.stringify(payload))
12
15
  } else {
13
16
  this.webSocket.addEventListener('open', () => {
14
- this.webSocket.send(JSON.stringify(data))
17
+ this.webSocket.send(JSON.stringify(payload))
15
18
  })
16
19
  }
17
20
  }
18
21
 
19
22
  onMessage(callback) {
20
- this.webSocket.addEventListener('message', (event) => {
23
+ this.webSocket.addEventListener('message', event => {
21
24
  const data = JSON.parse(event.data)
22
25
  const { type, message } = data
23
26
 
24
27
  if (this.ignoredEvents.includes(type)) {
25
- return;
28
+ return
26
29
  }
27
30
 
28
31
  callback(message)
@@ -31,7 +34,7 @@ class ApplicationChannel {
31
34
 
32
35
  get webSocket() {
33
36
  if (!ApplicationChannel.webSocket) {
34
- return ApplicationChannel.webSocket = new WebSocket("wss://www.hellotext.com/cable")
37
+ return (ApplicationChannel.webSocket = new WebSocket(Configuration.actionCableUrl))
35
38
  }
36
39
 
37
40
  return ApplicationChannel.webSocket
@@ -13,18 +13,18 @@ class WebchatChannel extends ApplicationChannel {
13
13
 
14
14
  subscribe() {
15
15
  const params = {
16
- channel: "WebchatChannel",
16
+ channel: 'WebchatChannel',
17
17
  id: this.id,
18
18
  session: this.session,
19
19
  conversation: this.conversation,
20
20
  }
21
21
 
22
- this.send( { command: 'subscribe', identifier: params })
22
+ this.send({ command: 'subscribe', identifier: params })
23
23
  }
24
24
 
25
25
  unsubscribe() {
26
26
  const params = {
27
- channel: "WebchatChannel",
27
+ channel: 'WebchatChannel',
28
28
  id: this.id,
29
29
  session: this.session,
30
30
  conversation: this.conversation,
@@ -33,32 +33,46 @@ class WebchatChannel extends ApplicationChannel {
33
33
  this.send({ command: 'unsubscribe', identifier: params })
34
34
  }
35
35
 
36
- onMessage(callback) {
37
- super.onMessage((message) => {
38
- if(message.type !== 'message') return
39
- callback(message)
40
- })
36
+ startTypingIndicator() {
37
+ const params = {
38
+ channel: 'WebchatChannel',
39
+ id: this.id,
40
+ session: this.session,
41
+ conversation: this.conversation,
42
+ }
43
+
44
+ this.send({ command: 'message', identifier: params, data: { action: 'started_typing' } })
41
45
  }
42
46
 
43
- onConversationAssignment(callback) {
44
- super.onMessage((message) => {
45
- if(message.type === 'conversation.assigned') {
46
- callback(message)
47
- }
47
+ stopTypingIndicator() {
48
+ const params = {
49
+ channel: 'WebchatChannel',
50
+ id: this.id,
51
+ session: this.session,
52
+ conversation: this.conversation,
53
+ }
54
+
55
+ this.send({ command: 'typing:stop', identifier: params, data: { action: 'stopped_typing' } })
56
+ }
57
+
58
+ onMessage(callback) {
59
+ super.onMessage(message => {
60
+ if (message.type !== 'message') return
61
+ callback(message)
48
62
  })
49
63
  }
50
64
 
51
- onAgentOnline(callback) {
52
- super.onMessage((message) => {
53
- if(message.type === 'agent_is_online') {
65
+ onReaction(callback) {
66
+ super.onMessage(message => {
67
+ if (message.type === 'reaction.create' || message.type === 'reaction.destroy') {
54
68
  callback(message)
55
69
  }
56
70
  })
57
71
  }
58
72
 
59
- onReaction(callback) {
60
- super.onMessage((message) => {
61
- if(message.type === 'reaction.create' || message.type === 'reaction.destroy') {
73
+ onTypingStart(callback) {
74
+ super.onMessage(message => {
75
+ if (message.type === 'started_typing') {
62
76
  callback(message)
63
77
  }
64
78
  })
@@ -23,6 +23,10 @@ export default class extends Controller {
23
23
  disabled: { type: Boolean, default: false },
24
24
  nextPage: { type: Number, default: undefined },
25
25
  fullScreenThreshold: { type: Number, default: 1024 },
26
+ typingIndicatorKeepAlive: { type: Number, default: 30000 }, // 30 seconds
27
+ offset: { type: Number, default: 24 },
28
+ padding: { type: Number, default: 24 },
29
+ optimisticTypingIndicatorWait: { type: Number, default: 1000 }, // 1 second
26
30
  }
27
31
 
28
32
  static classes = ['fadeOut']
@@ -40,12 +44,13 @@ export default class extends Controller {
40
44
  'messageTemplate',
41
45
  'messagesContainer',
42
46
  'title',
43
- 'onlineStatus',
44
47
  'attachmentImage',
45
48
  'footer',
46
49
  'toolbar',
47
50
  'message',
48
51
  'unreadCounter',
52
+ 'typingIndicator',
53
+ 'typingIndicatorTemplate',
49
54
  ]
50
55
 
51
56
  initialize() {
@@ -60,9 +65,8 @@ export default class extends Controller {
60
65
  this.files = []
61
66
 
62
67
  this.onMessageReceived = this.onMessageReceived.bind(this)
63
- this.onConversationAssignment = this.onConversationAssignment.bind(this)
64
- this.onAgentOnline = this.onAgentOnline.bind(this)
65
68
  this.onMessageReaction = this.onMessageReaction.bind(this)
69
+ this.onTypingStart = this.onTypingStart.bind(this)
66
70
 
67
71
  this.onScroll = this.onScroll.bind(this)
68
72
 
@@ -81,9 +85,8 @@ export default class extends Controller {
81
85
  this.setupFloatingUI({ trigger: this.triggerTarget, popover: this.popoverTarget })
82
86
 
83
87
  this.webChatChannel.onMessage(this.onMessageReceived)
84
- this.webChatChannel.onConversationAssignment(this.onConversationAssignment)
88
+ this.webChatChannel.onTypingStart(this.onTypingStart)
85
89
 
86
- this.webChatChannel.onAgentOnline(this.onAgentOnline)
87
90
  this.webChatChannel.onReaction(this.onMessageReaction)
88
91
 
89
92
  this.messagesContainerTarget.addEventListener('scroll', this.onScroll)
@@ -106,12 +109,94 @@ export default class extends Controller {
106
109
  this.broadcastChannel.removeEventListener('message', this.onOutboundMessageSent)
107
110
  this.messagesContainerTarget.removeEventListener('scroll', this.onScroll)
108
111
 
112
+ // Clean up typing indicator timeouts
113
+ this.clearTypingIndicator()
114
+
109
115
  this.broadcastChannel.close()
110
116
  this.floatingUICleanup()
111
117
 
112
118
  super.disconnect()
113
119
  }
114
120
 
121
+ onTypingStart() {
122
+ if (this.typingIndicatorVisible) {
123
+ return this.resetTypingIndicatorTimer()
124
+ }
125
+
126
+ this.showTypingIndicator()
127
+ }
128
+
129
+ showOptimisticTypingIndicator() {
130
+ if (this.typingIndicatorVisible) return
131
+
132
+ this.showTypingIndicator()
133
+ }
134
+
135
+ showTypingIndicator() {
136
+ this.clearTypingIndicator()
137
+
138
+ this.typingIndicatorVisible = true
139
+ const indicator = this.typingIndicatorTemplateTarget.cloneNode(true)
140
+
141
+ indicator.setAttribute('data-hellotext--webchat-target', 'typingIndicator')
142
+ indicator.style.display = 'flex'
143
+
144
+ this.messagesContainerTarget.appendChild(indicator)
145
+
146
+ requestAnimationFrame(() => {
147
+ this.messagesContainerTarget.scroll({
148
+ top: this.messagesContainerTarget.scrollHeight,
149
+ behavior: 'instant',
150
+ })
151
+ })
152
+
153
+ const timeout = this.typingIndicatorKeepAliveValue
154
+
155
+ this.incomingTypingIndicatorTimeout = setTimeout(() => {
156
+ this.clearTypingIndicator()
157
+ }, timeout)
158
+ }
159
+
160
+ resetTypingIndicatorTimer() {
161
+ if (!this.typingIndicatorVisible) return
162
+
163
+ // Clear existing timeout
164
+ clearTimeout(this.incomingTypingIndicatorTimeout)
165
+ clearTimeout(this.optimisticTypingTimeout)
166
+
167
+ // Use unified timeout for all typing indicators
168
+ const timeout = this.typingIndicatorKeepAliveValue
169
+
170
+ this.incomingTypingIndicatorTimeout = setTimeout(() => {
171
+ this.clearTypingIndicator()
172
+ }, timeout)
173
+ }
174
+
175
+ clearTypingIndicator() {
176
+ if (this.hasTypingIndicatorTarget) {
177
+ this.typingIndicatorTarget.remove()
178
+ }
179
+
180
+ this.typingIndicatorVisible = false
181
+
182
+ clearTimeout(this.incomingTypingIndicatorTimeout)
183
+ clearTimeout(this.optimisticTypingTimeout)
184
+ }
185
+
186
+ onMessageInputChange() {
187
+ this.resizeInput()
188
+ clearTimeout(this.typingIndicatorTimeout)
189
+
190
+ if (!this.hasSentTypingIndicator) {
191
+ this.webChatChannel.startTypingIndicator()
192
+ this.hasSentTypingIndicator = true
193
+ }
194
+
195
+ this.typingIndicatorTimeout = setTimeout(() => {
196
+ this.hasSentTypingIndicator = false
197
+ }, 3000)
198
+ }
199
+
115
200
  onOutboundMessageSent(event) {
116
201
  const { data } = event
117
202
 
@@ -120,7 +205,13 @@ export default class extends Controller {
120
205
  const element = new DOMParser().parseFromString(data.element, 'text/html').body
121
206
  .firstElementChild
122
207
 
123
- this.messagesContainerTarget.appendChild(element)
208
+ // Insert message before typing indicator if one exists
209
+ if (this.typingIndicatorVisible && this.hasTypingIndicatorTarget) {
210
+ this.messagesContainerTarget.insertBefore(element, this.typingIndicatorTarget)
211
+ } else {
212
+ this.messagesContainerTarget.appendChild(element)
213
+ }
214
+
124
215
  element.scrollIntoView({ behavior: 'instant' })
125
216
  },
126
217
  'message:failed': data => {
@@ -295,6 +386,7 @@ export default class extends Controller {
295
386
  })
296
387
  }
297
388
 
389
+ this.clearTypingIndicator()
298
390
  this.messagesContainerTarget.appendChild(element)
299
391
 
300
392
  Hellotext.eventEmitter.dispatch('webchat:message:received', {
@@ -303,7 +395,6 @@ export default class extends Controller {
303
395
  })
304
396
 
305
397
  element.scrollIntoView({ behavior: 'smooth' })
306
- this.setOfflineTimeout()
307
398
 
308
399
  if (this.openValue) {
309
400
  this.messagesAPI.markAsSeen(id)
@@ -316,24 +407,20 @@ export default class extends Controller {
316
407
  this.unreadCounterTarget.innerText = unreadCount > 99 ? '99+' : unreadCount
317
408
  }
318
409
 
319
- onConversationAssignment(conversation) {
320
- const { to: user } = conversation
410
+ resizeInput() {
411
+ const maxHeight = 96
321
412
 
322
- this.titleTarget.innerText = user.name
413
+ // Temporarily reset height to its natural content size
414
+ this.inputTarget.style.height = 'auto'
323
415
 
324
- if (user.online) {
325
- this.onlineStatusTarget.style.display = 'flex'
326
- } else {
327
- this.onlineStatusTarget.style.display = 'none'
328
- }
329
- }
416
+ // Set the height to the scrollHeight, which is the minimum height
417
+ // the element needs to fit its content without a scrollbar.
418
+ const scrollHeight = this.inputTarget.scrollHeight
330
419
 
331
- onAgentOnline() {
332
- this.onlineStatusTarget.style.display = 'flex'
333
- this.setOfflineTimeout()
420
+ this.inputTarget.style.height = `${Math.min(scrollHeight, maxHeight)}px`
334
421
  }
335
422
 
336
- async sendMessage() {
423
+ async sendMessage(e) {
337
424
  const formData = new FormData()
338
425
 
339
426
  const message = {
@@ -342,6 +429,10 @@ export default class extends Controller {
342
429
  }
343
430
 
344
431
  if (this.inputTarget.value.trim().length === 0 && this.files.length === 0) {
432
+ if (e && e.target) {
433
+ e.preventDefault()
434
+ }
435
+
345
436
  return
346
437
  }
347
438
 
@@ -381,7 +472,13 @@ export default class extends Controller {
381
472
  })
382
473
  }
383
474
 
384
- this.messagesContainerTarget.appendChild(element)
475
+ // Insert message before typing indicator if one exists
476
+ if (this.typingIndicatorVisible && this.hasTypingIndicatorTarget) {
477
+ this.messagesContainerTarget.insertBefore(element, this.typingIndicatorTarget)
478
+ } else {
479
+ this.messagesContainerTarget.appendChild(element)
480
+ }
481
+
385
482
  element.scrollIntoView({ behavior: 'smooth' })
386
483
 
387
484
  this.broadcastChannel.postMessage({
@@ -390,6 +487,7 @@ export default class extends Controller {
390
487
  })
391
488
 
392
489
  this.inputTarget.value = ''
490
+ this.resizeInput()
393
491
  this.files = []
394
492
 
395
493
  this.attachmentInputTarget.value = ''
@@ -400,9 +498,21 @@ export default class extends Controller {
400
498
 
401
499
  this.inputTarget.focus()
402
500
 
501
+ // Set up optimistic typing indicator BEFORE making the API call
502
+ // This prevents race conditions with server responses
503
+ if (!this.typingIndicatorVisible) {
504
+ clearTimeout(this.optimisticTypingTimeout)
505
+ this.optimisticTypingTimeout = setTimeout(() => {
506
+ this.showOptimisticTypingIndicator()
507
+ }, this.optimisticTypingIndicatorWaitValue)
508
+ }
509
+
403
510
  const response = await this.messagesAPI.create(formData)
404
511
 
405
512
  if (response.failed) {
513
+ // Clear the optimistic typing indicator on failure
514
+ clearTimeout(this.optimisticTypingTimeout)
515
+
406
516
  this.broadcastChannel.postMessage({
407
517
  type: 'message:failed',
408
518
  id: element.id,
@@ -422,6 +532,11 @@ export default class extends Controller {
422
532
  this.webChatChannel.updateSubscriptionWith(this.conversationIdValue)
423
533
  }
424
534
 
535
+ // If there's already a typing indicator visible, reset its timer
536
+ if (this.typingIndicatorVisible) {
537
+ this.resetTypingIndicatorTimer()
538
+ }
539
+
425
540
  this.attachmentContainerTarget.style.display = ''
426
541
  }
427
542
 
@@ -534,24 +649,12 @@ export default class extends Controller {
534
649
  this.inputTarget.focus()
535
650
  }
536
651
 
537
- setOfflineTimeout() {
538
- clearTimeout(this.offlineTimeout)
539
-
540
- this.offlineTimeout = setTimeout(() => {
541
- this.onlineStatusTarget.style.display = 'none'
542
- }, this.fiveMinutes)
543
- }
544
-
545
652
  byteToMegabyte(bytes) {
546
653
  return Math.ceil(bytes / 1024 / 1024)
547
654
  }
548
655
 
549
- get fiveMinutes() {
550
- return 300000
551
- }
552
-
553
656
  get middlewares() {
554
- return [offset(5), shift({ padding: 24 }), flip()]
657
+ return [offset(this.offsetValue), shift({ padding: this.paddingValue }), flip()]
555
658
  }
556
659
 
557
660
  get shouldOpenOnMount() {
@@ -14,6 +14,7 @@ import { Webchat } from './configuration/webchat'
14
14
  */
15
15
  class Configuration {
16
16
  static apiRoot = 'https://api.hellotext.com/v1'
17
+ static actionCableUrl = 'wss://www.hellotext.com/cable'
17
18
 
18
19
  static autoGenerateSession = true
19
20
  static session = null
package/src/hellotext.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { Configuration, Event } from './core'
2
2
 
3
3
  import API, { Response } from './api'
4
- import { Business, FormCollection, Query, Session, Webchat } from './models'
4
+ import { Business, FormCollection, Page, Query, Session, Webchat } from './models'
5
5
 
6
6
  import { NotInitializedError } from './errors'
7
7
 
@@ -23,6 +23,7 @@ class Hellotext {
23
23
  Configuration.assign(config)
24
24
  Session.initialize()
25
25
 
26
+ this.page = new Page()
26
27
  this.business = new Business(business)
27
28
  this.forms = new FormCollection()
28
29
 
@@ -54,11 +55,13 @@ class Hellotext {
54
55
  ...this.headers,
55
56
  }
56
57
 
58
+ const pageInstance = params && params.url ? new Page(params.url) : this.page
59
+
57
60
  const body = {
58
61
  session: this.session,
59
62
  action,
60
63
  ...params,
61
- url: (params && params.url) || window.location.href,
64
+ ...pageInstance.trackingData,
62
65
  }
63
66
 
64
67
  delete body.headers
@@ -2,7 +2,7 @@ import Hellotext from '../hellotext'
2
2
 
3
3
  class Cookies {
4
4
  static set(name, value) {
5
- if(typeof document !== 'undefined') {
5
+ if (typeof document !== 'undefined') {
6
6
  document.cookie = `${name}=${value}; path=/;`
7
7
  }
8
8
 
@@ -12,7 +12,7 @@ class Cookies {
12
12
  }
13
13
 
14
14
  static get(name) {
15
- if(typeof document !== 'undefined') {
15
+ if (typeof document !== 'undefined') {
16
16
  return document.cookie.match('(^|;)\\s*' + name + '\\s*=\\s*([^;]+)')?.pop()
17
17
  } else {
18
18
  return undefined
@@ -2,6 +2,8 @@ export { Business } from './business'
2
2
  export { Cookies } from './cookies'
3
3
  export { Form } from './form'
4
4
  export { FormCollection } from './form_collection'
5
+ export { Page } from './page'
5
6
  export { Query } from './query'
6
7
  export { Session } from './session'
8
+ export { UTM } from './utm'
7
9
  export { Webchat } from './webchat'
@@ -0,0 +1,65 @@
1
+ import { UTM } from './utm'
2
+
3
+ class Page {
4
+ constructor(url = null) {
5
+ this.utm = new UTM()
6
+ this._url = url
7
+ }
8
+
9
+ /**
10
+ * Get the current page URL
11
+ * @returns {string} The page URL
12
+ */
13
+ get url() {
14
+ return this._url || window.location.href
15
+ }
16
+
17
+ /**
18
+ * Get the current page title
19
+ * @returns {string} The page title
20
+ */
21
+ get title() {
22
+ return document.title
23
+ }
24
+
25
+ /**
26
+ * Get the current page path
27
+ * @returns {string} The page path
28
+ */
29
+ get path() {
30
+ if (this._url) {
31
+ try {
32
+ return new URL(this._url).pathname
33
+ } catch (e) {
34
+ return '/'
35
+ }
36
+ }
37
+
38
+ return window.location.pathname
39
+ }
40
+
41
+ /**
42
+ * Get current UTM parameters
43
+ * @returns {Object} The UTM parameters
44
+ */
45
+ get utmParams() {
46
+ return this.utm.current
47
+ }
48
+
49
+ /**
50
+ * Get page data for tracking
51
+ * @returns {Object} Object containing page object and utm_params
52
+ */
53
+ get trackingData() {
54
+ return {
55
+ page: {
56
+ url: this.url,
57
+ title: this.title,
58
+ path: this.path,
59
+ },
60
+ utm_params: this.utmParams,
61
+ }
62
+ }
63
+ }
64
+
65
+ export { Page }
@@ -1,5 +1,3 @@
1
- import { Cookies } from './cookies'
2
-
3
1
  class Query {
4
2
  static get inPreviewMode() {
5
3
  return new this().inPreviewMode
@@ -22,7 +20,7 @@ class Query {
22
20
  }
23
21
 
24
22
  get session() {
25
- return this.get('session') || Cookies.get('hello_session')
23
+ return this.get('session')
26
24
  }
27
25
 
28
26
  toHellotextParam(param) {
@@ -18,7 +18,7 @@ class Session {
18
18
  static initialize() {
19
19
  this.#query = new Query()
20
20
 
21
- this.session = Configuration.session || this.#query.session || Cookies.get('hello_session')
21
+ this.session = this.#query.session || Configuration.session || Cookies.get('hello_session')
22
22
 
23
23
  if (!this.session && Configuration.autoGenerateSession) {
24
24
  this.session = crypto.randomUUID()