UNPKG

owtlab-tracking

Version:
429 lines (373 loc) 10.9 kB
import 'promise-polyfill/src/polyfill'; import 'whatwg-fetch'; import base64 from './core/utils/base64'; import each from './core/utils/each'; import extend from './core/utils/extend'; import Owtlab from './index'; import { getExtendedEventBody } from './extend-events'; import fetchRetry from './utils/fetchRetry'; import isUnique from './utils/unique'; import { getDomainName } from './helpers/getDomainName'; import { getDeviceContext } from './helpers/getWindowProfile'; // ------------------------------ // .recordEvent // ------------------------------ export function recordEvent( eventCollectionOrConfigObject, eventBody, callback, ) { let eventCollection = eventCollectionOrConfigObject; let unique; let configObject; if ( typeof eventCollectionOrConfigObject === 'object' && eventCollectionOrConfigObject ) { // slowly but surely we migrate to one object with all args configObject = eventCollectionOrConfigObject; eventCollection = eventCollectionOrConfigObject.collection || eventCollectionOrConfigObject.event_collection; eventBody = eventCollectionOrConfigObject.event; callback = eventCollectionOrConfigObject.callback; unique = eventCollectionOrConfigObject.unique; } const url = this.url('events', encodeURIComponent(eventCollection)); const data = {}; if (!checkValidation.call(this, callback)) { return; } if (!eventCollection || typeof eventCollection !== 'string') { handleValidationError.call( this, 'Collection name must be a string.', callback, ); return; } extend(data, mapEventToServer(eventCollection, eventBody)); // ------------------------------ // Run extendEvent(s) transforms // ------------------------------ const extendedEventsHash = {}; getExtendedEventBody(extendedEventsHash, this.extensions.events); getExtendedEventBody( extendedEventsHash, this.extensions.collections[eventCollection], ); getExtendedEventBody(extendedEventsHash, [data]); if (unique) { return isUnique(configObject, extendedEventsHash).then((isUniqueResult) => { if (!isUniqueResult) { return Promise.resolve({ created: false, message: '[NOT_UNIQUE] This event has already been recorded', }); } return recordEvent.call(this, { ...eventCollectionOrConfigObject, unique: undefined, }); }); } this.emit('recordEvent', eventCollection, extendedEventsHash); if (!Owtlab.enabled) { handleValidationError.call(this, 'Owtlab.enabled is set to false.', callback); return false; } if (Owtlab.optedOut) { return Promise.resolve({ created: false, message: 'Owtlab.optedOut is set to true.', }); } if (Owtlab.doNotTrack) { return Promise.resolve({ created: false, message: 'Owtlab.doNotTrack is set to true.', }); } return send.call(this, { url, extendedEventsHash, callback, configObject, eventCollection, }); } // ------------------------------ // .recordEvents // ------------------------------ export function recordEvents(eventsHash, callback) { const self = this; const url = this.url('events'); if (!checkValidation.call(this, callback)) { return; } if (typeof eventsHash !== 'object' || eventsHash instanceof Array) { handleValidationError.call( this, 'First argument must be an object', callback, ); return; } if (arguments.length > 2) { handleValidationError.call( this, 'Incorrect arguments provided to #recordEvents method', callback, ); return; } // ------------------------------ // Run extendEvent(s) transforms // ------------------------------ const extendedEventsHash = {}; each(eventsHash, (eventList, eventCollection) => { // Find or create collection on new hash extendedEventsHash[eventCollection] = extendedEventsHash[eventCollection] || []; // Loop over each eventBody in the existing hash each(eventList, (eventBody, index) => { // Create a new data object const extendedEventBody = {}; // Process "events" transform pipeline getExtendedEventBody(extendedEventBody, self.extensions.events); // Process "collection" transform pipeline getExtendedEventBody( extendedEventBody, self.extensions.collections[eventCollection], ); // Blend existing eventBody data into the result getExtendedEventBody(extendedEventBody, [eventBody]); // Push extendedEventBody into new hash extendedEventsHash[eventCollection].push(extendedEventBody); }); }); this.emit('recordEvents', extendedEventsHash); if (!Owtlab.enabled) { handleValidationError.call(this, 'Owtlab.enabled is set to false.', callback); return false; } if (Owtlab.optedOut) { return Promise.resolve({ created: false, message: 'Owtlab.optedOut is set to true.', }); } if (Owtlab.doNotTrack) { return Promise.resolve({ created: false, message: 'Owtlab.doNotTrack is set to true.', }); } return send.call(this, { url, extendedEventsHash, callback }); } function send({ url, extendedEventsHash, callback, configObject = {}, eventCollection, }) { const requestType = configObject.requestType || this.config.requestType; if ( navigator && navigator.sendBeacon && requestType === 'beaconAPI' // so you can send specific recordEvent() using beaconAPI // even if your global client's config prefers Fetch ) { navigator.sendBeacon( `${url}?api_key=${this.client_secret()}`, JSON.stringify(extendedEventsHash), ); if (callback) { // Beacon API is not handling responses nor errors callback(); } return this; } // this is IMAGE beacon, not the Beacon API. deprecated if (requestType === 'beacon' || requestType === 'img') { const getRequestUrl = this.url( 'events', encodeURIComponent(eventCollection), { api_key: this.client_secret(), data: encodeURIComponent(base64.encode(JSON.stringify(extendedEventsHash))), modified: new Date().getTime(), }, ); const getRequestUrlOkLength = getRequestUrl.length < getUrlMaxLength(); if (getRequestUrlOkLength) { sendBeacon.call(this, getRequestUrl, callback); } else if (callback) { callback( 'Beacon URL length exceeds current browser limit, and XHR is not supported.', null, ); } return this; } if (typeof fetch !== 'undefined') { return sendFetch.call(this, 'POST', url, extendedEventsHash, callback); } return this; } function sendFetch(method, url, data, callback = undefined) { const self = this; return fetchRetry(url, { method, body: data ? JSON.stringify({ events: data, client_id: self.client_id(), client_secret: self.client_secret() }) : '', mode: 'cors', redirect: 'follow', referrerPolicy: self.referrerPolicy() || 'unsafe-url', headers: { 'Content-Type': 'application/json', }, // keepalive: true, not supported for CORS yet retry: self.config.retry, }) .catch((connectionError) => { if (typeof callback !== 'undefined') { callback.call(self, connectionError, null); } self.emit('error', connectionError); return Promise.reject(connectionError); }) .then((response) => { if (response.ok) { return response.json(); } return response.json().then(responseJSON => Promise.reject({ error_code: responseJSON.error_code, body: responseJSON.message, status: response.status, ok: false, statusText: response.statusText, })); }) .then((responseJSON) => { const eventsSavedSuccessfuly = checkEventsSavedSuccessfuly(responseJSON); if (eventsSavedSuccessfuly) { if (typeof callback !== 'undefined') { callback.call(self, null, responseJSON); } return Promise.resolve(responseJSON); } if (typeof callback !== 'undefined') { callback.call(self, responseJSON, null); } self.emit('error', responseJSON); return Promise.reject(responseJSON); }); } function checkEventsSavedSuccessfuly(response) { // single event if (typeof response.created !== 'undefined') { if (response.created) { return true; } return false; } // multiple events const responseKeys = Object.keys(response); const notSavedEvents = responseKeys .map(collection => response[collection].filter(event => !event.success)) .filter(collection => collection.length > 0); if (notSavedEvents.length === 0) { return true; } return false; } // Validation function checkValidation(callback) { if (!this.client_id()) { handleValidationError.call( this, 'Owtlab.Client is missing a client_id property.', callback, ); return false; } if (!this.client_secret()) { handleValidationError.call( this, 'Owtlab.Client is missing a client_secret property.', callback, ); return false; } return true; } function handleValidationError(message, callback) { const err = `Event(s) not recorded: ${message}`; this.emit('error', err); if (callback) { callback.call(this, err, null); } } function getUrlMaxLength() { if (typeof window !== 'undefined' && navigator) { if ( navigator.userAgent.indexOf('MSIE') !== -1 || navigator.appVersion.indexOf('Trident/') > 0 ) { return 1900; } } return 16000; } /* DEPRECATED METHODS */ // Image Beacon Requests // DEPRECATED function sendBeacon(url, callback) { let self = this, img = document.createElement('img'), loaded = false; img.onload = function () { loaded = true; if ('naturalHeight' in this) { if (this.naturalHeight + this.naturalWidth === 0) { this.onerror(); return; } } else if (this.width + this.height === 0) { this.onerror(); return; } if (callback) { callback.call(self); } }; img.onerror = function () { loaded = true; if (callback) { callback.call(self, 'An error occurred!', null); } }; img.src = `${url}&c=clv1`; } export function mapEventToServer(key, data) { const domainName = getDomainName(window.location.hostname + (window.location.port ? `:${window.location.port}` : '')); return { [key]: [ { ...data, link: domainName, local_time_full: new Date().toISOString(), context: getDeviceContext(), }, ], }; } export function mapObjectEventToServer(object) { console.log('1', object); return Object.assign({}, Object.entries(object).map(([key, value]) => mapEventToServer(key, { value }))[0]); }