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
1 change: 1 addition & 0 deletions lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ exports.min = function(options) {

function defaults(options) {
Copy link
Contributor

@silesky silesky Jan 19, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

options || (options = {});
options.globalAnalyticsKey || (options.globalAnalyticsKey = 'analytics');
options.apiKey || (options.apiKey = 'YOUR_API_KEY');
options.host || (options.host = 'cdn.segment.com');
options.ajsPath || (options.ajsPath = '/analytics.js/v1/\" + key + \"/analytics.min.js');
Expand Down
11 changes: 8 additions & 3 deletions template/snippet.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
(function() {
// define the key where the global analytics object will be accessible
// customers can safely set this to be something else if need be
var globalAnalyticsKey = "<%= settings.globalAnalyticsKey %>"

// Create a queue, but don't obliterate an existing one!
var analytics = window.analytics = window.analytics || [];
var analytics = window[globalAnalyticsKey] = window[globalAnalyticsKey] || [];

// If the real analytics.js is already on the page return.
if (analytics.initialize) return;
Expand Down Expand Up @@ -49,10 +53,10 @@
// stored as the first argument, so we can replay the data.
analytics.factory = function(e) {
return function() {
if (window.analytics.initialized) {
if (window[globalAnalyticsKey].initialized) {
// Sometimes users assigned analytics to a variable before analytics is done loading, resulting in a stale reference.
// If so, proxy any calls to the 'real' analytics instance.
return window.analytics[e].apply(window.analytics, arguments);
return window[globalAnalyticsKey][e].apply(window[globalAnalyticsKey], arguments);
}
var args = Array.prototype.slice.call(arguments);

Expand Down Expand Up @@ -90,6 +94,7 @@
var t = document.createElement("script");
t.type = "text/javascript";
t.async = true;
t.setAttribute("data-global-segment-analytics-key", globalAnalyticsKey)
t.src = "https://<%= settings.host %><%= settings.ajsPath %>";

// Insert our script next to the first script element.
Expand Down
65 changes: 56 additions & 9 deletions test/snippet.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,12 @@ describe('snippet', function() {
t: '',
r: document.referrer
};

before(function() {
snippet = Function(render.max({
// https://app.segment.com/segment-libraries/sources/snippet/settings/keys
apiKey: 'zCueSsEKipbrRgqbJarlTG8UJsAZWpkm'
}));
});

beforeEach(function() {
var setup = function(options) {
snippet = Function(render.max(Object.assign({}, {
// https://app.segment.com/segment-libraries/sources/snippet/settings/keys
apiKey: 'zCueSsEKipbrRgqbJarlTG8UJsAZWpkm',
}, options)));
sandbox = sinon.sandbox.create();
origConsole = window.console;
origError = window.console.error;
Expand All @@ -42,7 +39,7 @@ describe('snippet', function() {
sandbox.spy(window.console, 'error');
window.analytics = undefined;
snippet();
});
};

afterEach(function() {
sandbox.restore();
Expand All @@ -51,27 +48,45 @@ describe('snippet', function() {
});

it('should define a global queue', function() {
setup()
assert(window.analytics instanceof Array);
});

it('works with different globalAnalyticsKey', function () {
setup({
globalAnalyticsKey: 'segment_analytics'
})

assert(window.analytics === undefined)
assert(window.segment_analytics instanceof Array)
assert(typeof window.segment_analytics.track === 'function')
assert(typeof window.segment_analytics.identify === 'function')
// check that the custom global key is exposed to the main runtime through a data attribute
assert(document.querySelector('script[data-global-segment-analytics-key]').dataset.globalSegmentAnalyticsKey === 'segment_analytics')
})

it('should load the script once', function() {
setup()
var scripts = document.scripts;
var length = scripts.length;
Function(snippet)();
assert(length === scripts.length);
});

it('should set SNIPPET_VERSION to module version', function() {
setup()
assert(require('../package.json').version === window.analytics.SNIPPET_VERSION);
});

it('should warn using console.error when the snippet is included > 1', function() {
setup()
snippet();
var args = window.console.error.args;
assert.equal('Segment snippet included twice.', args[0][0]);
});

it('should ignore the snippet when the real analytics is already included', function() {
setup()
var ajs = { initialize: function() {} };
window.analytics = ajs;
snippet();
Expand All @@ -81,20 +96,23 @@ describe('snippet', function() {
});

it('should not call .page() again when included > 1', function() {
setup()
window.analytics = { invoked: true, page: sandbox.spy() };
snippet();
var args = window.analytics.page.args;
assert.equal(0, args.length);
});

it('should not error when window.console is unavailable', function() {
setup()
window.analytics.included = true;
window.console = null;
snippet();
});

describe('.page', function() {
it('should call .page by default', function() {
setup()
assert.strictEqual(window.analytics[0][0], 'page');
});
});
Expand All @@ -103,6 +121,7 @@ describe('snippet', function() {
['track', 'screen', 'alias', 'group', 'page', 'identify'].forEach(
function(method) {
it(method + ' should have a buffered page context', function() {
setup()
window.analytics[method]('foo');
var lastCall = window.analytics[window.analytics.length - 1];
assert.deepStrictEqual(lastCall, [method, 'foo', bufferedPageContext]);
Expand All @@ -113,112 +132,138 @@ describe('snippet', function() {

describe('.methods', function() {
it('should define analytics.js methods', function() {
setup()
assert(window.analytics.methods instanceof Array);
});

it('.identify', function() {
setup()
assert(arrayContains(window.analytics.methods, 'identify'));
});

it('.track', function() {
setup()
assert(arrayContains(window.analytics.methods, 'track'));
});

it('.trackLink', function() {
setup()
assert(arrayContains(window.analytics.methods, 'trackLink'));
});

it('.trackForm', function() {
setup()
assert(arrayContains(window.analytics.methods, 'trackForm'));
});

it('.trackClick', function() {
setup()
assert(arrayContains(window.analytics.methods, 'trackClick'));
});

it('.trackSubmit', function() {
setup()
assert(arrayContains(window.analytics.methods, 'trackSubmit'));
});

it('.page', function() {
setup()
assert(arrayContains(window.analytics.methods, 'page'));
});

it('.pageview', function() {
setup()
assert(arrayContains(window.analytics.methods, 'pageview'));
});

it('.alias', function() {
setup()
assert(arrayContains(window.analytics.methods, 'alias'));
});

it('.ready', function() {
setup()
assert(arrayContains(window.analytics.methods, 'ready'));
});

it('.group', function() {
setup()
assert(arrayContains(window.analytics.methods, 'group'));
});

it('.on', function() {
setup()
assert(arrayContains(window.analytics.methods, 'on'));
});

it('.once', function() {
setup()
assert(arrayContains(window.analytics.methods, 'once'));
});

it('.off', function() {
setup()
assert(arrayContains(window.analytics.methods, 'off'));
});

it('.addSourceMiddleware', function() {
setup()
assert(arrayContains(window.analytics.methods, 'addSourceMiddleware'));
});

it('.addIntegrationMiddleware', function() {
setup()
assert(arrayContains(window.analytics.methods, 'addIntegrationMiddleware'));
});

it('.setAnonymousId', function() {
setup()
assert(arrayContains(window.analytics.methods, 'setAnonymousId'));
});

it('.addDestinationMiddleware', function() {
setup()
assert(arrayContains(window.analytics.methods, 'addDestinationMiddleware'));
});

it('.screen', function() {
setup()
assert(arrayContains(window.analytics.methods, 'screen'));
});

it('.register', function() {
setup()
assert(arrayContains(window.analytics.methods, 'register'));
});
});

describe('.factory', function() {
it('should define a factory', function() {
setup()
assert.strictEqual(typeof window.analytics.factory, 'function');
});

it('should return a queue stub', function() {
setup()
assert.strictEqual(typeof window.analytics.factory('test'), 'function');
});

it('should push arguments onto the stub', function() {
setup()
var stub = window.analytics.factory('test');
stub(1, 2, 3);
var args = window.analytics[window.analytics.length - 1];
assert.deepEqual(args, ['test', 1, 2, 3]);
});

it('should return the analytics object', function() {
setup()
var stub = window.analytics.factory();
assert(window.analytics === stub());
});

it('should generate a stub for each method', function() {
setup()
for (var i = 0; i < window.analytics.methods.length; i++) {
var method = window.analytics.methods[i];
assert.strictEqual(typeof window.analytics[method], 'function');
Expand All @@ -228,10 +273,12 @@ describe('snippet', function() {

describe('.load', function() {
it('should define a load method', function() {
setup()
assert.strictEqual(typeof window.analytics.load, 'function');
});

it('should load analytics.js from the server', function(done) {
setup()
var id = setInterval(function() {
if (typeof window.analytics === 'object') {
clearInterval(id);
Expand Down
6 changes: 6 additions & 0 deletions types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,12 @@ declare module '@segment/snippet' {
* to tell Analytics JS where to fetch bundles from.
*/
useHostForBundles?: boolean

/**
* They key at which the global Analytics object will become accessible on the
* window namespace
*/
globalAnalyticsKey?: string
}

/**
Expand Down