Skip to content
This repository was archived by the owner on Sep 3, 2022. It is now read-only.
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
4 changes: 4 additions & 0 deletions HISTORY.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# 3.13.3 / 2020-05-26

- feat: add destination middleware

# 3.13.2 / 2020-05-21

- fix: null values should delete cookies
Expand Down
71 changes: 66 additions & 5 deletions lib/analytics.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ var Identify = require('segmentio-facade').Identify;
var SourceMiddlewareChain = require('./middleware').SourceMiddlewareChain;
var IntegrationMiddlewareChain = require('./middleware')
.IntegrationMiddlewareChain;
var DestinationMiddlewareChain = require('./middleware')
.DestinationMiddlewareChain;
var Page = require('segmentio-facade').Page;
var Track = require('segmentio-facade').Track;
var bindAll = require('bind-all');
Expand Down Expand Up @@ -50,6 +52,7 @@ function Analytics() {
this.Integrations = {};
this._sourceMiddlewares = new SourceMiddlewareChain();
this._integrationMiddlewares = new IntegrationMiddlewareChain();
this._destinationMiddlewares = {};
this._integrations = {};
this._readied = false;
this._timeout = 300;
Expand Down Expand Up @@ -111,6 +114,7 @@ Analytics.prototype.addSourceMiddleware = function(middleware) {

/**
* Define a new `IntegrationMiddleware`
* DEPRECATED
*
* @param {Function} Middleware
* @return {Analytics}
Expand All @@ -121,6 +125,32 @@ Analytics.prototype.addIntegrationMiddleware = function(middleware) {
return this;
};

/**
* Define a new `DestinationMiddleware`
* Destination Middleware is chained after integration middleware
*
* @param {String} integrationName
* @param {Array} Middlewares
* @return {Analytics}
*/

Analytics.prototype.addDestinationMiddleware = function(
integrationName,
middlewares
) {
var self = this;
middlewares.forEach(function(middleware) {
if (!self._destinationMiddlewares[integrationName]) {
self._destinationMiddlewares[
integrationName
] = new DestinationMiddlewareChain();
}

self._destinationMiddlewares[integrationName].add(middleware);
});
return self;
};

/**
* Initialize with the given integration `settings` and `options`.
*
Expand Down Expand Up @@ -791,12 +821,43 @@ Analytics.prototype._invoke = function(method, facade) {
result = new Facade(result);
}

metrics.increment('analytics_js.integration.invoke', {
method: method,
integration_name: integration.name
});
// apply destination middlewares
// Apply any integration middlewares that exist, then invoke the integration with the result.
if (self._destinationMiddlewares[integration.name]) {
self._destinationMiddlewares[integration.name].applyMiddlewares(
facadeCopy,
integration.name,
function(result) {
Copy link
Contributor

Choose a reason for hiding this comment

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

is there a way, using JSDocs, document the format we expect result to respect?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think you would need to do one line per property like this https://jsdoc.app/tags-param.html#parameters-with-properties

// A nullified payload should not be sent to an integration.
if (result === null) {
self.log(
'Payload to destination "%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);
}
);
} else {
metrics.increment('analytics_js.integration.invoke', {
method: method,
integration_name: integration.name
});

integration.invoke.call(integration, method, result);
integration.invoke.call(integration, method, result);
}
}
);
} catch (e) {
Expand Down
14 changes: 14 additions & 0 deletions lib/middleware.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,20 @@ module.exports.IntegrationMiddlewareChain = function IntegrationMiddlewareChain(
};
};

module.exports.DestinationMiddlewareChain = function DestinationMiddlewareChain() {
var apply = middlewareChain(this);

this.applyMiddlewares = function(facade, integration, callback) {
return apply(
function(mw, payload, next) {
mw({ payload: payload, integration: integration, next: next });
},
facade,
callback
);
};
};

// Chain is essentially a linked list of middlewares to run in order.
function middlewareChain(dest) {
var middlewares = [];
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@segment/analytics.js-core",
"author": "Segment <friends@segment.com>",
"version": "3.13.2",
"version": "3.13.3",
"description": "The hassle-free way to integrate analytics into any web application.",
"keywords": [
"analytics",
Expand Down
45 changes: 45 additions & 0 deletions test/analytics.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2058,6 +2058,51 @@ describe('Analytics', function() {
});
});

describe('#addDestinationMiddleware', function() {
it('should have a defined _integrationMiddlewares property', function() {
assert(analytics._destinationMiddlewares !== undefined);
});

it('should allow users to add a valid Middleware', function() {
try {
analytics.addDestinationMiddleware('foo', [function() {}]);
} catch (e) {
// This assert should not run.
assert(false, 'error was incorrectly thrown!');
}
});

it('should throw an error if the selected Middleware is not a function', function() {
try {
analytics.addDestinationMiddleware('foo', [7]);

// This assert should not run.
assert(false, 'error was not thrown!');
} catch (e) {
assert(
e.message === 'attempted to add non-function middleware',
'wrong error return'
);
}
});

it('should not throw an error if AJS has already initialized', function() {
analytics.init();
try {
analytics.addDestinationMiddleware('foo', [function() {}]);
} catch (e) {
// This assert should not run.
assert(false, 'error was thrown!');
}
});

it('should return the analytics object', function() {
assert(
analytics === analytics.addDestinationMiddleware('foo', [function() {}])
);
});
});

describe('#addSourceMiddleware', function() {
it('should have a defined _sourceMiddlewares property', function() {
assert(analytics._sourceMiddlewares !== undefined);
Expand Down