Skip to content

Commit

Permalink
Add support for configuring multiple SNS topics
Browse files Browse the repository at this point in the history
  • Loading branch information
martinj committed Feb 20, 2017
1 parent 170524a commit bec034b
Show file tree
Hide file tree
Showing 4 changed files with 149 additions and 58 deletions.
56 changes: 56 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,3 +109,59 @@ Register subscription and set custom topic and subscription attributes on startu
});

```

Configuring multiple SNS topics

```javascript

const Hapi = require('hapi');
const server = new Hapi.Server();
server.connection();

server
.register({
register: require('hookido'),
options: [{
topic: {
arn: 'arn:to:mytopic'
},
aws: {
region: 'eu-west-1',
accessKeyId: 'a',
secretAccessKey: 'a'
},
route: {
path: '/path/used/in/subscription'
},
handlers: {
notification: (req, reply, payload) => {
console.log('Got notification from SNS', payload);
reply('OK');
}
}
}, {
topic: {
arn: 'arn:to:mytopic2'
},
aws: {
region: 'eu-central-1',
accessKeyId: 'b',
secretAccessKey: 'b'
},
route: {
path: '/second/path'
},
handlers: {
notification: (req, reply, payload) => {
console.log('Got notification from SNS', payload);
reply('OK');
}
}
}]
})
.then(() => server.start())
.then(() => {
console.log('Server running and accepting SNS notifications');
});

```
17 changes: 6 additions & 11 deletions handlers.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,12 @@
const Hoek = require('hoek');
const request = require('request-prom');

exports.hook = ({handlers, skipPayloadValidation, topic}) => {
exports.hook = (sns, {handlers, skipPayloadValidation, topic}) => {
handlers = Hoek.applyToDefaults({
subscriptionconfirmation: confirmSubscription.bind(null, topic)
subscriptionconfirmation: confirmSubscription.bind(null, sns, topic)
}, handlers);

return (req, reply) => {
const sns = req.server.plugins.hookido.sns;

sns
.validatePayload(req.payload, skipPayloadValidation)
.then(dispatchToHandler.bind(null, handlers, req, reply))
Expand All @@ -30,13 +28,11 @@ function dispatchToHandler(handlers, req, reply, payload) {
return handler(req, reply, payload);
}

function confirmSubscription(topicOpts, req, reply, payload) {
const sns = req.server.plugins.hookido.sns;

function confirmSubscription(sns, topicOpts, req, reply, payload) {
return request
.get(payload.SubscribeURL)
.then((res) => {
req.log(['hookido', 'info'], 'SNS subscription confirmed');
req.log(['hookido', 'info'], `SNS subscription confirmed for ${payload.TopicArn}`);
reply().code(200);

const susbscriptionAttr = Hoek.reach(topicOpts, 'subscribe.attributes');
Expand All @@ -45,13 +41,12 @@ function confirmSubscription(topicOpts, req, reply, payload) {
.findSubscriptionArn(topicOpts.arn, topicOpts.subscribe.protocol, topicOpts.subscribe.endpoint)
.then((arn) => sns.setSubscriptionAttributes(arn, susbscriptionAttr))
.catch((err) => {
console.log(err);
req.log(['hookido', 'error'], `Unable to update subscription attributes, err: ${err.message}`);
req.log(['hookido', 'error'], `Unable to update subscription attributes for ${payload.TopicArn}, err: ${err.message}`);
});
}
})
.catch((err) => {
req.log(['hookido', 'error'], `Unable to confirm SNS subscription, err: ${err.message}`);
req.log(['hookido', 'error'], `Unable to confirm SNS subscription for ${payload.TopicArn}, err: ${err.message}`);
throw err;
});
}
86 changes: 45 additions & 41 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ const Hoek = require('hoek');
const SNS = require('./lib/sns');
const handlers = require('./handlers');

const validOptions = Joi.object({
const validOptions = Joi.array().items(Joi.object({
topic: Joi.object({
arn: Joi.string().required(),
attributes: Joi.object().optional().description('Automatically sets topic attributes on onPostStart'),
Expand All @@ -22,61 +22,65 @@ const validOptions = Joi.object({
subscriptionconfirmation: Joi.func().optional().description('If omitted the plugin will handle the subscription confirmation'),
notification: Joi.func().required()
}).required().description('Request handler functions for different kind of sns messages')
});
})).single();

exports.register = function (server, opts, next) {
const results = Joi.validate(opts, validOptions);
Hoek.assert(!results.error, results.error);

const sns = new SNS(opts.aws);
server.expose('sns', sns);
const snsInstances = [];
results.value.forEach((config) => init(config));
server.expose('snsInstances', snsInstances);
return next();

const subscribe = Hoek.reach(opts, 'topic.subscribe');
function init(config) {
const sns = new SNS(config.aws);
snsInstances.push(sns);
const subscribe = Hoek.reach(config, 'topic.subscribe');

function requestSubscription() {
return sns
.subscribe(opts.topic.arn, subscribe.protocol, subscribe.endpoint)
.then(() => server.log(['hookido', 'subscribe'], 'Subscription request sent'));
}

if (subscribe) {
function requestSubscription() {
return sns
.subscribe(config.topic.arn, subscribe.protocol, subscribe.endpoint)
.then(() => server.log(['hookido', 'subscribe'], `Subscription request sent for ${config.topic.arn}`));
}

server.ext('onPostStart', (srv, next) => {
sns
.findSubscriptionArn(opts.topic.arn, subscribe.protocol, subscribe.endpoint)
.then(() => server.log(['hookido', 'subscribe'], 'Subscription already exists'))
.catch({code: 'NOT_FOUND'}, requestSubscription)
.catch({code: 'PENDING'}, requestSubscription)
.catch((err) => server.log(['hookido', 'subscribe', 'error'], err));
if (subscribe) {

next();
});
server.ext('onPostStart', (srv, next) => {
sns
.findSubscriptionArn(config.topic.arn, subscribe.protocol, subscribe.endpoint)
.then(() => server.log(['hookido', 'subscribe'], `Subscription already exists for ${config.topic.arn}`))
.catch({code: 'NOT_FOUND'}, requestSubscription)
.catch({code: 'PENDING'}, requestSubscription)
.catch((err) => server.log(['hookido', 'subscribe', 'error'], err));

}
next();
});

const topicAttributes = Hoek.reach(opts, 'topic.attributes');
if (topicAttributes) {
server.ext('onPostStart', (srv, next) => {
sns
.setTopicAttributes(opts.topic.arn, topicAttributes)
.then(() => server.log(['hookido', 'setTopicAttributes'], 'topicAttributes was updated'))
.catch((err) => server.log(['hookido', 'setTopicAttributes', 'error'], err));
}

next();
});
}
const topicAttributes = Hoek.reach(config, 'topic.attributes');
if (topicAttributes) {
server.ext('onPostStart', (srv, next) => {
sns
.setTopicAttributes(config.topic.arn, topicAttributes)
.then(() => server.log(['hookido', 'setTopicAttributes'], `topicAttributes was updated for ${config.topic.arn}`))
.catch((err) => server.log(['hookido', 'setTopicAttributes', 'error'], err));

server.route(Hoek.applyToDefaults({
method: 'POST',
path: '/hookido',
config: {
auth: false,
description: 'SNS webhook endpoint',
handler: handlers.hook(opts)
next();
});
}
}, opts.route || {}));

next();
server.route(Hoek.applyToDefaults({
method: 'POST',
path: '/hookido',
config: {
auth: false,
description: 'SNS webhook endpoint',
handler: handlers.hook(sns, config)
}
}, config.route || {}));
}
};

exports.register.attributes = {
Expand Down
48 changes: 42 additions & 6 deletions test/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ describe('Hookido Hapi Plugin', () => {
return done(err);
}

server.plugins.hookido.sns.findSubscriptionArn = (arn, protocol, endpoint) => {
server.plugins.hookido.snsInstances[0].findSubscriptionArn = (arn, protocol, endpoint) => {
return Promise.resolve('foo');
};

Expand Down Expand Up @@ -100,13 +100,13 @@ describe('Hookido Hapi Plugin', () => {
return done(err);
}

server.plugins.hookido.sns.findSubscriptionArn = (arn, protocol, endpoint) => {
server.plugins.hookido.snsInstances[0].findSubscriptionArn = (arn, protocol, endpoint) => {
const err = new Error();
err.code = 'NOT_FOUND';
return Promise.reject(err);
};

server.plugins.hookido.sns.subscribe = (arn, protocol, endpoint) => {
server.plugins.hookido.snsInstances[0].subscribe = (arn, protocol, endpoint) => {

expect(arn).to.equal('foo');
expect(protocol).to.equal('HTTP');
Expand Down Expand Up @@ -150,13 +150,13 @@ describe('Hookido Hapi Plugin', () => {
return done(err);
}

server.plugins.hookido.sns.findSubscriptionArn = (arn, protocol, endpoint) => {
server.plugins.hookido.snsInstances[0].findSubscriptionArn = (arn, protocol, endpoint) => {
const err = new Error();
err.code = 'PENDING';
return Promise.reject(err);
};

server.plugins.hookido.sns.subscribe = (arn, protocol, endpoint) => {
server.plugins.hookido.snsInstances[0].subscribe = (arn, protocol, endpoint) => {

expect(arn).to.equal('foo');
expect(protocol).to.equal('HTTP');
Expand Down Expand Up @@ -199,7 +199,7 @@ describe('Hookido Hapi Plugin', () => {
return done(err);
}

server.plugins.hookido.sns.setTopicAttributes = (topic, attributes) => {
server.plugins.hookido.snsInstances[0].setTopicAttributes = (topic, attributes) => {

expect(topic).to.equal('foo');
expect(attributes).to.deep.equal({foo: 'bar'});
Expand Down Expand Up @@ -384,4 +384,40 @@ describe('Hookido Hapi Plugin', () => {
});

});

it('supports multiple configurations', (done) => {

const server = new Hapi.Server();
server.connection();

server.register({
register: plugin,
options: [{
route: {
path: '/foobar'
},
handlers: {
notification: () => {}
}
}, {
route: {
path: '/foobar2'
},
handlers: {
notification: () => {}
}
}]
}, (err) => {
if (err) {
return done(err);
}

const table = server.connections[0].table();
expect(table[0].path).to.equal('/foobar');
expect(table[1].path).to.equal('/foobar2');
expect(server.plugins.hookido.snsInstances).to.have.a.lengthOf(2);
done();
});

});
});

0 comments on commit bec034b

Please sign in to comment.