@splitsoftware/splitio-commons
Version:
Split JavaScript SDK common components
122 lines (121 loc) • 6.37 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.BrowserSignalListener = void 0;
var impressionsSubmitter_1 = require("../sync/submitters/impressionsSubmitter");
var impressionCountsSubmitter_1 = require("../sync/submitters/impressionCountsSubmitter");
var objectAssign_1 = require("../utils/lang/objectAssign");
var constants_1 = require("../logger/constants");
var consent_1 = require("../consent");
var VISIBILITYCHANGE_EVENT = 'visibilitychange';
var PAGEHIDE_EVENT = 'pagehide';
var EVENT_NAME = 'for visibilitychange and pagehide events.';
/**
* We'll listen for events over the window object.
*/
var BrowserSignalListener = /** @class */ (function () {
function BrowserSignalListener(syncManager, settings, storage, serviceApi) {
this.syncManager = syncManager;
this.settings = settings;
this.storage = storage;
this.serviceApi = serviceApi;
this.flushData = this.flushData.bind(this);
this.flushDataIfHidden = this.flushDataIfHidden.bind(this);
this.fromImpressionsCollector = impressionsSubmitter_1.fromImpressionsCollector.bind(undefined, settings.core.labelsEnabled);
}
/**
* start method.
* Called when SplitFactory is initialized, it adds event listeners to close streaming and flush impressions and events.
*/
BrowserSignalListener.prototype.start = function () {
this.settings.log.debug(constants_1.CLEANUP_REGISTERING, [EVENT_NAME]);
if (typeof document !== 'undefined' && document.addEventListener) {
// Flush data whenever the page is hidden or unloaded.
document.addEventListener(VISIBILITYCHANGE_EVENT, this.flushDataIfHidden);
}
if (typeof window !== 'undefined' && window.addEventListener) {
// Some browsers, like Safari, does not fire the `visibilitychange` event when the page is being unloaded. Therefore, we also flush data in the `pagehide` event.
// If both events are triggered, the latter will find the storage empty, so no duplicate data will be submitted.
window.addEventListener(PAGEHIDE_EVENT, this.flushData);
}
};
/**
* stop method.
* Called when client is destroyed, it removes event listeners.
*/
BrowserSignalListener.prototype.stop = function () {
this.settings.log.debug(constants_1.CLEANUP_DEREGISTERING, [EVENT_NAME]);
if (typeof document !== 'undefined' && document.removeEventListener) {
document.removeEventListener(VISIBILITYCHANGE_EVENT, this.flushDataIfHidden);
}
if (typeof window !== 'undefined' && window.removeEventListener) {
window.removeEventListener(PAGEHIDE_EVENT, this.flushData);
}
};
/**
* flushData method.
* Called when pagehide event is triggered. It flushed remaining impressions and events to the backend,
* using beacon API if possible, or falling back to regular post transport.
*/
BrowserSignalListener.prototype.flushData = function () {
if (!this.syncManager)
return; // In consumer mode there is not sync manager and data to flush
var _a = this.settings.urls, events = _a.events, telemetry = _a.telemetry;
// Flush impressions & events data if there is user consent
if ((0, consent_1.isConsentGranted)(this.settings)) {
var extraMetadata = {
// sim stands for Sync/Split Impressions Mode
sim: this.settings.sync.impressionsMode
};
this._flushData(events + '/testImpressions/beacon', this.storage.impressions, this.serviceApi.postTestImpressionsBulk, this.fromImpressionsCollector, extraMetadata);
this._flushData(events + '/events/beacon', this.storage.events, this.serviceApi.postEventsBulk);
this._flushData(events + '/testImpressions/count/beacon', this.storage.impressionCounts, this.serviceApi.postTestImpressionsCount, impressionCountsSubmitter_1.fromImpressionCountsCollector);
this._flushData(telemetry + '/v1/keys/cs/beacon', this.storage.uniqueKeys, this.serviceApi.postUniqueKeysBulkCs);
}
// Flush telemetry data
if (this.storage.telemetry)
this._flushData(telemetry + '/v1/metrics/usage/beacon', this.storage.telemetry, this.serviceApi.postMetricsUsage);
};
BrowserSignalListener.prototype.flushDataIfHidden = function () {
// Precondition: document defined
if (document.visibilityState === 'hidden')
this.flushData(); // On a 'visibilitychange' event, flush data if state is hidden
};
BrowserSignalListener.prototype._flushData = function (url, cache, postService, fromCacheToPayload, extraMetadata) {
// if there is data in cache, send it to backend
if (!cache.isEmpty()) {
var dataPayload = fromCacheToPayload ? fromCacheToPayload(cache.pop()) : cache.pop();
if (!this._sendBeacon(url, dataPayload, extraMetadata)) {
postService(JSON.stringify(dataPayload)).catch(function () { }); // no-op to handle possible promise rejection
}
}
};
/**
* _sendBeacon method.
* Util method that check if beacon API is available, build the payload and send it.
* Returns true if beacon API was used successfully, false otherwise.
*/
BrowserSignalListener.prototype._sendBeacon = function (url, data, extraMetadata) {
if (typeof navigator !== 'undefined' && navigator.sendBeacon) {
var json = {
entries: data,
token: this.settings.core.authorizationKey,
sdk: this.settings.version
};
// Extend with endpoint specific metadata where needed
if (extraMetadata)
(0, objectAssign_1.objectAssign)(json, extraMetadata);
// Stringify the payload
var payload = JSON.stringify(json);
// https://xgwang.me/posts/you-may-not-know-beacon/#it-may-throw-error%2C-be-sure-to-catch
try {
return navigator.sendBeacon(url, payload);
}
catch (e) {
return false;
}
}
return false;
};
return BrowserSignalListener;
}());
exports.BrowserSignalListener = BrowserSignalListener;