@68publishers/amp-client
Version:
JS Client for 68publishers/amp
134 lines (111 loc) • 4.82 kB
JavaScript
import { State } from '../banner/state.mjs';
import { Fingerprint } from '../banner/fingerprint.mjs';
import { Events } from '../event/events.mjs';
import { IntersectionObserverFactory } from './intersection-observer-factory.mjs';
export class BannerInteractionWatcher {
#started;
#bannerManager;
#eventBus;
#fingerprints;
#intersectionObserver;
/**
* @param {BannerManager} bannerManager
* @param {EventBus} eventBus
* @param {Object} interactionOptions
*/
constructor (bannerManager, eventBus, interactionOptions) {
this.#started = false;
this.#bannerManager = bannerManager;
this.#eventBus = eventBus;
this.#fingerprints = {};
this.#intersectionObserver = IntersectionObserverFactory.create(
bannerManager,
eventBus,
this.#fingerprints,
interactionOptions.defaultIntersectionRatio,
interactionOptions.intersectionRatioMap,
interactionOptions.firstTimeSeenTimeout,
);
}
start() {
if (this.#started) {
return;
}
this.#started = true;
this.#eventBus.subscribe(Events.ON_BANNER_STATE_CHANGED, ({ banner }) => {
if (State.RENDERED === banner.state && !banner.isEmbed()) {
this.#watchBanner(banner);
}
}, null, -100);
this.#eventBus.subscribe(Events.ON_BANNER_MUTATED, ({ banner, mutation }) => {
if (State.RENDERED === banner.state && !banner.isEmbed() && 0 < mutation.addedNodes.length) {
this.#watchBanner(banner);
}
});
const banners = this.#bannerManager.getBannersByState({
state: State.RENDERED,
embed: false,
});
for (let banner of banners) {
this.#watchBanner(banner);
}
}
/**
* @param {ManagedBanner|ExternalBanner} banner
*/
#watchBanner(banner) {
const self = this;
const elements = banner.element.querySelectorAll('[data-amp-banner-fingerprint]');
for (let element of elements) {
const fingerprint = element.dataset.ampBannerFingerprint;
// save new fingerprint if it does not exist
if (!(fingerprint in this.#fingerprints)) {
this.#fingerprints[fingerprint] = {
fingerprint: Fingerprint.createFromValue(fingerprint),
alreadySeen: false,
alreadyFullySeen: false,
firstTimeSeenTimeoutId: null,
firstTimeFullySeenTimeoutId: null,
};
}
// when fingerprint element is not attached yet
if (!element._ampBannerFingerprintObserved) {
element._ampBannerFingerprintObserved = true;
this.#intersectionObserver.observe(element);
}
const linkElements = element.getElementsByTagName('a');
for (let linkElement of linkElements) {
// prevent multiple events
if (linkElement._ampClickingMetricsAttached) {
continue;
}
linkElement._ampClickingMetricsAttached = true;
linkElement.addEventListener('click', function (event) {
const fingerprintMetadata = self.#fingerprints[fingerprint];
if (!fingerprintMetadata) {
console.warn(`Unable to invoke an event "amp:banner:link-clicked" because the fingerprint "${fingerprint}" not managed.`);
return;
}
const banner = self.#bannerManager.getBannerByFingerprint(fingerprint);
if (!banner) {
console.warn(`Unable to invoke an event "amp:banner:link-clicked" because the banner for fingerprint "${fingerprint}" not found.`);
return;
}
const fingerprintArgs = {
fingerprint: fingerprintMetadata.fingerprint,
element: this.closest('[data-amp-banner-fingerprint]'),
banner: banner,
target: this,
clickEvent: event,
}
// a banner was not visible, but the user clicked on it
if (!fingerprintMetadata.alreadySeen) {
fingerprintMetadata.alreadySeen = true;
self.#eventBus.dispatch(Events.ON_BANNER_FIRST_TIME_SEEN, fingerprintArgs);
}
self.#eventBus.dispatch(Events.ON_BANNER_LINK_CLICKED, fingerprintArgs);
});
}
}
}
}