@benshi.ai/js-sdk
Version:
Benshi SDK
146 lines (115 loc) • 4.33 kB
text/typescript
import { EventEmitter } from 'events'
import BsViewableImpressionObserver, { ImpressionObservedTypes } from './ViewableImpressionObserver'
import {
Dataset,
IImpression,
IImpressionManager,
ImpressionEventType,
ViewableImpressionOptions
} from './typings';
declare interface ViewableImpressionEngine {
on<U extends keyof BsViewableImpressionManagerEvents>(
event: U, listener: BsViewableImpressionManagerEvents[U]
): this;
emit<U extends keyof BsViewableImpressionManagerEvents>(
event: U, ...args: Parameters<BsViewableImpressionManagerEvents[U]>
): boolean;
}
interface BsViewableImpressionManagerEvents {
[ImpressionEventType.Impression]: (item: Dataset) => void,
}
class ViewableImpressionEngine extends EventEmitter implements IImpressionManager {
private alreadyVisibleImpressed
private pendingImpressions: IImpression | {}
private impressionObserver = null
private containerClassname
private itemClassname
private appData:any = {}
private options
// at least one second in the screen
constructor(impressionObserver: BsViewableImpressionObserver, options: Partial<ViewableImpressionOptions>) {
super()
this.alreadyVisibleImpressed = new Set()
this.pendingImpressions = {}
this.options = options
this.impressionObserver = impressionObserver
this.impressionObserver.on(ImpressionObservedTypes.View, (id, eventData) => {
this.add(id, eventData)
})
this.impressionObserver.on(ImpressionObservedTypes.Hide, id => {
this.remove(id)
})
setInterval(() => this.triggerPendingImpressions(), this.options.triggerInterval)
}
triggerPendingImpressions() {
const currentTimestamp = Date.now()
const idsToTrigger = []
for (const [id, { time_in }] of Object.entries(this.pendingImpressions)) {
// a potential optimization of this would be to sort the
// list by date and stop checking when one does not
// satisfy the time constraint
if (currentTimestamp - time_in > this.options.keepVisibleTimeout) {
idsToTrigger.push(id)
}
}
idsToTrigger.forEach(id => {
this.alreadyVisibleImpressed.add(id)
this.emit(
ImpressionEventType.Impression,
{
dataset: this.pendingImpressions[id].data,
appData: this.appData
})
delete this.pendingImpressions[id]
})
}
start(containerClassname, itemClassname, appData) {
this.appData = appData
this.containerClassname = containerClassname
this.itemClassname = itemClassname
this.impressionObserver.start(containerClassname, itemClassname)
}
stop() {
this.triggerPendingImpressions()
this.impressionObserver.stop()
this.alreadyVisibleImpressed.clear()
this.pendingImpressions = {}
}
restart(appData: any) {
this.triggerPendingImpressions()
this.appData = appData
this.alreadyVisibleImpressed.clear()
this.pendingImpressions = {}
this.impressionObserver.stop()
this.impressionObserver.start(this.containerClassname, this.itemClassname)
}
add(id, data: Dataset) {
if (this.alreadyVisibleImpressed.has(id)) {
// the item has been already tracked
return
}
if (!this.pendingImpressions[id]) {
this.pendingImpressions[id] = {
time_in: Date.now(),
appData: this.appData,
data
}
}
}
remove(id) {
if (!this.pendingImpressions[id]) {
return
}
// this check is needed in case the interval is still sleeping
if (Date.now() - this.pendingImpressions[id].time_in > this.options.keepVisibleTimeout) {
this.emit(
ImpressionEventType.Impression,
{
dataset: this.pendingImpressions[id].data,
appData: this.appData
})
}
delete this.pendingImpressions[id]
}
}
export default ViewableImpressionEngine