@benshi.ai/js-sdk
Version:
Benshi SDK
313 lines (270 loc) • 9.28 kB
text/typescript
import ECommmercePropertiesTI from '../ECommerce/typings-ti'
import OxygenPropertiesTI from '../ECommerce/oxygen.typings-ti'
import BloodPropertiesTI from '../ECommerce/blood.typings-ti'
import MedicalEquipmentPropertiesTI from '../ECommerce/medicalEquipment.typings-ti'
import NavigationPropertiesTI from './typings-ti'
import { AudioVideoType, ContentBlock, ImageType, InternalMediaProperties, MediaData, NudgeResponseProperties, RateProperties, TrackProperties, TrackTypes } from './typings'
import {
AppProperties,
IdentifyProperties,
InternalSearchProperties,
MediaProperties,
NavigationTypes,
PageProperties,
SearchProperties
} from "./typings"
import { injectEvent } from "../../core/injector"
import { getCoreInstance } from '../../coreInstanceGetter'
import { ICatalogRepository } from '../../core/repositories/catalog/CatalogRepository'
import { ItemType,TypedItem } from '../ECommerce/typings'
import { hasRepeatedIds } from '../../utils'
let catalogRepository: ICatalogRepository;
const moduleName = ContentBlock.Core
/**
* `title` may be overwritten by the developer
* This variable stores the known title, that is,
* the one provided by the BsAppStateMonitor or by developer
*/
let lastKnownTitle
/**
* @internal
*
* @param properties
* @param sendNow
*/
const logAppEvent = (properties: AppProperties, sendNow = false) => {
injectEvent(
properties,
[NavigationPropertiesTI, ECommmercePropertiesTI, BloodPropertiesTI, OxygenPropertiesTI, MedicalEquipmentPropertiesTI],
NavigationTypes.App,
moduleName,
'',
sendNow)
}
/**
* @internal
*
* @param userId
* @param properties
* @param sendNow
*/
const logIdentifyEvent = (properties: IdentifyProperties, sendNow = false, ) => {
injectEvent(
properties,
[NavigationPropertiesTI, ECommmercePropertiesTI, BloodPropertiesTI, OxygenPropertiesTI, MedicalEquipmentPropertiesTI],
NavigationTypes.Identify,
moduleName,
'',
sendNow)
}
/**
* Whenever the user interacts with a media element (Video, Audio, Image), log that
* action with this method
*
* @param {MediaProperties} properties
*
* @param {MediaData} mediaData Information related to media features to be injected
* into the media catalog
*
* @param sendNow
*/
const logMediaEvent = async (
properties: MediaProperties,
mediaData: MediaData,
sendNow = false): Promise<void> => {
properties.time = properties.type === ImageType.Image ? 0 : properties.time
const internalMediaProperties: InternalMediaProperties = {
...properties,
id_source: properties.id,
id: `${properties.type}_${properties.id}`
}
injectEvent(
internalMediaProperties,
[NavigationPropertiesTI, ECommmercePropertiesTI, BloodPropertiesTI, OxygenPropertiesTI, MedicalEquipmentPropertiesTI],
NavigationTypes.Media,
properties.contentBlock || ContentBlock.Core,
'',
sendNow
)
if (mediaData !== undefined && Object.keys(mediaData).length !== 0) {
catalogRepository.injectMedia(properties.id, properties.type as AudioVideoType, mediaData)
}
}
/**
* SDK tracks automatically page changes by inspecting the URL. However,
* depending on the app implementation, it may happen that the URL does not
* change when a new view is presented to the user. For these cases, use this method
*
* @param properties
* @param sendNow
*/
const logPageEvent = (properties: PageProperties, sendNow = false): void => {
if (properties.duration === 0) {
// avoid noise due internal URLs that changes so fast
return
}
properties.title = lastKnownTitle ? lastKnownTitle : properties.title
injectEvent(
properties,
[NavigationPropertiesTI, ECommmercePropertiesTI, BloodPropertiesTI, OxygenPropertiesTI, MedicalEquipmentPropertiesTI],
NavigationTypes.Page,
moduleName,
'',
sendNow
)
lastKnownTitle = undefined
}
/**
* Log generic action regarding navigation, like view an item or a list, that are not
* included in other specific modules like `eCommerce` or `eLearning`. It also allows
* to log navigation to external links
*
* @param properties
* @param sendNow
*/
const logTrackEvent = async (
properties: TrackProperties,
sendNow = false): Promise<void> => {
const internalProperties = {
...properties,
type: TrackTypes.ReferenceGuide
}
injectEvent(
internalProperties,
[NavigationPropertiesTI, ECommmercePropertiesTI, BloodPropertiesTI, OxygenPropertiesTI, MedicalEquipmentPropertiesTI],
NavigationTypes.Track,
moduleName,
'',
sendNow)
}
/**
* @internal
*
* @param properties
* @param sendNow
*/
const logPushNotificationEvent = (properties: NudgeResponseProperties, sendNow = false): void => {
injectEvent(
properties,
[NavigationPropertiesTI, ECommmercePropertiesTI, BloodPropertiesTI, OxygenPropertiesTI, MedicalEquipmentPropertiesTI],
NavigationTypes.NudgeResponse,
moduleName,
'',
sendNow)
}
const logRateEvent = (properties: RateProperties, sendNow = false): void => {
const RATE_MIN_VALUE = 0
const RATE_MAX_VALUE = 5
if (properties.rate_value < RATE_MIN_VALUE || properties.rate_value > RATE_MAX_VALUE) {
throw new Error('invalid range for value')
}
injectEvent(
properties,
[NavigationPropertiesTI, ECommmercePropertiesTI, BloodPropertiesTI, OxygenPropertiesTI, MedicalEquipmentPropertiesTI],
NavigationTypes.Rate,
moduleName,
'',
sendNow)
}
/**
* This function logs into the platform a new search performed by the user,
* including both, the parameters and the resulting ids
*
* @param {SearchProperties} properties
* @param {boolean} isNewSearch Whenever the user performs a new search,
* call the function with `true` in this parameter; however, when the user
* clicks on the links to go to next paginated page or load more items,
* related to the same search, just pass `false`
*
* @returns Search ID, which must be included in subsequences Item events
*/
const logSearchEvent = (properties: SearchProperties, searchId: string, sendNow = false): void => {
const { contentBlock, ...restOfProperties } = properties
const ERROR_REPEATEAD_IDS = 'repeated-ids'
let formattedResultsIds: TypedItem[] = []
if (!properties.results_ids) {
throw new Error('missing-results-ids')
}
if (properties.results_ids.length !== 0 && typeof properties.results_ids[0] === 'string') {
formattedResultsIds = (properties.results_ids as string[])
.map( (id: string) => ({id, type: ItemType.Drug}))
} else {
formattedResultsIds = properties.results_ids as TypedItem[]
}
if (hasRepeatedIds(formattedResultsIds)) {
throw new Error(ERROR_REPEATEAD_IDS)
}
const internalProperties: InternalSearchProperties = {
...restOfProperties,
results_list: formattedResultsIds,
id: searchId,
}
delete internalProperties.results_ids
injectEvent(
internalProperties,
[NavigationPropertiesTI, ECommmercePropertiesTI, BloodPropertiesTI, OxygenPropertiesTI, MedicalEquipmentPropertiesTI],
NavigationTypes.Search,
properties.contentBlock || ContentBlock.Core,
'',
sendNow
)
}
/**
* SDK has some autonomy to trigger events automatically. For instance,
* it detects URL changes and notify Benshi.ai backend about that changes
* and the duration. However, it is not able to find out which block that URL
* belongs to. The goal of this method is to solve that. So, call it whenever
* the user goes to another section within the site. For example, if the user
* jumps from the e-commerce section to e-learning, call in this way:
*
* ```
* Navigation.setCurrentBlock(ContentBlock.Elearning)
* ```
*
* @param {ContentBlock} block the new section where the user has just jumped
*/
const setCurrentBlock = (block: ContentBlock) => {
const core = getCoreInstance()
core.setCurrentBlock(block)
}
/**
* There are scenarious where the browser page title is not available,
* for instance when the web is embedded within a webview. Use this function
* whenever the new page is loaded to let the SDK know the title.
*
* Note that only is necessary when the standard `document.title` is not
* available for any reason
*
*
* @param title
*/
const setTitle = (title: string) => {
// the timeout is for waiting for the page-changed event, if exists,
// see BsSystem for more details
setTimeout(() => {
lastKnownTitle = title
}, 15)
}
/**
* Private function to inject CurrencyRepository
*
* @ignore
*/
const init = (
injectedCatalogRepository: ICatalogRepository
) => {
catalogRepository = injectedCatalogRepository
}
export default {
logAppEvent,
logIdentifyEvent,
logMediaEvent,
logPageEvent,
logPushNotificationEvent,
logRateEvent,
logSearchEvent,
logTrackEvent,
setCurrentBlock,
setTitle,
init
}