diff --git a/lib/index.js b/lib/index.js index bc4d63f..d86be67 100644 --- a/lib/index.js +++ b/lib/index.js @@ -55,6 +55,7 @@ exports.min = function(options) { function defaults(options) { 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'); diff --git a/template/snippet.js b/template/snippet.js index 75bfbeb..678ce6c 100644 --- a/template/snippet.js +++ b/template/snippet.js @@ -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; @@ -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); @@ -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. diff --git a/test/snippet.test.js b/test/snippet.test.js index fc45f42..8e7d839 100644 --- a/test/snippet.test.js +++ b/test/snippet.test.js @@ -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; @@ -42,7 +39,7 @@ describe('snippet', function() { sandbox.spy(window.console, 'error'); window.analytics = undefined; snippet(); - }); + }; afterEach(function() { sandbox.restore(); @@ -51,10 +48,25 @@ 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)(); @@ -62,16 +74,19 @@ describe('snippet', function() { }); 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(); @@ -81,6 +96,7 @@ 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; @@ -88,6 +104,7 @@ describe('snippet', function() { }); it('should not error when window.console is unavailable', function() { + setup() window.analytics.included = true; window.console = null; snippet(); @@ -95,6 +112,7 @@ describe('snippet', function() { describe('.page', function() { it('should call .page by default', function() { + setup() assert.strictEqual(window.analytics[0][0], 'page'); }); }); @@ -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]); @@ -113,100 +132,124 @@ 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]; @@ -214,11 +257,13 @@ describe('snippet', function() { }); 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'); @@ -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); diff --git a/types.d.ts b/types.d.ts index 7ae77d0..a468793 100644 --- a/types.d.ts +++ b/types.d.ts @@ -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 } /**