UNPKG

@splitsoftware/splitio-commons

Version:
119 lines (118 loc) 6.17 kB
import { fromImpressionsCollector } from '../sync/submitters/impressionsSubmitter'; import { fromImpressionCountsCollector } from '../sync/submitters/impressionCountsSubmitter'; import { objectAssign } from '../utils/lang/objectAssign'; import { CLEANUP_REGISTERING, CLEANUP_DEREGISTERING } from '../logger/constants'; import { isConsentGranted } from '../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 = 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(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(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 (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, 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) 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; }()); export { BrowserSignalListener };