owtlab-tracking
Version:
A simple Tracking system
370 lines (335 loc) • 9.85 kB
JavaScript
import pkg from '../package.json';
export function initAutoTrackingCore(lib) {
return function (obj) {
const client = this;
const helpers = lib.helpers;
const utils = lib.utils;
const options = utils.extend(
{
ignoreDisabledFormFields: false,
ignoreFormFieldTypes: ['password'],
recordClicks: true,
recordClicksPositionPointer: false,
recordFormSubmits: true,
recordPageViews: true,
recordPageViewsOnExit: false,
recordScrollState: true,
shareUuidAcrossDomains: false,
collectIpAddress: true,
collectUuid: true,
recordElementViews: true,
catchError: undefined, // optional, function(someError) - error handler
},
obj,
);
if (client.config.requestType === 'beaconAPI' && options.catchError) {
throw 'You cannot use the BeaconAPI and catchError function in the same time, because BeaconAPI ignores errors. For requests with error handling - use requestType: \'fetch\'';
}
if (
client.config.requestType === 'jsonp' // jsonp is deprecated, it's the default value from old owtlab's client
) {
if (options.catchError) {
client.config.requestType = 'fetch';
} else {
client.config.requestType = 'beaconAPI';
}
}
let now = new Date();
let allTimeOnSiteS = 0;
let allTimeOnSiteMS = 0;
if (typeof document !== 'undefined') {
let hidden;
let visibilityChange;
if (typeof document.hidden !== 'undefined') {
hidden = 'hidden';
visibilityChange = 'visibilitychange';
} else if (typeof document.msHidden !== 'undefined') {
hidden = 'msHidden';
visibilityChange = 'msvisibilitychange';
} else if (typeof document.webkitHidden !== 'undefined') {
hidden = 'webkitHidden';
visibilityChange = 'webkitvisibilitychange';
}
const handleVisibilityChange = () => {
if (document[hidden]) {
allTimeOnSiteS += getSecondsSinceDate(now);
allTimeOnSiteMS += getMiliSecondsSinceDate(now);
return;
}
now = new Date();
};
if (
typeof document.addEventListener !== 'undefined' ||
hidden !== undefined
) {
document.addEventListener(
visibilityChange,
handleVisibilityChange,
false,
);
}
}
const cookie = new utils.cookie('owtlab');
const domainName = helpers.getDomainName(window.location.hostname);
const cookieDomain =
domainName && options.shareUuidAcrossDomains
? {
domain: `.${domainName}`,
}
: {};
let uuid;
if (options.collectUuid) {
uuid = cookie.get('uuid');
if (!uuid) {
uuid = helpers.getUniqueId();
cookie.set('uuid', uuid, cookieDomain);
}
}
let initialReferrer = cookie.get('initialReferrer');
if (!initialReferrer) {
initialReferrer = (document && document.referrer) || undefined;
cookie.set('initialReferrer', initialReferrer, cookieDomain);
}
let scrollState = {};
if (options.recordScrollState) {
scrollState = helpers.getScrollState();
utils.listener('window').on('scroll', () => {
scrollState = helpers.getScrollState(scrollState);
});
}
const addons = [
{
name: 'owtlab:ua_parser',
input: {
ua_string: 'user_agent',
},
output: 'tech',
},
{
name: 'owtlab:url_parser',
input: {
url: 'url.full',
},
output: 'url.info',
},
{
name: 'owtlab:url_parser',
input: {
url: 'referrer.full',
},
output: 'referrer.info',
},
{
name: 'owtlab:date_time_parser',
input: {
date_time: 'owtlab.timestamp',
},
output: 'time.utc',
},
{
name: 'owtlab:date_time_parser',
input: {
date_time: 'local_time_full',
},
output: 'time.local',
},
];
const ip_address = '${owtlab.ip}';
addons.push({
name: 'owtlab:ip_to_geo',
input: {
ip: 'ip_address',
remove_ip_property: !options.collectIpAddress,
},
output: 'geo',
});
client.extendEvents(() => {
const browserProfile = helpers.getBrowserProfile();
return {
tracked_by: `${pkg.name}-${pkg.version}`,
local_time_full: new Date().toISOString(),
user: {
uuid,
},
page: {
title: document ? document.title : null,
description: browserProfile.description,
scroll_state: scrollState,
time_on_page:
allTimeOnSiteS > 0 ? allTimeOnSiteS : getSecondsSinceDate(now),
time_on_page_ms:
allTimeOnSiteMS > 0
? allTimeOnSiteMS
: getMiliSecondsSinceDate(now),
},
ip_address,
geo: {
/* Enriched */
},
user_agent: '${owtlab.user_agent}',
tech: {
profile: browserProfile,
/* Enriched */
},
url: {
full: window ? window.location.href : '',
info: {
/* Enriched */
},
},
referrer: {
initial: initialReferrer,
full: document ? document.referrer : '',
info: {
/* Enriched */
},
},
time: {
local: {
/* Enriched */
},
utc: {
/* Enriched */
},
},
owtlab: {
timestamp: new Date().toISOString(),
addons,
},
};
});
if (options.recordClicks === true) {
utils.listener('a, a *').on('click', (e) => {
const el = e.target;
let event = {
element: helpers.getDomNodeProfile(el),
local_time_full: new Date().toISOString(),
};
// pointer position tracking
if (options.recordClicksPositionPointer === true) {
const pointer = {
x_position: e.pageX,
y_position: e.pageY,
};
event = { ...event, pointer };
}
if (options.catchError) {
return client
.recordEvent({
collection: 'clicks',
event,
})
.catch((err) => {
options.catchError(err);
});
}
return client.recordEvent({
collection: 'clicks',
event,
});
});
}
if (options.recordFormSubmits === true) {
utils.listener('form').on('submit', (e) => {
const el = e.target;
const serializerOptions = {
disabled: options.ignoreDisabledFormFields,
ignoreTypes: options.ignoreFormFieldTypes,
};
const event = {
form: {
action: el.action,
fields: utils.serializeForm(el, serializerOptions),
method: el.method,
},
element: helpers.getDomNodeProfile(el),
local_time_full: new Date().toISOString(),
};
if (options.catchError) {
return client
.recordEvent({
collection: 'form_submissions',
event,
})
.catch((err) => {
options.catchError(err);
});
}
return client.recordEvent({
collection: 'form_submissions',
event,
});
});
}
if (options.recordPageViews === true && !options.recordPageViewsOnExit) {
if (options.catchError) {
client
.recordEvent({
collection: 'pageviews',
})
.catch((err) => {
options.catchError(err);
});
} else {
client.recordEvent({
collection: 'pageviews',
});
}
}
if (options.recordPageViewsOnExit && typeof window !== 'undefined') {
window.addEventListener('beforeunload', () => {
client.config.requestType = 'beaconAPI'; // you can run beforeunload only with beaconAPI
client.recordEvent({
collection: 'pageviews',
});
});
}
if (options.recordElementViews === true) {
if (typeof IntersectionObserver !== 'undefined') {
const elementViewsOptions = {
threshold: 1.0,
};
const elementViewsCallback = (events, observer) => {
events.forEach((el) => {
if (el.isIntersecting) {
const event = {
element: helpers.getDomNodeProfile(el.target),
local_time_full: new Date().toISOString(),
};
if (options.catchError) {
return client
.recordEvent({
collection: 'element_views',
event,
})
.catch((err) => {
options.catchError(err);
});
}
return client.recordEvent({
collection: 'element_views',
event,
});
}
});
};
const observer = new IntersectionObserver(
elementViewsCallback,
elementViewsOptions,
);
const target = document.querySelectorAll('.track-element-view');
target.forEach((el) => {
observer.observe(el);
});
client.observers.IntersectionObserver = observer;
}
}
return client;
};
}
function getSecondsSinceDate(date) {
return Math.round(getMiliSecondsSinceDate(date) / 1000);
}
function getMiliSecondsSinceDate(date) {
return new Date().getTime() - date.getTime();
}