@segment/analytics.js-core
Advanced tools
Comparing version 3.9.0 to 3.10.0
@@ -14,3 +14,5 @@ 'use strict'; | ||
var Identify = require('segmentio-facade').Identify; | ||
var MiddlewareChain = require('./middleware').Chain; | ||
var SourceMiddlewareChain = require('./middleware').SourceMiddlewareChain; | ||
var IntegrationMiddlewareChain = require('./middleware') | ||
.IntegrationMiddlewareChain; | ||
var Page = require('segmentio-facade').Page; | ||
@@ -51,3 +53,4 @@ var Track = require('segmentio-facade').Track; | ||
this.Integrations = {}; | ||
this._integrationMiddlewares = new MiddlewareChain(); | ||
this._sourceMiddlewares = new SourceMiddlewareChain(); | ||
this._integrationMiddlewares = new IntegrationMiddlewareChain(); | ||
this._integrations = {}; | ||
@@ -101,2 +104,19 @@ this._readied = false; | ||
/** | ||
* Define a new `SourceMiddleware` | ||
* | ||
* @param {Function} Middleware | ||
* @return {Analytics} | ||
*/ | ||
Analytics.prototype.addSourceMiddleware = function(middleware) { | ||
if (this.initialized) | ||
throw new Error( | ||
'attempted to add a source middleware after initialization' | ||
); | ||
this._sourceMiddlewares.add(middleware); | ||
return this; | ||
}; | ||
/** | ||
* Define a new `IntegrationMiddleware` | ||
@@ -110,3 +130,5 @@ * | ||
if (this.initialized) | ||
throw new Error('attempted to add middleware after initialization'); | ||
throw new Error( | ||
'attempted to add an integration middleware after initialization' | ||
); | ||
@@ -695,65 +717,102 @@ this._integrationMiddlewares.add(middleware); | ||
var self = this; | ||
metrics.increment('analytics_js.invoke', { | ||
method: method | ||
}); | ||
this.emit('invoke', facade); | ||
var failedInitializations = self.failedInitializations || []; | ||
each(function(integration, name) { | ||
var facadeCopy = extend(true, new Facade({}), facade); | ||
try { | ||
this._sourceMiddlewares.applyMiddlewares( | ||
extend(true, new Facade({}), facade), | ||
this._integrations, | ||
function(result) { | ||
// A nullified payload should not be sent. | ||
if (result === null) { | ||
self.log( | ||
'Payload with method "%s" was null and dropped by source a middleware.', | ||
method | ||
); | ||
return; | ||
} | ||
if (!facadeCopy.enabled(name)) return; | ||
// Check if an integration failed to initialize. | ||
// If so, do not process the message as the integration is in an unstable state. | ||
if (failedInitializations.indexOf(name) >= 0) { | ||
self.log( | ||
'Skipping invocation of .%s method of %s integration. Integration failed to initialize properly.', | ||
method, | ||
name | ||
); | ||
} else { | ||
try { | ||
// Apply any integration middlewares that exist, then invoke the integration with the result. | ||
self._integrationMiddlewares.applyMiddlewares( | ||
facadeCopy, | ||
integration.name, | ||
function(result) { | ||
// A nullified payload should not be sent to an integration. | ||
if (result === null) { | ||
self.log( | ||
'Payload to integration "%s" was null and dropped.', | ||
name | ||
); | ||
return; | ||
} | ||
// Check if the payload is still a Facade. If not, convert it to one. | ||
if (!(result instanceof Facade)) { | ||
result = new Facade(result); | ||
} | ||
// Check if the payload is still a Facade. If not, convert it to one. | ||
if (!(result instanceof Facade)) { | ||
result = new Facade(result); | ||
} | ||
self.emit('invoke', result); | ||
metrics.increment('analytics_js.invoke', { | ||
method: method | ||
}); | ||
metrics.increment('analytics_js.integration.invoke', { | ||
method: method, | ||
integration_name: integration.name | ||
}); | ||
applyIntegrationMiddlewares(result); | ||
} | ||
); | ||
} catch (e) { | ||
metrics.increment('analytics_js.invoke.error', { | ||
method: method | ||
}); | ||
self.log( | ||
'Error invoking .%s method of %s integration: %o', | ||
method, | ||
name, | ||
e | ||
); | ||
} | ||
integration.invoke.call(integration, method, result); | ||
} | ||
); | ||
} catch (e) { | ||
metrics.increment('analytics_js.integration.invoke.error', { | ||
method: method, | ||
integration_name: integration.name | ||
}); | ||
return this; | ||
function applyIntegrationMiddlewares(facade) { | ||
var failedInitializations = self.failedInitializations || []; | ||
each(function(integration, name) { | ||
var facadeCopy = extend(true, new Facade({}), facade); | ||
if (!facadeCopy.enabled(name)) return; | ||
// Check if an integration failed to initialize. | ||
// If so, do not process the message as the integration is in an unstable state. | ||
if (failedInitializations.indexOf(name) >= 0) { | ||
self.log( | ||
'Error invoking .%s method of %s integration: %o', | ||
'Skipping invocation of .%s method of %s integration. Integration failed to initialize properly.', | ||
method, | ||
name, | ||
e | ||
name | ||
); | ||
} else { | ||
try { | ||
// Apply any integration middlewares that exist, then invoke the integration with the result. | ||
self._integrationMiddlewares.applyMiddlewares( | ||
facadeCopy, | ||
integration.name, | ||
function(result) { | ||
// A nullified payload should not be sent to an integration. | ||
if (result === null) { | ||
self.log( | ||
'Payload to integration "%s" was null and dropped by a middleware.', | ||
name | ||
); | ||
return; | ||
} | ||
// Check if the payload is still a Facade. If not, convert it to one. | ||
if (!(result instanceof Facade)) { | ||
result = new Facade(result); | ||
} | ||
metrics.increment('analytics_js.integration.invoke', { | ||
method: method, | ||
integration_name: integration.name | ||
}); | ||
integration.invoke.call(integration, method, result); | ||
} | ||
); | ||
} catch (e) { | ||
metrics.increment('analytics_js.integration.invoke.error', { | ||
method: method, | ||
integration_name: integration.name | ||
}); | ||
self.log( | ||
'Error invoking .%s method of %s integration: %o', | ||
method, | ||
name, | ||
e | ||
); | ||
} | ||
} | ||
} | ||
}, this._integrations); | ||
return this; | ||
}, self._integrations); | ||
} | ||
}; | ||
@@ -760,0 +819,0 @@ |
@@ -5,12 +5,44 @@ 'use strict'; | ||
module.exports.SourceMiddlewareChain = function SourceMiddlewareChain() { | ||
var apply = middlewareChain(this); | ||
this.applyMiddlewares = function(facade, integrations, callback) { | ||
return apply( | ||
function(mw, payload, next) { | ||
mw({ | ||
integrations: integrations, | ||
next: next, | ||
payload: payload | ||
}); | ||
}, | ||
facade, | ||
callback | ||
); | ||
}; | ||
}; | ||
module.exports.IntegrationMiddlewareChain = function IntegrationMiddlewareChain() { | ||
var apply = middlewareChain(this); | ||
this.applyMiddlewares = function(facade, integration, callback) { | ||
return apply( | ||
function(mw, payload, next) { | ||
mw(payload, integration, next); | ||
}, | ||
facade, | ||
callback | ||
); | ||
}; | ||
}; | ||
// Chain is essentially a linked list of middlewares to run in order. | ||
function Chain() { | ||
function middlewareChain(dest) { | ||
var middlewares = []; | ||
// Return a copy to prevent external mutations. | ||
this.getMiddlewares = function() { | ||
dest.getMiddlewares = function() { | ||
return middlewares.slice(); | ||
}; | ||
this.add = function(middleware) { | ||
dest.add = function(middleware) { | ||
if (typeof middleware !== 'function') | ||
@@ -26,6 +58,6 @@ throw new Error('attempted to add non-function middleware'); | ||
// fn is the callback to be run once all middlewares have been applied. | ||
this.applyMiddlewares = function(facade, integration, fn) { | ||
return function applyMiddlewares(run, facade, callback) { | ||
if (typeof facade !== 'object') | ||
throw new Error('applyMiddlewares requires a payload object'); | ||
if (typeof fn !== 'function') | ||
if (typeof callback !== 'function') | ||
throw new Error('applyMiddlewares requires a function callback'); | ||
@@ -35,4 +67,4 @@ | ||
var middlewaresToApply = middlewares.slice(); | ||
middlewaresToApply.push(fn); | ||
executeChain(facade, integration, middlewaresToApply, 0); | ||
middlewaresToApply.push(callback); | ||
executeChain(run, facade, middlewaresToApply, 0); | ||
}; | ||
@@ -42,3 +74,3 @@ } | ||
// Go over all middlewares until all have been applied. | ||
function executeChain(payload, integration, middlewares, index) { | ||
function executeChain(run, payload, middlewares, index) { | ||
// If the facade has been nullified, immediately skip to the final middleware. | ||
@@ -59,4 +91,4 @@ if (payload === null) { | ||
if (middlewares[index + 1]) { | ||
mw(payload, integration, function(result) { | ||
executeChain(result, integration, middlewares, ++index); | ||
run(mw, payload, function(result) { | ||
executeChain(run, result, middlewares, ++index); | ||
}); | ||
@@ -69,2 +101,2 @@ } else { | ||
module.exports.Chain = Chain; | ||
module.exports.middlewareChain = middlewareChain; |
{ | ||
"name": "@segment/analytics.js-core", | ||
"author": "Segment <friends@segment.com>", | ||
"version": "3.9.0", | ||
"version": "3.10.0", | ||
"description": "The hassle-free way to integrate analytics into any web application.", | ||
@@ -6,0 +6,0 @@ "keywords": [ |
@@ -5,8 +5,11 @@ 'use strict'; | ||
var Facade = require('segmentio-facade'); | ||
var MiddlewareChain = require('../lib/middleware').Chain; | ||
var SourceMiddlewareChain = require('../lib/middleware').SourceMiddlewareChain; | ||
var IntegrationMiddlewareChain = require('../lib/middleware') | ||
.IntegrationMiddlewareChain; | ||
var middlewareChain = require('../lib/middleware').middlewareChain; | ||
describe('middleware', function() { | ||
describe('middlewareChain', function() { | ||
var chain; | ||
beforeEach(function() { | ||
chain = new MiddlewareChain(); | ||
middlewareChain((chain = {})); | ||
}); | ||
@@ -72,3 +75,10 @@ | ||
}); | ||
}); | ||
describe('IntegrationMiddlewareChain', function() { | ||
var chain; | ||
beforeEach(function() { | ||
chain = new IntegrationMiddlewareChain(); | ||
}); | ||
describe('#applyMiddlewares', function() { | ||
@@ -115,2 +125,15 @@ it('should require a function callback', function() { | ||
it('should apply no middleware', function() { | ||
var payload = new Facade({ | ||
testVal: 'success' | ||
}); | ||
chain.applyMiddlewares(payload, 'Test', function(payload) { | ||
assert( | ||
payload.obj.testVal === 'success', | ||
'payload value incorrectly set' | ||
); | ||
}); | ||
}); | ||
it('should apply a middleware', function() { | ||
@@ -192,1 +215,138 @@ chain.add(function(payload, integration, next) { | ||
}); | ||
describe('SourceMiddlewareChain', function() { | ||
var chain; | ||
beforeEach(function() { | ||
chain = new SourceMiddlewareChain(); | ||
}); | ||
describe('#applyMiddlewares', function() { | ||
it('should require a function callback', function() { | ||
try { | ||
chain.applyMiddlewares({}); | ||
} catch (e) { | ||
assert( | ||
e.message === 'applyMiddlewares requires a function callback', | ||
'wrong error return' | ||
); | ||
} | ||
try { | ||
chain.applyMiddlewares({}, 'Test', ['this is not a function =(']); | ||
} catch (e) { | ||
assert( | ||
e.message === 'applyMiddlewares requires a function callback', | ||
'wrong error return' | ||
); | ||
} | ||
}); | ||
}); | ||
it('should require a payload object', function() { | ||
try { | ||
chain.applyMiddlewares(7, 'Test', function() { | ||
// This assert should not run. | ||
assert(false, 'error was not thrown!'); | ||
}); | ||
} catch (e) { | ||
assert( | ||
e.message === 'applyMiddlewares requires a payload object', | ||
'wrong error return' | ||
); | ||
} | ||
chain.applyMiddlewares(null, 'Test', function(payload) { | ||
// This assert SHOULD run. | ||
assert(payload === null, 'payload should have been null'); | ||
}); | ||
}); | ||
it('should apply no middleware', function() { | ||
var payload = new Facade({ | ||
testVal: 'success' | ||
}); | ||
chain.applyMiddlewares(payload, 'Test', function(payload) { | ||
assert( | ||
payload.obj.testVal === 'success', | ||
'payload value incorrectly set' | ||
); | ||
}); | ||
}); | ||
it('should apply a middleware', function() { | ||
chain.add(function(chain) { | ||
chain.payload.obj.testVal = 'success'; | ||
chain.next(chain.payload); | ||
}); | ||
chain.applyMiddlewares({}, 'Test', function(payload) { | ||
assert( | ||
payload.obj.testVal === 'success', | ||
'payload value incorrectly set' | ||
); | ||
}); | ||
}); | ||
it('should apply multiple middlewares in order', function() { | ||
chain.add(function(chain) { | ||
chain.payload.obj.test.push(1); | ||
chain.next(chain.payload); | ||
}); | ||
chain.add(function(chain) { | ||
chain.payload.obj.test.push(2); | ||
chain.next(chain.payload); | ||
}); | ||
chain.add(function(chain) { | ||
chain.payload.obj.test.push(3); | ||
chain.next(chain.payload); | ||
}); | ||
chain.applyMiddlewares({ test: [] }, 'Test', function(payload) { | ||
assert.deepEqual(payload.obj.test, [1, 2, 3]); | ||
}); | ||
}); | ||
it('should stop running middlewares if payload becomes null', function() { | ||
chain.add(function(chain) { | ||
chain.payload.obj.test.push(1); | ||
chain.next(chain.payload); | ||
}); | ||
chain.add(function(chain) { | ||
chain.next(null); | ||
}); | ||
chain.add(function() { | ||
throw new Error('Middleware chain was not interrupted by null!'); | ||
}); | ||
chain.applyMiddlewares({ test: [] }, 'Test', function(payload) { | ||
assert(payload === null, 'payload was not nullified'); | ||
}); | ||
}); | ||
it('should convert non-facade objects to facades', function() { | ||
chain.add(function(chain) { | ||
chain.next(chain.payload); | ||
}); | ||
var payload = {}; | ||
assert(!(payload instanceof Facade), 'Payload should not be a facade yet.'); | ||
chain.applyMiddlewares(payload, 'Test', function(payload) { | ||
assert(payload instanceof Facade, 'Payload should be a facade.'); | ||
}); | ||
}); | ||
it('should be able to handle facade objects as input', function() { | ||
chain.add(function(chain) { | ||
chain.next(chain.payload); | ||
}); | ||
var payload = new Facade({}); | ||
assert(payload instanceof Facade, 'Payload should be a facade.'); | ||
chain.applyMiddlewares(payload, 'Test', function(payload) { | ||
assert(payload instanceof Facade, 'Payload should still be a facade.'); | ||
}); | ||
}); | ||
}); |
Sorry, the diff of this file is too big to display
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
474312
5581