@hellotext/hellotext 2.2.0 → 2.3.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.
@@ -0,0 +1,53 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.User = 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 User = /*#__PURE__*/function () {
14
+ function User() {
15
+ _classCallCheck(this, User);
16
+ }
17
+ _createClass(User, null, [{
18
+ key: "id",
19
+ get: function () {
20
+ return _cookies.Cookies.get('hello_user_id');
21
+ }
22
+ }, {
23
+ key: "source",
24
+ get: function () {
25
+ return _cookies.Cookies.get('hello_user_source');
26
+ }
27
+ }, {
28
+ key: "remember",
29
+ value: function remember(id, source) {
30
+ if (source) {
31
+ _cookies.Cookies.set('hello_user_source', source);
32
+ }
33
+ _cookies.Cookies.set('hello_user_id', id);
34
+ }
35
+ }, {
36
+ key: "forget",
37
+ value: function forget() {
38
+ _cookies.Cookies.delete('hello_user_id');
39
+ _cookies.Cookies.delete('hello_user_source');
40
+ }
41
+ }, {
42
+ key: "identificationData",
43
+ get: function () {
44
+ if (!this.id) return {};
45
+ return {
46
+ id: this.id,
47
+ source: this.source
48
+ };
49
+ }
50
+ }]);
51
+ return User;
52
+ }();
53
+ exports.User = User;
@@ -0,0 +1,47 @@
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 User = /*#__PURE__*/function () {
8
+ function User() {
9
+ _classCallCheck(this, User);
10
+ }
11
+ _createClass(User, null, [{
12
+ key: "id",
13
+ get: function get() {
14
+ return Cookies.get('hello_user_id');
15
+ }
16
+ }, {
17
+ key: "source",
18
+ get: function get() {
19
+ return Cookies.get('hello_user_source');
20
+ }
21
+ }, {
22
+ key: "remember",
23
+ value: function remember(id, source) {
24
+ if (source) {
25
+ Cookies.set('hello_user_source', source);
26
+ }
27
+ Cookies.set('hello_user_id', id);
28
+ }
29
+ }, {
30
+ key: "forget",
31
+ value: function forget() {
32
+ Cookies.delete('hello_user_id');
33
+ Cookies.delete('hello_user_source');
34
+ }
35
+ }, {
36
+ key: "identificationData",
37
+ get: function get() {
38
+ if (!this.id) return {};
39
+ return {
40
+ id: this.id,
41
+ source: this.source
42
+ };
43
+ }
44
+ }]);
45
+ return User;
46
+ }();
47
+ export { User };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hellotext/hellotext",
3
- "version": "2.2.0",
3
+ "version": "2.3.0",
4
4
  "description": "Hellotext JavaScript Client",
5
5
  "source": "src/index.js",
6
6
  "main": "lib/index.cjs",
@@ -0,0 +1,25 @@
1
+ import Hellotext from '../hellotext'
2
+
3
+ import { Configuration } from '../core'
4
+ import { Response } from './response'
5
+
6
+ class IdentificationsAPI {
7
+ static get endpoint() {
8
+ return Configuration.endpoint('public/identifications')
9
+ }
10
+
11
+ static async create(data = {}) {
12
+ const response = await fetch(this.endpoint, {
13
+ method: 'POST',
14
+ headers: Hellotext.headers,
15
+ body: JSON.stringify({
16
+ session: Hellotext.session,
17
+ ...data,
18
+ }),
19
+ })
20
+
21
+ return new Response(response.ok, response)
22
+ }
23
+ }
24
+
25
+ export default IdentificationsAPI
package/src/api/index.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import BusinessesAPI from './businesses'
2
2
  import EventsAPI from './events'
3
3
  import FormsAPI from './forms'
4
+ import IdentificationsAPI from './identifications'
4
5
  import WebchatsAPI from './webchats'
5
6
 
6
7
  export default class API {
@@ -19,6 +20,10 @@ export default class API {
19
20
  static get webchats() {
20
21
  return WebchatsAPI
21
22
  }
23
+
24
+ static get identifications() {
25
+ return IdentificationsAPI
26
+ }
22
27
  }
23
28
 
24
29
  export { Response } from './response'
package/src/hellotext.js CHANGED
@@ -1,14 +1,11 @@
1
1
  import { Configuration, Event } from './core'
2
2
 
3
3
  import API, { Response } from './api'
4
- import { Business, FormCollection, Page, Query, Session, Webchat } from './models'
4
+ import { Business, FormCollection, Page, Query, Session, User, Webchat } from './models'
5
5
 
6
6
  import { NotInitializedError } from './errors'
7
7
 
8
8
  class Hellotext {
9
- static #session
10
- static #query
11
-
12
9
  static eventEmitter = new Event()
13
10
  static forms
14
11
  static business
@@ -27,7 +24,7 @@ class Hellotext {
27
24
  this.business = new Business(business)
28
25
  this.forms = new FormCollection()
29
26
 
30
- this.#query = new Query()
27
+ this.query = new Query()
31
28
 
32
29
  if (Configuration.webchat.id) {
33
30
  this.webchat = await Webchat.load(Configuration.webchat.id)
@@ -59,6 +56,7 @@ class Hellotext {
59
56
 
60
57
  const body = {
61
58
  session: this.session,
59
+ user: User.identificationData,
62
60
  action,
63
61
  ...params,
64
62
  ...pageInstance.trackingData,
@@ -72,6 +70,40 @@ class Hellotext {
72
70
  })
73
71
  }
74
72
 
73
+ /**
74
+ * @typedef { Object } IdentificationOptions
75
+ * @property { String } [email] - the email of the user
76
+ * @property { String } [phone] - the phone number of the user
77
+ * @property { String } [name] - the name of the user
78
+ * @property { String } [source] - the platform specific identifier where this pixel is running on.
79
+ *
80
+ * Identifies a user and attaches the hello_session to the user ID
81
+ * @param { String } externalId - the user ID
82
+ * @param { IdentificationOptions } options - the options for the identification
83
+ * @returns {Promise<Response>}
84
+ */
85
+ static async identify(externalId, options = {}) {
86
+ const response = await API.identifications.create({
87
+ user_id: externalId,
88
+ ...options,
89
+ })
90
+
91
+ if (response.succeeded) {
92
+ User.remember(externalId, options.source)
93
+ }
94
+
95
+ return response
96
+ }
97
+
98
+ /**
99
+ * Clears the user session, use when the user logs out to clear the hello cookies
100
+ *
101
+ * @returns {void}
102
+ */
103
+ static forget() {
104
+ User.forget()
105
+ }
106
+
75
107
  /**
76
108
  * Registers an event listener
77
109
  * @param event the name of the event to listen to
@@ -1,9 +1,18 @@
1
1
  import Hellotext from '../hellotext'
2
+ import { Page } from './page'
2
3
 
3
4
  class Cookies {
4
5
  static set(name, value) {
5
6
  if (typeof document !== 'undefined') {
6
- document.cookie = `${name}=${value}; path=/;`
7
+ const secure = window.location.protocol === 'https:' ? '; Secure' : ''
8
+ const domain = Page.getRootDomain()
9
+ const maxAge = 10 * 365 * 24 * 60 * 60 // 10 years in seconds
10
+
11
+ if (domain) {
12
+ document.cookie = `${name}=${value}; path=/${secure}; domain=${domain}; max-age=${maxAge}; SameSite=Lax`
13
+ } else {
14
+ document.cookie = `${name}=${value}; path=/${secure}; max-age=${maxAge}; SameSite=Lax`
15
+ }
7
16
  }
8
17
 
9
18
  if (name === 'hello_session') {
@@ -24,6 +33,19 @@ class Cookies {
24
33
  return undefined
25
34
  }
26
35
  }
36
+
37
+ static delete(name) {
38
+ if (typeof document !== 'undefined') {
39
+ const domain = Page.getRootDomain()
40
+ const secure = window.location.protocol === 'https:' ? '; Secure' : ''
41
+
42
+ if (domain) {
43
+ document.cookie = `${name}=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT${secure}; domain=${domain}; SameSite=Lax`
44
+ } else {
45
+ document.cookie = `${name}=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT${secure}; SameSite=Lax`
46
+ }
47
+ }
48
+ }
27
49
  }
28
50
 
29
51
  export { Cookies }
@@ -5,5 +5,6 @@ export { FormCollection } from './form_collection'
5
5
  export { Page } from './page'
6
6
  export { Query } from './query'
7
7
  export { Session } from './session'
8
+ export { User } from './user'
8
9
  export { UTM } from './utm'
9
10
  export { Webchat } from './webchat'
@@ -11,7 +11,7 @@ class Page {
11
11
  * @returns {string} The page URL
12
12
  */
13
13
  get url() {
14
- return this._url || window.location.href
14
+ return this._url !== null && this._url !== undefined ? this._url : window.location.href
15
15
  }
16
16
 
17
17
  /**
@@ -60,6 +60,88 @@ class Page {
60
60
  utm_params: this.utmParams,
61
61
  }
62
62
  }
63
+
64
+ /**
65
+ * Get the current page domain (root domain for cookie sharing)
66
+ * @returns {string} The root domain with leading dot, or null if no valid URL
67
+ */
68
+ get domain() {
69
+ try {
70
+ const url = this.url
71
+ if (!url) {
72
+ return null
73
+ }
74
+
75
+ const hostname = new URL(url).hostname
76
+ return Page.getRootDomain(hostname)
77
+ } catch (e) {
78
+ return null
79
+ }
80
+ }
81
+
82
+ /**
83
+ * Get the root domain from a hostname (static method, no instance needed)
84
+ * @param {string} hostname - The hostname to parse
85
+ * @returns {string} The root domain with leading dot, or null if invalid
86
+ */
87
+ static getRootDomain(hostname = null) {
88
+ try {
89
+ if (!hostname) {
90
+ if (typeof window === 'undefined' || !window.location?.hostname) {
91
+ return null
92
+ }
93
+ hostname = window.location.hostname
94
+ }
95
+
96
+ const parts = hostname.split('.')
97
+
98
+ // Handle localhost or single-part domains
99
+ if (parts.length <= 1) {
100
+ return hostname
101
+ }
102
+
103
+ // Handle e-commerce platform domains where store identifier must be preserved
104
+ // Examples:
105
+ // storename.myshopify.com -> .storename.myshopify.com
106
+ // storename.vtexcommercestable.com.br -> .storename.vtexcommercestable.com.br
107
+ // storename.wixsite.com -> .storename.wixsite.com
108
+ const platformSuffixes = [
109
+ 'myshopify.com',
110
+ 'vtexcommercestable.com.br',
111
+ 'myvtex.com',
112
+ 'wixsite.com',
113
+ ]
114
+
115
+ for (const suffix of platformSuffixes) {
116
+ const suffixParts = suffix.split('.')
117
+ // Get the last N parts of the hostname (e.g., last 2 parts for 'myshopify.com')
118
+ const domainTail = parts.slice(-suffixParts.length).join('.')
119
+
120
+ // Check if the tail exactly matches the platform suffix
121
+ // and there are additional parts for the store identifier
122
+ if (domainTail === suffix && parts.length > suffixParts.length) {
123
+ // Include store identifier + platform suffix
124
+ // e.g., ['secure', 'mystore', 'myshopify', 'com'] -> '.mystore.myshopify.com'
125
+ return `.${parts.slice(-(suffixParts.length + 1)).join('.')}`
126
+ }
127
+ }
128
+
129
+ // Simple heuristic: if TLD is 2 chars and second-level is short (<=3 chars),
130
+ // it's likely a multi-part TLD like .com.br, .co.uk
131
+ const tld = parts[parts.length - 1]
132
+ const secondLevel = parts[parts.length - 2]
133
+
134
+ if (parts.length > 2 && tld.length === 2 && secondLevel.length <= 3) {
135
+ // Multi-part TLD: take last 3 parts (e.g., store.com.br)
136
+ return `.${parts.slice(-3).join('.')}`
137
+ }
138
+
139
+ // Regular TLD: take last 2 parts (e.g., example.com)
140
+ return `.${parts.slice(-2).join('.')}`
141
+ } catch (e) {
142
+ return null
143
+ }
144
+ }
63
145
  }
64
146
 
65
147
  export { Page }
@@ -0,0 +1,35 @@
1
+ import { Cookies } from './cookies'
2
+
3
+ class User {
4
+ static get id() {
5
+ return Cookies.get('hello_user_id')
6
+ }
7
+
8
+ static get source() {
9
+ return Cookies.get('hello_user_source')
10
+ }
11
+
12
+ static remember(id, source) {
13
+ if (source) {
14
+ Cookies.set('hello_user_source', source)
15
+ }
16
+
17
+ Cookies.set('hello_user_id', id)
18
+ }
19
+
20
+ static forget() {
21
+ Cookies.delete('hello_user_id')
22
+ Cookies.delete('hello_user_source')
23
+ }
24
+
25
+ static get identificationData() {
26
+ if (!this.id) return {}
27
+
28
+ return {
29
+ id: this.id,
30
+ source: this.source,
31
+ }
32
+ }
33
+ }
34
+
35
+ export { User }