Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion CHANGES.txt
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
0.8.0 (July 22, 2022)
- Added `autoRequire` configuration option to the Google Analytics to Split integration, which takes care of requiring the splitTracker plugin on trackers dynamically created by Google tag managers (See https://help.split.io/hc/en-us/articles/360040838752#set-up-with-gtm-and-gtag.js).
- Updated browser listener to push remaining impressions and events on 'visibilitychange' and 'pagehide' DOM events, instead of 'unload', which is not reliable in modern mobile and desktop Web browsers.
- Updated the synchronization flow to be more reliable in the event of an edge case generating delay in cache purge propagation, keeping the SDK cache properly synced.
- Updated some dependencies for vulnerability fixes.

0.7.0 (June 29, 2022)
- Added a new config option to control the tasks that listen or poll for updates on feature flags and segments, via the new config sync.enabled . Running online Split will always pull the most recent updates upon initialization, this only affects updates fetching on a running instance. Useful when a consistent session experience is a must or to save resources when updates are not being used.
- Updated telemetry logic to track the anonymous config for user consent flag set to declined or unknown.
- Updated submitters logic, to avoid duplicating the post of impressions to Split cloud when the SDK is destroyed while its periodic post of impressions is running.

- Bugfixing - Updated submitters logic, to avoid dropping impressions and events that are being tracked while POST request is pending.

0.6.0 (May 24, 2022)
- Added `scheduler.telemetryRefreshRate` property to SDK configuration.
Expand Down
501 changes: 269 additions & 232 deletions package-lock.json

Large diffs are not rendered by default.

14 changes: 8 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@splitsoftware/splitio-browserjs",
"version": "0.7.0",
"version": "0.8.0",
"description": "Split SDK for Javascript on Browser",
"main": "cjs/index.js",
"module": "esm/index.js",
Expand All @@ -14,7 +14,8 @@
"esm",
"src",
"types",
"full"
"full",
"scripts/ga-to-split-autorequire.js"
],
"scripts": {
"check": "npm run check:lint && npm run check:types && npm run check:version",
Expand All @@ -26,6 +27,7 @@
"build:cjs": "rimraf cjs && tsc -outDir cjs -m CommonJS && ./scripts/build_cjs_replace_imports.sh",
"build:umd-visualizer": "rollup --config rollup.visualizer.config.js",
"build:umd": "rollup --config rollup.ci.config.js --branch=$BUILD_BRANCH --commit_hash=$BUILD_COMMIT",
"build:ga-to-split-autorequire": "terser ./node_modules/@splitsoftware/splitio-commons/src/integrations/ga/autoRequire.js --mangle --output ./scripts/ga-to-split-autorequire.js",
"test": "npm run test:unit && npm run test:e2e",
"test:unit": "jest",
"test:e2e": "npm run test:e2e-logger && npm run test:e2e-offline && npm run test:e2e-online && npm run test:e2e-destroy && npm run test:e2e-errors && npm run test:e2e-push && npm run test:e2e-gaintegration && npm run test:e2e-consumer",
Expand Down Expand Up @@ -61,7 +63,7 @@
"bugs": "https://github.com/splitio/javascript-browser-client/issues",
"homepage": "https://github.com/splitio/javascript-browser-client#readme",
"dependencies": {
"@splitsoftware/splitio-commons": "1.5.0",
"@splitsoftware/splitio-commons": "1.6.1",
"@types/google.analytics": "0.0.40"
},
"devDependencies": {
Expand All @@ -77,9 +79,9 @@
"eslint-plugin-import": "^2.25.4",
"fetch-mock": "^9.11.0",
"jest": "^27.2.3",
"karma": "^6.3.16",
"karma-chrome-launcher": "^3.1.0",
"karma-rollup-preprocessor": "^7.0.5",
"karma": "^6.4.0",
"karma-chrome-launcher": "^3.1.1",
"karma-rollup-preprocessor": "^7.0.8",
"karma-tap": "^4.2.0",
"replace": "^1.2.1",
"rimraf": "^3.0.2",
Expand Down
1 change: 1 addition & 0 deletions scripts/ga-to-split-autorequire.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion src/__tests__/destroy/browser.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@ const settings = settingsValidator({

fetchMock.getOnce(url(settings, '/splitChanges?since=-1'), { status: 200, body: splitChangesMock1 });
fetchMock.getOnce(url(settings, '/splitChanges?since=-1500492097547'), { status: 200, body: splitChangesMock2 });

fetchMock.getOnce(url(settings, '/mySegments/ut1'), { status: 200, body: mySegmentsMock });
fetchMock.getOnce(url(settings, '/mySegments/ut2'), { status: 200, body: mySegmentsMock });
fetchMock.getOnce(url(settings, '/mySegments/ut3'), { status: 200, body: mySegmentsMock });
fetchMock.postOnce(url(settings, '/v1/metrics/config'), 200); // 0.1% sample rate

tape('SDK destroy for BrowserJS', async function (assert) {
const config = {
Expand Down
3 changes: 2 additions & 1 deletion src/__tests__/gaIntegration/browser.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,11 @@ const settings = settingsValidator({
}
});

tape('## E2E CI Tests ##', function(assert) {
tape('## E2E CI Tests ##', function (assert) {

fetchMock.get(url(settings, '/splitChanges?since=-1'), { status: 200, body: splitChangesMock1 });
fetchMock.get(url(settings, '/mySegments/facundo%40split.io'), { status: 200, body: mySegmentsFacundo });
fetchMock.post(/\/v1\/metrics/, 200); // 0.1% sample rate

/* Validate GA integration */
assert.test('E2E / GA-to-Split', gaToSplitSuite.bind(null, fetchMock));
Expand Down
46 changes: 46 additions & 0 deletions src/__tests__/gaIntegration/ga-to-split.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -406,4 +406,50 @@ export default function (fetchMock, assert) {

});

// test 'autoRequire' script placed right after GA script tag.
// We get same result if it is placed right before, and also applies for Universal Analytics configured with GTM and gtag.js tags.
// If it is executed asynchronously, trackers creation might be missed.
assert.test(t => {
fetchMock.postOnce(url(settings, '/events/bulk'), (url, opts) => {
const resp = JSON.parse(opts.body);
const sentHitsTracker1 = window.gaSpy.getHits('tracker1');
const sentHitsTracker2 = window.gaSpy.getHits('tracker2');

t.equal(resp.length, sentHitsTracker1.length + sentHitsTracker2.length, 'All hits of all trackers are captured as Split events');

setTimeout(() => {
client.destroy();
t.end();
});
return 200;
});

gaTag();

// Run autoRequire iife. `require` cannot be used because it is not polyfilled by "rollup-plugin-node-polyfills"
// eslint-disable-next-line
(function(n,t,e){n[e]=n[e]||t;n[t]=n[t]||function(){n[t].q.push(arguments)};n[t].q=n[t].q||[];var r={};function i(n){return typeof n==="object"&&typeof n.name==="string"&&n.name}function o(e){if(e&&e[0]==="create"){var o=i(e[1])||i(e[2])||i(e[3])||(typeof e[3]==="string"?e[3]:undefined);if(!r[o]){r[o]=true;n[t]((o?o+".":"")+"require","splitTracker")}}}n[t].q.forEach(o);var u=n[t].q.push;n[t].q.push=function(n){var t=u.apply(this,arguments);o(n);return t}})(window,"ga","GoogleAnalyticsObject");

window.ga('create', 'UA-00000000-1', { name: 'tracker1', cookieDomain: 'auto', siteSpeedSampleRate: 0 });

gaSpy(['tracker1']);

window.ga('tracker1.send', 'event', 'mycategory', 'myaction1'); // Captured

const factory = SplitFactory({
...config,
integrations: [GoogleAnalyticsToSplit({
autoRequire: true
})],
});

window.ga('tracker1.send', 'event', 'mycategory', 'myaction2'); // Captured
window.ga('create', 'UA-00000001-1', 'auto', 'tracker2', { siteSpeedSampleRate: 0 }); // New tracker
gaSpy(['tracker2'], false);
window.ga('tracker2.send', 'event', 'mycategory', 'myaction3'); // Captured

client = factory.client();

});

}
12 changes: 7 additions & 5 deletions src/__tests__/gaIntegration/gaTestUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,21 @@ export const DEFAULT_TRACKER = 't0';

const HIT_FIELDS = ['hitType', 'nonInteraction'];
const EVENT_FIELDS = ['eventCategory', 'eventAction', 'eventLabel', 'eventValue'];
const FIELDS = [...HIT_FIELDS, ...EVENT_FIELDS]; // List of hit fields to spy, which are the ones set by the default SplitToGa mapper.

let hits = {};

/**
* Spy ga hits per tracker.
*
* @param {string[]} trackerNames names of the trackers to spy. If not provided, it spies the default tracker. i.e., `gaSpy()` is equivalent to `gaSpy(['t0'])`.
* @param {string[]} fieldNames names of the hit fields to spy. If not provided, it spies hit and event related fields. i.e., 'hitType', 'nonInteraction',
* 'eventCategory', 'eventAction', 'eventLabel', and 'eventValue', which are the ones set by default SplitToGa mapper.
* @param {boolean} resetSpy true to reset the list of captured hits.
*
* @see {@link https://developers.google.com/analytics/devguides/collection/analyticsjs/field-reference}
*/
export function gaSpy(trackerNames = [DEFAULT_TRACKER], fieldNames = [...HIT_FIELDS, ...EVENT_FIELDS]) {
export function gaSpy(trackerNames = [DEFAULT_TRACKER], resetSpy = true) {

const hits = {};
if (resetSpy) hits = {};

// access ga via its gaAlias, accounting for the possibility that the global command queue
// has been renamed or not yet defined (analytics.js mutates window[gaAlias] reference)
Expand All @@ -31,7 +33,7 @@ export function gaSpy(trackerNames = [DEFAULT_TRACKER], fieldNames = [...HIT_FIE
trackerToSniff.set('sendHitTask', function (model) {
originalSendHitTask(model);
const hit = {};
fieldNames.forEach(fieldName => {
FIELDS.forEach(fieldName => {
hit[fieldName] = model.get(fieldName);
});
hits[trackerName].push(hit);
Expand Down
7 changes: 4 additions & 3 deletions src/__tests__/online/browser.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ tape('## E2E CI Tests ##', function (assert) {
fetchMock.get(url(settings, '/mySegments/emmanuel%40split.io'), { status: 200, body: mySegmentsEmmanuel });
fetchMock.post(url(settings, '/testImpressions/bulk'), 200);
fetchMock.post(url(settings, '/testImpressions/count'), 200);
Math.random = () => 0.5; // SDKs without telemetry

/* Check client evaluations. */
assert.test('E2E / In Memory', evaluationsSuite.bind(null, configInMemory, fetchMock));
Expand Down Expand Up @@ -119,9 +120,9 @@ tape('## E2E CI Tests ##', function (assert) {
assert.test('E2E / Readiness', readinessSuite.bind(null, fetchMock));
/* Validate headers for ip and hostname are not sended with requests (ignore setting IPAddressesEnabled) */
assert.test('E2E / Ignore setting IPAddressesEnabled', ignoreIpAddressesSettingSuite.bind(null, fetchMock));
/* Check that impressions and events are sended to backend via Beacon API or Fetch when page unload is triggered. */
assert.test('E2E / Use Beacon API (or Fetch if not available) to send remaining impressions and events when browser page is unload', useBeaconApiSuite.bind(null, fetchMock));
assert.test('E2E / Use Beacon API DEBUG (or Fetch if not available) to send remaining impressions and events when browser page is unload', useBeaconDebugApiSuite.bind(null, fetchMock));
/* Check that impressions and events are sended to backend via Beacon API or Fetch when pagehide/visibilitychange events are triggered. */
assert.test('E2E / Use Beacon API (or Fetch if not available) to send remaining impressions and events when browser page is unload or hidden', useBeaconApiSuite.bind(null, fetchMock));
assert.test('E2E / Use Beacon API DEBUG (or Fetch if not available) to send remaining impressions and events when browser page is unload or hidden', useBeaconDebugApiSuite.bind(null, fetchMock));
/* Validate ready from cache behaviour (might be merged into another suite if we end up having simple behavior around it as expected) */
assert.test('E2E / Readiness from cache', readyFromCache.bind(null, fetchMock));
/* Validate readiness with ready promises */
Expand Down
10 changes: 5 additions & 5 deletions src/__tests__/online/impressions.debug.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ import { DEBUG } from '@splitsoftware/splitio-commons/src/utils/constants';
import { url } from '../testUtils';

const baseUrls = {
sdk: 'https://sdk.baseurl/impressionsSuite',
events: 'https://events.baseurl/impressionsSuite'
sdk: 'https://sdk.baseurl/impressionsDebugSuite',
events: 'https://events.baseurl/impressionsDebugSuite'
};

const settings = settingsValidator({
Expand Down Expand Up @@ -83,12 +83,12 @@ export default function (fetchMock, assert) {
assert.equal(req.headers.SplitSDKImpressionsMode, DEBUG);
assertPayload(req);

client.destroy();
assert.end();
client.destroy().then(() => {
assert.end();
});

return 200;
});
fetchMock.postOnce(url(settings, '/testImpressions/bulk'), 200);

client.ready().then(() => {
client.getTreatment('split_with_config');
Expand Down
7 changes: 3 additions & 4 deletions src/__tests__/online/impressions.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -84,12 +84,12 @@ export default function (fetchMock, assert) {
assert.comment('We do one retry, so after a failed impressions post we will try once more.');
assertPayload(req);

client.destroy();
assert.end();
client.destroy().then(() => {
assert.end();
});

return 200;
});
fetchMock.postOnce(url(settings, '/testImpressions/bulk'), 200);

fetchMock.postOnce(url(settings, '/testImpressions/count'), (url, opts) => {
const data = JSON.parse(opts.body);
Expand All @@ -109,7 +109,6 @@ export default function (fetchMock, assert) {

return 200;
});
fetchMock.postOnce(url(settings, '/testImpressions/count'), 200);

client.ready().then(() => {
truncatedTimeFrame = truncateTimeFrame(Date.now());
Expand Down
23 changes: 12 additions & 11 deletions src/__tests__/online/use-beacon-api.debug.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { settingsValidator } from '../../settings';
import splitChangesMock1 from '../mocks/splitchanges.since.-1.json';
import mySegmentsFacundo from '../mocks/mysegments.facundo@split.io.json';
import { DEBUG } from '@splitsoftware/splitio-commons/src/utils/constants';
import { url, triggerUnloadEvent } from '../testUtils';
import { url, triggerPagehideEvent, triggerVisibilitychange } from '../testUtils';

const config = {
core: {
Expand Down Expand Up @@ -60,7 +60,7 @@ const assertCallsToBeaconAPI = (assert) => {
assertEventSent(assert, parsedPayload.entries[0]);
};

// This E2E test checks that Beacon API is not called when page unload is triggered and there are not events or impressions to send.
// This E2E test checks that Beacon API is not called when page is hidden and there are not events or impressions to send.
function beaconApiNotSendTestDebug(fetchMock, assert) {
sendBeaconSpyDebug = sinon.spy(window.navigator, 'sendBeacon');

Expand All @@ -74,8 +74,9 @@ function beaconApiNotSendTestDebug(fetchMock, assert) {
const client = splitio.client();
client.on(client.Event.SDK_READY, () => {

// trigger unload event, without tracked events and impressions
triggerUnloadEvent();
// trigger events, without tracked events and impressions
triggerPagehideEvent();
triggerVisibilitychange();

// destroy the client and execute the next E2E test named beaconApiSendTest
setTimeout(() => {
Expand All @@ -89,7 +90,7 @@ function beaconApiNotSendTestDebug(fetchMock, assert) {
});
}

// This E2E test checks that impressions and events are sent to backend via Beacon API when page unload is triggered.
// This E2E test checks that impressions and events are sent to backend via Beacon API when page is hidden.
function beaconApiSendTestDebug(fetchMock, assert) {

// Init and run Split client
Expand All @@ -99,8 +100,8 @@ function beaconApiSendTestDebug(fetchMock, assert) {
client.getTreatment('hierarchical_splits_test');
client.track('sometraffictype', 'someEvent', 10);

// trigger unload event inmmediatly, before scheduled push of events and impressions
triggerUnloadEvent();
// trigger visibilitychange event inmmediatly, before scheduled push of events and impressions
triggerVisibilitychange();

// queue the assertion of the Beacon requests, destroy the client and execute the next E2E test named fallbackTest
setTimeout(() => {
Expand All @@ -114,7 +115,7 @@ function beaconApiSendTestDebug(fetchMock, assert) {
});
}

// This E2E test checks that impressions and events are sent to backend via Axios when page unload is triggered and Beacon API is not available.
// This E2E test checks that impressions and events are sent to backend via Fetch API when page is hidden and Beacon API is not available.
function fallbackTest(fetchMock, assert) {

// destroy reference to Beacon API
Expand All @@ -134,7 +135,7 @@ function fallbackTest(fetchMock, assert) {
}, 0);
})();

// Mock endpoints used by Axios
// Mock endpoints
fetchMock.postOnce(url(settings, '/testImpressions/bulk'), (url, opts) => {
const resp = JSON.parse(opts.body);
assert.ok(opts, 'Fallback to /testImpressions/bulk');
Expand All @@ -153,8 +154,8 @@ function fallbackTest(fetchMock, assert) {
client.on(client.Event.SDK_READY, () => {
client.getTreatment('hierarchical_splits_test');
client.track('sometraffictype', 'someEvent', 10);
// trigger unload event inmmediatly, before scheduled push of events and impressions
triggerUnloadEvent();
// trigger pagehide event inmmediatly, before scheduled push of events and impressions
triggerPagehideEvent();
});
}

Expand Down
Loading