@hellotext/hellotext
Version:
Hellotext JavaScript Client
231 lines (192 loc) • 5.91 kB
JavaScript
import { Configuration, Event } from './core'
import API, { Response, keepaliveFor } from './api'
import {
Business,
Fingerprint,
FormCollection,
Page,
Query,
Session,
User,
Webchat,
} from './models'
import { NotInitializedError } from './errors'
class Hellotext {
static eventEmitter = new Event()
static forms
static business
static webchat
/**
* initialize the module.
* @param business public business id
* @param { Configuration } config
*/
static async initialize(business, config = {}) {
this.business = new Business(business)
this.page = new Page()
Configuration.assign(config)
Session.initialize(this.page)
this.forms = new FormCollection()
this.query = new Query()
const businessData = await this.business.hydrate()
const webchatConfig =
config.webchat === false
? false
: this.mergeWebchatConfig(
(businessData && businessData.webchat) || {},
config.webchat || {},
)
const hasExplicitBehaviourOverride =
config.webchat &&
config.webchat !== false &&
Object.prototype.hasOwnProperty.call(config.webchat, 'behaviour')
Configuration.webchat.behaviourOverride = hasExplicitBehaviourOverride
if (webchatConfig && webchatConfig.id) {
Configuration.webchat.assign(webchatConfig)
this.webchat = await Webchat.load(webchatConfig.id)
}
if (typeof MutationObserver !== 'undefined') {
this.forms.collectExistingFormsOnPage()
}
}
static mergeWebchatConfig(dashboardConfig, localConfig) {
return this.deepMergePlainObjects(dashboardConfig, localConfig)
}
static deepMergePlainObjects(base, override) {
const result = { ...base }
Object.entries(override).forEach(([key, value]) => {
if (this.isPlainObject(value) && this.isPlainObject(result[key])) {
result[key] = this.deepMergePlainObjects(result[key], value)
} else {
result[key] = value
}
})
return result
}
static isPlainObject(value) {
return typeof value === 'object' && value !== null && !Array.isArray(value)
}
/**
* Tracks an action that has happened on the page
*
* @param { String } action a valid action name
* @param { Object } params
* @returns {Promise<Response>}
*/
static async track(action, params = {}) {
if (this.notInitialized) {
throw new NotInitializedError()
}
const headers = {
...((params && params.headers) || {}),
...this.headers,
}
const user_parameters = {
...User.identificationData,
...(params.user_parameters || {}),
}
const pageInstance = params && params.url ? new Page(params.url) : this.page
const body = {
session: this.session,
user_parameters,
action,
...params,
...pageInstance.trackingData,
}
delete body.headers
return await API.events.create({
headers,
body,
// Track is the SDK's unload-sensitive analytics path. Keepalive belongs
// here rather than on identify/forms/webchat calls because event tracking
// is allowed to be fire-and-navigate, while those other calls have
// stronger request/response or interaction contracts.
keepalive: keepaliveFor(body),
})
}
/**
* @typedef { Object } IdentificationOptions
* @property { String } [email] - the email of the user
* @property { String } [phone] - the phone number of the user
* @property { String } [name] - the name of the user
* @property { String } [source] - the platform specific identifier where this pixel is running on.
*
* Identifies a user and attaches the hello_session to the user ID.
* Repeated calls are skipped only when the last successful identify payload
* for the current session remains unchanged.
* @param { String } externalId - the user ID
* @param { IdentificationOptions } options - the options for the identification
* @returns {Promise<Response>}
*/
static async identify(externalId, options = {}) {
const fingerprint = await Fingerprint.generate(this.session, externalId, options)
if (Fingerprint.matches(User.fingerprint, fingerprint)) {
return new Response(true, {
json: async () => ({
already_identified: true,
}),
})
}
const response = await API.identifications.create({
user_id: externalId,
...options,
})
if (response.succeeded) {
User.remember(externalId, options.source, fingerprint)
}
return response
}
/**
* Clears the user session, use when the user logs out to clear the hello cookies
*
* @returns {void}
*/
static forget() {
User.forget()
}
/**
* Registers an event listener
* @param event the name of the event to listen to
* @param callback the callback. This method will be called with the payload
*/
static on(event, callback) {
this.eventEmitter.addSubscriber(event, callback)
}
/**
* Removes an event listener
* @param event the name of the event to remove
* @param callback the callback to remove
*/
static removeEventListener(event, callback) {
this.eventEmitter.removeSubscriber(event, callback)
}
/**
*
* @returns {String}
*/
static get session() {
return Session.session
}
/**
* Determines if the session is set or not
* @returns {boolean}
*/
static get isInitialized() {
return Session.session !== undefined
}
// private
static get notInitialized() {
return !this.business || this.business.id === undefined
}
static get headers() {
if (this.notInitialized) {
throw new NotInitializedError()
}
return {
Authorization: `Bearer ${this.business.id}`,
Accept: 'application/json',
'Content-Type': 'application/json',
}
}
}
export default Hellotext