clean-insights-sdk
Version:
A privacy-preserving measurement framework.
182 lines (180 loc) • 6.6 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Insights = void 0;
var consents_1 = require("./consents");
var dayjs_1 = __importDefault(require("dayjs"));
var duration_1 = __importDefault(require("dayjs/plugin/duration"));
dayjs_1.default.extend(duration_1.default);
var Insights = /** @class */ (function () {
/**
* Create an `Insights` object according to configuration with all data from the store which
* is due for offloading to the server.
*
* @param {Configuration} conf
* The current configuration.
*
* @param {{consents: Consents, visits: Visit[], events: Event[]}} store
* The current measurement and consents store.
*/
function Insights(conf, store) {
var _this = this;
/**
* Matomo site ID.
*/
this.idsite = 0;
/**
* Preferred user languages as an HTTP Accept header.
*/
this.lang = undefined;
/**
* User Agent string.
*/
this.ua = undefined;
/**
* `Visit` data points.
*/
this.visits = [];
/**
* `Event` data points.
*/
this.events = [];
this.idsite = conf.siteId;
if (!conf.serverSideAnonymousUsage
&& store.consents.stateOfFeature(consents_1.Feature.lang) == consents_1.ConsentState.granted
&& typeof navigator === 'object' && navigator) {
this.lang = Insights.acceptLanguages();
}
if (!conf.serverSideAnonymousUsage
&& store.consents.stateOfFeature(consents_1.Feature.ua) == consents_1.ConsentState.granted
&& typeof navigator === 'object' && navigator) {
this.ua = navigator.userAgent;
}
Insights.purge(conf, store);
var now = (0, dayjs_1.default)();
store.visits.forEach(function (visit) {
if (!conf.campaigns.hasOwnProperty(visit.campaignId)) {
return;
}
// Only send, after aggregation period is over. `last` should contain that date!
if (now.isAfter(visit.last)) {
_this.visits.push({
action_name: visit.scenePath.join('/'),
period_start: visit.first.unix(),
period_end: visit.last.unix(),
times: visit.times
});
}
});
store.events.forEach(function (event) {
if (!conf.campaigns.hasOwnProperty(event.campaignId)) {
return;
}
// Only send, after aggregation period is over. `last` should contain that date!
if (now.isAfter(event.last)) {
_this.events.push({
category: event.category,
action: event.action,
name: event.name,
value: event.value,
period_start: event.first.unix(),
period_end: event.last.unix(),
times: event.times
});
}
});
}
Object.defineProperty(Insights.prototype, "isEmpty", {
/**
* @returns {boolean}
*/
get: function () {
return this.visits.length < 1 && this.events.length < 1;
},
enumerable: false,
configurable: true
});
/**
* Removes all visits and events from the given `Store`, which are also available in here.
*
* This should be called, when all `Insights` were offloaded at the server successfully.
*
* @param {{consents: Consents, visits: Visit[], events: Event[]}} store
* The store where the `Visit`s and `Event`s in here came from.
*/
Insights.prototype.clean = function (store) {
this.visits.forEach(function (d) {
var i = store.visits.length;
while (i--) {
var v = store.visits[i];
if (v.scenePath.join('/') === d.action_name && Insights.equals(v, d)) {
store.visits.splice(i, 1);
}
}
});
this.events.forEach(function (d) {
var i = store.events.length;
while (i--) {
var e = store.events[i];
// noinspection EqualityComparisonWithCoercionJS
if (e.category === d.category && e.action === d.action
&& e.name == d.name && e.value == d.value && Insights.equals(e, d)) {
store.events.splice(i, 1);
}
}
});
};
/**
*
* @param {DataPoint} dp
* @param {{period_start: number, period_end: number, times: number}} data
* @return {boolean}
*/
Insights.equals = function (dp, data) {
return dp.first.unix() === data.period_start && dp.last.unix() === data.period_end && dp.times === data.times;
};
/**
* Create an HTTP-accept-language-header-like string from the information
* we can get from inside JavaScript.
*
* @private
* @return {string}
*/
Insights.acceptLanguages = function () {
var components = [];
var languages = navigator.languages || [navigator.language];
for (var i = 0; i < languages.length; i++) {
var quality = 1.0 - (i * 0.1);
components.push("".concat(languages[i], ";q=").concat(quality));
if (quality <= 0.5) {
break;
}
}
return components.join(',');
};
/**
Removes `DataPoint`s, which are too old. These were never been sent, otherwise, they would have
been removed, already.
Remove them now, if they're over the threshold, to not accumulate too many `DataPoints` and
therefore reduce privacy.
*/
Insights.purge = function (conf, store) {
var threshold = (0, dayjs_1.default)().subtract(dayjs_1.default.duration(conf.maxAgeOfOldData, 'days'));
var i = store.visits.length;
while (i--) {
if (store.visits[i].last.isBefore(threshold)) {
store.visits.splice(i, 1);
}
}
i = store.events.length;
while (i--) {
if (store.events[i].last.isBefore(threshold)) {
store.events.splice(i, 1);
}
}
};
return Insights;
}());
exports.Insights = Insights;