Skip to content
This repository was archived by the owner on Sep 3, 2022. It is now read-only.

Commit c515df7

Browse files
aultimusMatthew Ault
andauthored
Add destination mw (#148)
* feat: add addDestinationMiddleware function WIP * feat: fix bug whereby changes made by integration mw were not persisted This bug meant that this code did not send an event with a userId=bar to segment.io. This change will result in this code sending an event with userId=bar to segment.io: f=function(payload, integration, next) { payload.obj.userId = "bar";next(payload); }; analytics.addIntegrationMiddleware(f); analytics.identify(); * feat: correct doc * feat: update addDestinationMiddleware docs * feat: update dest mw signature to be consistent with source mw * chore: fix syntax error * chore: fix failing tests * chore: fix failing test by enabling integration Co-authored-by: Matthew Ault <[email protected]>
1 parent 64ab7d7 commit c515df7

File tree

3 files changed

+137
-12
lines changed

3 files changed

+137
-12
lines changed

lib/analytics.js

Lines changed: 77 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ var Identify = require('segmentio-facade').Identify;
1414
var SourceMiddlewareChain = require('./middleware').SourceMiddlewareChain;
1515
var IntegrationMiddlewareChain = require('./middleware')
1616
.IntegrationMiddlewareChain;
17+
var DestinationMiddlewareChain = require('./middleware')
18+
.DestinationMiddlewareChain;
1719
var Page = require('segmentio-facade').Page;
1820
var Track = require('segmentio-facade').Track;
1921
var bindAll = require('bind-all');
@@ -50,6 +52,7 @@ function Analytics() {
5052
this.Integrations = {};
5153
this._sourceMiddlewares = new SourceMiddlewareChain();
5254
this._integrationMiddlewares = new IntegrationMiddlewareChain();
55+
this._destinationMiddlewares = {};
5356
this._integrations = {};
5457
this._readied = false;
5558
this._timeout = 300;
@@ -111,6 +114,7 @@ Analytics.prototype.addSourceMiddleware = function(middleware) {
111114

112115
/**
113116
* Define a new `IntegrationMiddleware`
117+
* DEPRECATED
114118
*
115119
* @param {Function} Middleware
116120
* @return {Analytics}
@@ -121,6 +125,32 @@ Analytics.prototype.addIntegrationMiddleware = function(middleware) {
121125
return this;
122126
};
123127

128+
/**
129+
* Define a new `DestinationMiddleware`
130+
* Destination Middleware is chained after integration middleware
131+
*
132+
* @param {String} integrationName
133+
* @param {Array} Middlewares
134+
* @return {Analytics}
135+
*/
136+
137+
Analytics.prototype.addDestinationMiddleware = function(
138+
integrationName,
139+
middlewares
140+
) {
141+
var self = this;
142+
middlewares.forEach(function(middleware) {
143+
if (!self._destinationMiddlewares[integrationName]) {
144+
self._destinationMiddlewares[
145+
integrationName
146+
] = new DestinationMiddlewareChain();
147+
}
148+
149+
self._destinationMiddlewares[integrationName].add(middleware);
150+
});
151+
return self;
152+
};
153+
124154
/**
125155
* Initialize with the given integration `settings` and `options`.
126156
*
@@ -734,11 +764,6 @@ Analytics.prototype._invoke = function(method, facade) {
734764
result = new Facade(result);
735765
}
736766

737-
self.emit('invoke', result);
738-
metrics.increment('analytics_js.invoke', {
739-
method: method
740-
});
741-
742767
applyIntegrationMiddlewares(result);
743768
}
744769
);
@@ -791,12 +816,53 @@ Analytics.prototype._invoke = function(method, facade) {
791816
result = new Facade(result);
792817
}
793818

794-
metrics.increment('analytics_js.integration.invoke', {
795-
method: method,
796-
integration_name: integration.name
797-
});
798-
799-
integration.invoke.call(integration, method, result);
819+
// apply destination middlewares
820+
// Apply any integration middlewares that exist, then invoke the integration with the result.
821+
if (self._destinationMiddlewares[integration.name]) {
822+
self._destinationMiddlewares[integration.name].applyMiddlewares(
823+
facadeCopy,
824+
integration.name,
825+
function(result) {
826+
// A nullified payload should not be sent to an integration.
827+
if (result === null) {
828+
self.log(
829+
'Payload to destination "%s" was null and dropped by a middleware.',
830+
name
831+
);
832+
return;
833+
}
834+
835+
// Check if the payload is still a Facade. If not, convert it to one.
836+
if (!(result instanceof Facade)) {
837+
result = new Facade(result);
838+
}
839+
840+
self.emit('invoke', result);
841+
metrics.increment('analytics_js.invoke', {
842+
method: method
843+
});
844+
845+
metrics.increment('analytics_js.integration.invoke', {
846+
method: method,
847+
integration_name: integration.name
848+
});
849+
850+
integration.invoke.call(integration, method, result);
851+
}
852+
);
853+
} else {
854+
self.emit('invoke', result);
855+
metrics.increment('analytics_js.invoke', {
856+
method: method
857+
});
858+
859+
metrics.increment('analytics_js.integration.invoke', {
860+
method: method,
861+
integration_name: integration.name
862+
});
863+
864+
integration.invoke.call(integration, method, result);
865+
}
800866
}
801867
);
802868
} catch (e) {

lib/middleware.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,20 @@ module.exports.IntegrationMiddlewareChain = function IntegrationMiddlewareChain(
3434
};
3535
};
3636

37+
module.exports.DestinationMiddlewareChain = function DestinationMiddlewareChain() {
38+
var apply = middlewareChain(this);
39+
40+
this.applyMiddlewares = function(facade, integration, callback) {
41+
return apply(
42+
function(mw, payload, next) {
43+
mw({ payload: payload, integration: integration, next: next });
44+
},
45+
facade,
46+
callback
47+
);
48+
};
49+
};
50+
3751
// Chain is essentially a linked list of middlewares to run in order.
3852
function middlewareChain(dest) {
3953
var middlewares = [];

test/analytics.test.js

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -495,7 +495,7 @@ describe('Analytics', function() {
495495
});
496496

497497
it('should emit "invoke" with facade', function(done) {
498-
var opts = { All: false };
498+
var opts = { All: true };
499499
var identify = new Identify({ testVal: 'success', options: opts });
500500
analytics.on('invoke', function(msg) {
501501
assert(msg instanceof Facade);
@@ -2058,6 +2058,51 @@ describe('Analytics', function() {
20582058
});
20592059
});
20602060

2061+
describe('#addDestinationMiddleware', function() {
2062+
it('should have a defined _integrationMiddlewares property', function() {
2063+
assert(analytics._destinationMiddlewares !== undefined);
2064+
});
2065+
2066+
it('should allow users to add a valid Middleware', function() {
2067+
try {
2068+
analytics.addDestinationMiddleware('foo', [function() {}]);
2069+
} catch (e) {
2070+
// This assert should not run.
2071+
assert(false, 'error was incorrectly thrown!');
2072+
}
2073+
});
2074+
2075+
it('should throw an error if the selected Middleware is not a function', function() {
2076+
try {
2077+
analytics.addDestinationMiddleware('foo', [7]);
2078+
2079+
// This assert should not run.
2080+
assert(false, 'error was not thrown!');
2081+
} catch (e) {
2082+
assert(
2083+
e.message === 'attempted to add non-function middleware',
2084+
'wrong error return'
2085+
);
2086+
}
2087+
});
2088+
2089+
it('should not throw an error if AJS has already initialized', function() {
2090+
analytics.init();
2091+
try {
2092+
analytics.addDestinationMiddleware('foo', [function() {}]);
2093+
} catch (e) {
2094+
// This assert should not run.
2095+
assert(false, 'error was thrown!');
2096+
}
2097+
});
2098+
2099+
it('should return the analytics object', function() {
2100+
assert(
2101+
analytics === analytics.addDestinationMiddleware('foo', [function() {}])
2102+
);
2103+
});
2104+
});
2105+
20612106
describe('#addSourceMiddleware', function() {
20622107
it('should have a defined _sourceMiddlewares property', function() {
20632108
assert(analytics._sourceMiddlewares !== undefined);

0 commit comments

Comments
 (0)